1. 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 main
        import ( regexp "github.com/dlclark/regexp2" "github.com/gin-gonic/gin" "net/http")
        type UserHandler struct { emailExp *regexp.Regexp passwordExp *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 LoginRequest if 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 的基本步驟如下:

        1. 導(dǎo)入 net/http/httptest 包。

        2. 創(chuàng)建一個(gè) httptest.Server 實(shí)例,并指定你想要的服務(wù)器行為。

        3. 在測(cè)試代碼中使用 httptest.NewRequest 創(chuàng)建一個(gè)模擬的 HTTP 請(qǐng)求,并將其發(fā)送到 httptest.Server

        4. 檢查響應(yīng)內(nèi)容或狀態(tài)碼是否符合預(yù)期。

        以下是一個(gè)簡(jiǎn)單的 httptest 用法示例

        package main
        import ( "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 string reqBody string wantCode int wantBody 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ù)登錄功能的不同情況。

        1. testCases 列表定義了多個(gè)測(cè)試用例,每個(gè)測(cè)試用例包含了測(cè)試名稱(chēng)、請(qǐng)求體、期望的 HTTP 狀態(tài)碼和期望的響應(yīng)體內(nèi)容。

        2. 使用 for 循環(huán)遍歷測(cè)試用例列表,每次循環(huán)創(chuàng)建一個(gè)新的測(cè)試子函數(shù),并在其中模擬 HTTP 請(qǐng)求發(fā)送給登錄接口。

        3. 在每個(gè)測(cè)試子函數(shù)中,先創(chuàng)建一個(gè) Gin 的默認(rèn)上下文和用戶(hù)處理器 UserHandler,然后注冊(cè)路由并創(chuàng)建一個(gè)模擬的 HTTP 請(qǐng)求。

        4. 通過(guò) httptest.NewRecorder() 創(chuàng)建一個(gè)響應(yīng)記錄器,使用 server.ServeHTTP(resp, req) 處理模擬請(qǐng)求,得到響應(yīng)結(jié)果。

        5. 最后使用斷言來(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 的基本用法如下:

        1. 啟動(dòng)攔截器:在測(cè)試開(kāi)始前,使用 gock.New 函數(shù)啟動(dòng)攔截器,并指定你想要攔截的域名和端口。

        2. 定義攔截規(guī)則:你可以使用 gock.Intercept 方法來(lái)定義攔截規(guī)則,比如攔截特定的 URL、方法、頭部信息等。

        3. 設(shè)置響應(yīng):你可以使用 gock.NewJsongock.NewText 等方法來(lái)設(shè)置攔截后的響應(yīng)內(nèi)容。

        4. 運(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ù)的API resp, 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 Result if 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_demo
        import ( "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返回100 gock.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返回200 gock.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)歸原作者所有,侵刪)

        瀏覽 77
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 国产成人AV在线 | 性欧美bbbbbb图片 | 人人超碰人人操 | 国产乱子伦趁熟睡女人 | 国产传媒在线 |