亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Golang如何實現單元測試中的接口層

發布時間:2023-03-11 11:30:27 來源:億速云 閱讀:362 作者:iii 欄目:開發技術

這篇文章主要介紹“Golang如何實現單元測試中的接口層”,在日常操作中,相信很多人在Golang如何實現單元測試中的接口層問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Golang如何實現單元測試中的接口層”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

    環境

    本文以常用的 gin 框架為例,使用一種個人比較喜歡也非常簡單的方式來實現單元測試。特點主要有:

    • 不需要啟動路由服務

    • 復用已有的項目內的請求結構

    代碼

    由于之前已經貼過,所以 service 層的 代碼這里就不贅述了

    base case

    package controller
    
    import (
        "context"
    
        "github.com/gin-gonic/gin"
        "go-demo/m/unit-test/entity"
    )
    
    //go:generate mockgen -source=./user.go -destination=../mock/user_service_mock.go -package=mock
    type UserService interface {
        AddUser(ctx context.Context, username string) (err error)
        GetUser(ctx context.Context, userID int) (user *entity.User, err error)
    }
    
    type AddUserRequest struct {
        Username string `json:"username" binding:"required"`
    }
    
    type GetUserRequest struct {
        UserID int `form:"user_id" binding:"required"`
    }
    
    type GetUserResponse struct {
        Username string `json:"username"`
    }
    
    type UserController struct {
        UserService UserService
    }
    
    func NewUserController(userService UserService) *UserController {
        return &UserController{UserService: userService}
    }
    
    func (uc *UserController) AddUser(ctx *gin.Context) {
        req := &AddUserRequest{}
        if err := ctx.BindJSON(req); err != nil {
            return
        }
        if err := uc.UserService.AddUser(ctx, req.Username); err != nil {
            ctx.JSON(400, gin.H{"error": err.Error()})
            return
        }
        ctx.JSON(200, gin.H{"message": "success"})
    }
    
    func (uc *UserController) GetUser(ctx *gin.Context) {
        req := &GetUserRequest{}
        if err := ctx.BindQuery(req); err != nil {
            return
        }
        user, err := uc.UserService.GetUser(ctx, req.UserID)
        if err != nil {
            ctx.JSON(400, gin.H{"error": err.Error()})
            return
        }
        ctx.JSON(200, &GetUserResponse{Username: user.Username})
    }
    • 既然之前我們 service 的單元測試已經通過,這次我們就需要 mock 的是 service 層的接口 mockgen -source=./user.go -destination=../mock/user_service_mock.go -package=mock

    • 這里我將請求和返回的結構 如:GetUserRequest、GetUserResponse 放在了這里僅僅是為了方便展示代碼

    單元測試

    基礎代碼非常簡單,就是我們常見的,最重要的讓我們來看看單元測試應該怎么寫

    工具方法

    在編寫實際單元測試之前,我們需要一些工具方法來幫助我們構建一些請求。

    func createGetReqCtx(req interface{}, handlerFunc gin.HandlerFunc) (isSuccess bool, resp string) {
        w := httptest.NewRecorder()
        c, _ := gin.CreateTestContext(w)
        encode := structToURLValues(req).Encode()
        c.Request, _ = http.NewRequest("GET", "/?"+encode, nil)
        handlerFunc(c)
        return w.Code == http.StatusOK, w.Body.String()
    }
    
    func createPostReqCtx(req interface{}, handlerFunc gin.HandlerFunc) (isSuccess bool, resp string) {
        responseRecorder := httptest.NewRecorder()
        ctx, _ := gin.CreateTestContext(responseRecorder)
        body, _ := json.Marshal(req)
        ctx.Request, _ = http.NewRequest("POST", "/", bytes.NewBuffer(body))
        ctx.Request.Header.Set("Content-Type", "application/json")
    
        handlerFunc(ctx)
        return responseRecorder.Code == http.StatusOK, responseRecorder.Body.String()
    }
    
    // 將結構體轉換為 URL 參數
    func structToURLValues(s interface{}) url.Values {
        v := reflect.ValueOf(s)
        if v.Kind() == reflect.Ptr {
            v = v.Elem()
        }
        t := v.Type()
    
        values := url.Values{}
        for i := 0; i < t.NumField(); i++ {
            field := t.Field(i)
            tag := field.Tag.Get("form")
            if tag == "" {
                continue
            }
    
            value := v.Field(i).Interface()
            values.Set(tag, valueToString(value))
        }
    
        return values
    }
    
    // 由于 get 請求常常參數并不會特別復雜,通常的幾種類型就應該可以包括,有需要可以繼續添加
    func valueToString(v interface{}) string {
        switch v := v.(type) {
        case int:
            return strconv.Itoa(v)
        case string:
            return v
        default:
            return ""
        }
    }

    既然我們不想啟動路由,其實最關鍵的問題就在如何構建一個 gin.Context 來模擬正常的請求。

    • 通過 gin.CreateTestContext 創建一個我們需要模擬的 context

    • 通過 http.NewRequest 來創建我們需要的請求結構

    單元測試

    有了我們的工具方法,那么編寫單元測試的時候就非常方便了,mock 方法和之前類似,剩下要調用對應的方法就可以了。并且這里可以復用我們已經在原有程序中使用的 請求結構 如 GetUserRequest 這樣就可以不需要重新勞動了。

    package controller
    
    import (
        "fmt"
        "testing"
    
        "github.com/golang/mock/gomock"
        "github.com/stretchr/testify/assert"
        "go-demo/m/unit-test/entity"
        "go-demo/m/unit-test/mock"
    )
    
    func TestUserController_AddUser(t *testing.T) {
        ctl := gomock.NewController(t)
        defer ctl.Finish()
    
        req := &AddUserRequest{Username: "LinkinStar"}
        mockUserService := mock.NewMockUserService(ctl)
        mockUserService.EXPECT().AddUser(gomock.Any(), gomock.Any()).Return(nil)
    
        userController := NewUserController(mockUserService)
    
        success, resp := createPostReqCtx(req, userController.AddUser)
        assert.True(t, success)
        fmt.Println(resp)
    }
    
    func TestUserController_GetUser(t *testing.T) {
        ctl := gomock.NewController(t)
        defer ctl.Finish()
    
        req := &GetUserRequest{UserID: 1}
        user := &entity.User{Username: "LinkinStar"}
        mockUserService := mock.NewMockUserService(ctl)
        mockUserService.EXPECT().GetUser(gomock.Any(), gomock.Any()).Return(user, nil)
    
        userController := NewUserController(mockUserService)
    
        success, resp := createGetReqCtx(req, userController.GetUser)
        assert.True(t, success)
        fmt.Println(resp)
    }

    可以看到測試方法如出一轍,再詳細的話只需要對請求的返回值做解析然后進行斷言即可。

    問題

    當然以上述方式來實現單元測試的話,是會遺漏一些問題,畢竟偷懶是要有代價的。

    • 路由路徑的問題:可以看到上述的單元測試中并沒有注冊對應的 url 地址,那么實際中可能會由于代碼路由的書寫錯誤而導致 404 的情況

    • 請求結構字段錯誤:由于我們復用了原有代碼中的請求結構,即使單詞拼寫錯誤依然能成功,因為兩邊都一樣錯,所以即使字段名稱與接口文檔不一致也無法發現。

    針對這兩個問題,我覺得可以由更加上層的測試來保證,由于這里僅僅是單元測試,我覺得這些代價還是可以接受的。并且,如果是使用 swagger 生成文檔的情況下,也能保證文檔和代碼的統一性。但在此還是要出來提個醒,畢竟實際問題我還是遇到過的。

    優化點

    當然,這里的舉例還是過于簡單,實際中的請求往往會比較復雜。

    • 實際場景往往一些請求需要鑒權,這個可以在根據實際你的鑒權方式在前面添加中間件統一來處理登錄就可以

    • 其他類型的請求也是類似的如 PUT、DELETE 等

    • 當前只是簡單的處理了正常的 200 HTTP Code 還會出現其他異常的情況也需要按實際接口進行處理

    到此,關于“Golang如何實現單元測試中的接口層”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    玛曲县| 洪湖市| 本溪| 靖江市| 瑞丽市| 雷波县| 镇远县| 白河县| 潮安县| 上犹县| 台南县| 乐业县| 宁安市| 黔南| 林甸县| 八宿县| 安新县| 金堂县| 邢台县| 莆田市| 广平县| 上蔡县| 铜川市| 汉中市| 贺兰县| 巴彦淖尔市| 安陆市| 平原县| 乐清市| 庐江县| 建宁县| 苍山县| 龙江县| 双江| 镇雄县| 旺苍县| 阳山县| 凌海市| 农安县| 清镇市| 通州市|