Go 單元測(cè)試基本介紹
共 9014字,需瀏覽 19分鐘
·
2024-07-03 22:25
目錄
一、httptest
1.1 前置代碼準(zhǔn)備
1.2 介紹
1.3 基本用法
二、gock
2.4.1 前置代碼
2.4.2 測(cè)試用例
2.1介紹
2.2 安裝
2.3 基本使用
2.4 舉個(gè)例子
一、httptest
1.1 前置代碼準(zhǔn)備
假設(shè)我們的業(yè)務(wù)邏輯是搭建一個(gè)http server端,對(duì)外提供HTTP服務(wù)。用來(lái)處理用戶(hù)登錄請(qǐng)求,用戶(hù)需要輸入郵箱,密碼。
package mainimport (regexp "github.com/dlclark/regexp2""github.com/gin-gonic/gin""net/http")type UserHandler struct {emailExp *regexp.RegexppasswordExp *regexp.Regexp}func (u *UserHandler) RegisterRoutes(server *gin.Engine) {ug := server.Group("/user")ug.POST("/login", u.Login)}func NewUserHandler() *UserHandler {const (emailRegexPattern = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$`)emailExp := regexp.MustCompile(emailRegexPattern, regexp.None)passwordExp := regexp.MustCompile(passwordRegexPattern, regexp.None)return &UserHandler{emailExp: emailExp,passwordExp: passwordExp,}}type LoginRequest struct {Email string `json:"email"`Pwd string `json:"pwd"`}func (u *UserHandler) Login(ctx *gin.Context) {var req LoginRequestif err := ctx.ShouldBindJSON(&req); err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"msg": "參數(shù)不正確!"})return}// 校驗(yàn)郵箱和密碼是否為空if req.Email == "" || req.Pwd == "" {ctx.JSON(http.StatusBadRequest, gin.H{"msg": "郵箱或密碼不能為空"})return}// 正則校驗(yàn)郵箱ok, err := u.emailExp.MatchString(req.Email)if err != nil {ctx.JSON(http.StatusInternalServerError, gin.H{"msg": "系統(tǒng)錯(cuò)誤!"})return}if !ok {ctx.JSON(http.StatusBadRequest, gin.H{"msg": "郵箱格式不正確"})return}// 校驗(yàn)密碼格式ok, err = u.passwordExp.MatchString(req.Pwd)if err != nil {ctx.JSON(http.StatusInternalServerError, gin.H{"msg": "系統(tǒng)錯(cuò)誤!"})return}if !ok {ctx.JSON(http.StatusBadRequest, gin.H{"msg": "密碼必須大于8位,包含數(shù)字、特殊字符"})return}// 校驗(yàn)郵箱和密碼是否匹配特定的值來(lái)確定登錄成功與否if req.Email != "[email protected]" || req.Pwd != "hello#world123" {ctx.JSON(http.StatusBadRequest, gin.H{"msg": "郵箱或密碼不匹配!"})return}ctx.JSON(http.StatusOK, gin.H{"msg": "登錄成功!"})}func InitWebServer(userHandler *UserHandler) *gin.Engine {server := gin.Default()userHandler.RegisterRoutes(server)return server}func main() {uh := &UserHandler{}server := InitWebServer(uh)server.Run(":8080") // 在8080端口啟動(dòng)服務(wù)器}
1.2 介紹
在 Web 開(kāi)發(fā)場(chǎng)景下,單元測(cè)試經(jīng)常需要模擬 HTTP 請(qǐng)求和響應(yīng)。使用 httptest 可以讓我們?cè)跍y(cè)試代碼中創(chuàng)建一個(gè) HTTP 服務(wù)器實(shí)例,并定義特定的請(qǐng)求和響應(yīng)行為,從而模擬真實(shí)世界的網(wǎng)絡(luò)交互,在Go語(yǔ)言中,一般都推薦使用Go標(biāo)準(zhǔn)庫(kù) net/http/httptest 進(jìn)行測(cè)試。
1.3 基本用法
使用 httptest 的基本步驟如下:
導(dǎo)入
net/http/httptest包。創(chuàng)建一個(gè)
httptest.Server實(shí)例,并指定你想要的服務(wù)器行為。在測(cè)試代碼中使用
httptest.NewRequest創(chuàng)建一個(gè)模擬的 HTTP 請(qǐng)求,并將其發(fā)送到httptest.Server。檢查響應(yīng)內(nèi)容或狀態(tài)碼是否符合預(yù)期。
以下是一個(gè)簡(jiǎn)單的 httptest 用法示例
package mainimport ("bytes""github.com/gin-gonic/gin""github.com/stretchr/testify/assert""net/http""net/http/httptest""testing")func TestUserHandler_Login(t *testing.T) {// 定義測(cè)試用例testCases := []struct {name stringreqBody stringwantCode intwantBody string}{{name: "登錄成功",reqBody: `{"email": "[email protected]", "pwd": "hello#world123"}`,wantCode: http.StatusOK,wantBody: `{"msg": "登錄成功!"}`,},{name: "參數(shù)不正確",reqBody: `{"email": "[email protected]", "pwd": "hello#world123",}`,wantCode: http.StatusBadRequest,wantBody: `{"msg": "參數(shù)不正確!"}`,},{name: "郵箱或密碼為空",reqBody: `{"email": "", "pwd": ""}`,wantCode: http.StatusBadRequest,wantBody: `{"msg": "郵箱或密碼不能為空"}`,},{name: "郵箱格式不正確",reqBody: `{"email": "invalidemail", "pwd": "hello#world123"}`,wantCode: http.StatusBadRequest,wantBody: `{"msg": "郵箱格式不正確"}`,},{name: "密碼格式不正確",reqBody: `{"email": "[email protected]", "pwd": "invalidpassword"}`,wantCode: http.StatusBadRequest,wantBody: `{"msg": "密碼必須大于8位,包含數(shù)字、特殊字符"}`,},{name: "郵箱或密碼不匹配",reqBody: `{"email": "[email protected]", "pwd": "hello#world123"}`,wantCode: http.StatusBadRequest,wantBody: `{"msg": "郵箱或密碼不匹配!"}`,},}for _, tc := range testCases {t.Run(tc.name, func(t *testing.T) {// 創(chuàng)建一個(gè) gin 的上下文server := gin.Default()h := NewUserHandler()h.RegisterRoutes(server)// mock 創(chuàng)建一個(gè) http 請(qǐng)求req, err := http.NewRequest(http.MethodPost, // 請(qǐng)求方法"/user/login", // 請(qǐng)求路徑bytes.NewBuffer([]byte(tc.reqBody)), // 請(qǐng)求體)// 斷言沒(méi)有錯(cuò)誤assert.NoError(t, err)// 設(shè)置請(qǐng)求頭req.Header.Set("Content-Type", "application/json")// 創(chuàng)建一個(gè)響應(yīng)resp := httptest.NewRecorder()// 服務(wù)端處理請(qǐng)求server.ServeHTTP(resp, req)// 斷言響應(yīng)碼和響應(yīng)體assert.Equal(t, tc.wantCode, resp.Code)// 斷言 JSON 字符串是否相等assert.JSONEq(t, tc.wantBody, resp.Body.String())})}}
在這個(gè)例子中,我們創(chuàng)建了一個(gè)簡(jiǎn)單的 HTTP 請(qǐng)求,TestUserHandler_Login 函數(shù)定義了一個(gè)測(cè)試函數(shù),用于測(cè)試用戶(hù)登錄功能的不同情況。
testCases列表定義了多個(gè)測(cè)試用例,每個(gè)測(cè)試用例包含了測(cè)試名稱(chēng)、請(qǐng)求體、期望的 HTTP 狀態(tài)碼和期望的響應(yīng)體內(nèi)容。使用
for循環(huán)遍歷測(cè)試用例列表,每次循環(huán)創(chuàng)建一個(gè)新的測(cè)試子函數(shù),并在其中模擬 HTTP 請(qǐng)求發(fā)送給登錄接口。在每個(gè)測(cè)試子函數(shù)中,先創(chuàng)建一個(gè) Gin 的默認(rèn)上下文和用戶(hù)處理器
UserHandler,然后注冊(cè)路由并創(chuàng)建一個(gè)模擬的 HTTP 請(qǐng)求。通過(guò)
httptest.NewRecorder()創(chuàng)建一個(gè)響應(yīng)記錄器,使用server.ServeHTTP(resp, req)處理模擬請(qǐng)求,得到響應(yīng)結(jié)果。最后使用斷言來(lái)驗(yàn)證實(shí)際響應(yīng)的 HTTP 狀態(tài)碼和響應(yīng)體是否與測(cè)試用例中的期望一致。
最后,使用Goland 運(yùn)行測(cè)試,結(jié)果如下:
二、gock
2.1介紹
gock 可以幫助你在測(cè)試過(guò)程中模擬 HTTP 請(qǐng)求和響應(yīng),這對(duì)于測(cè)試涉及外部 API 調(diào)用的應(yīng)用程序非常有用。它可以讓你輕松地定義模擬請(qǐng)求,并驗(yàn)證你的應(yīng)用程序是否正確處理了這些請(qǐng)求。
GitHub 地址:github.com/h2non/gock
2.2 安裝
你可以通過(guò)以下方式安裝 gock:
go get -u github.com/h2non/gock
導(dǎo)入 gock 包:
import "github.com/h2non/gock"
2.3 基本使用
gock 的基本用法如下:
啟動(dòng)攔截器:在測(cè)試開(kāi)始前,使用
gock.New函數(shù)啟動(dòng)攔截器,并指定你想要攔截的域名和端口。定義攔截規(guī)則:你可以使用
gock.Intercept方法來(lái)定義攔截規(guī)則,比如攔截特定的 URL、方法、頭部信息等。設(shè)置響應(yīng):你可以使用
gock.NewJson、gock.NewText等方法來(lái)設(shè)置攔截后的響應(yīng)內(nèi)容。運(yùn)行測(cè)試:在定義了攔截規(guī)則和響應(yīng)后,你可以運(yùn)行測(cè)試,
gock會(huì)攔截你的 HTTP 請(qǐng)求,并返回你設(shè)置的響應(yīng)。
2.4 舉個(gè)例子
2.4.1 前置代碼
如果我們是在代碼中請(qǐng)求外部API的場(chǎng)景(比如通過(guò)API調(diào)用其他服務(wù)獲取返回值)又該怎么編寫(xiě)單元測(cè)試呢?
例如,我們有以下業(yè)務(wù)邏輯代碼,依賴(lài)外部API:http://your-api.com/post提供的數(shù)據(jù)。
// ReqParam API請(qǐng)求參數(shù)type ReqParam struct {X int `json:"x"`}// Result API返回結(jié)果type Result struct {Value int `json:"value"`}func GetResultByAPI(x, y int) int {p := &ReqParam{X: x}b, _ := json.Marshal(p)// 調(diào)用其他服務(wù)的APIresp, err := http.Post("http://your-api.com/post","application/json",bytes.NewBuffer(b),)if err != nil {return -1}body, _ := ioutil.ReadAll(resp.Body)var ret Resultif err := json.Unmarshal(body, &ret); err != nil {return -1}// 這里是對(duì)API返回的數(shù)據(jù)做一些邏輯處理return ret.Value + y}
在對(duì)類(lèi)似上述這類(lèi)業(yè)務(wù)代碼編寫(xiě)單元測(cè)試的時(shí)候,如果不想在測(cè)試過(guò)程中真正去發(fā)送請(qǐng)求或者依賴(lài)的外部接口還沒(méi)有開(kāi)發(fā)完成時(shí),我們可以在單元測(cè)試中對(duì)依賴(lài)的API進(jìn)行mock。
2.4.2 測(cè)試用例
使用gock對(duì)外部API進(jìn)行mock,即mock指定參數(shù)返回約定好的響應(yīng)內(nèi)容。下面的代碼中mock了兩組數(shù)據(jù),組成了兩個(gè)測(cè)試用例。
package gock_demoimport ("testing""github.com/stretchr/testify/assert""gopkg.in/h2non/gock.v1")func TestGetResultByAPI(t *testing.T) {defer gock.Off() // 測(cè)試執(zhí)行后刷新掛起的mock// mock 請(qǐng)求外部api時(shí)傳參x=1返回100gock.New("http://your-api.com").Post("/post").MatchType("json").JSON(map[string]int{"x": 1}).Reply(200).JSON(map[string]int{"value": 100})// 調(diào)用我們的業(yè)務(wù)函數(shù)res := GetResultByAPI(1, 1)// 校驗(yàn)返回結(jié)果是否符合預(yù)期assert.Equal(t, res, 101)// mock 請(qǐng)求外部api時(shí)傳參x=2返回200gock.New("http://your-api.com").Post("/post").MatchType("json").JSON(map[string]int{"x": 2}).Reply(200).JSON(map[string]int{"value": 200})// 調(diào)用我們的業(yè)務(wù)函數(shù)res = GetResultByAPI(2, 2)// 校驗(yàn)返回結(jié)果是否符合預(yù)期assert.Equal(t, res, 202)assert.True(t, gock.IsDone()) // 斷言mock被觸發(fā)}
分享是一種快樂(lè),開(kāi)心是一種態(tài)度!
鏈接:https://www.cnblogs.com/taoxiaoxin/p/18139003
(版權(quán)歸原作者所有,侵刪)
