最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang實(shí)現(xiàn)單元測試中的接口層

 更新時(shí)間:2023年03月11日 10:52:08   作者:LinkinStar  
接口層主要負(fù)責(zé)的就是請求的處理,最常見的就是?HTTP?請求的處理。這篇文章主要為大家介紹了Golang如何實(shí)現(xiàn)單元測試中的接口層,需要的可以參考一下

上次我們已經(jīng)搞定了邏輯層的單元測試,這次我們來康康接口層的單元測試。接口層主要負(fù)責(zé)的就是請求的處理,最常見的就是 HTTP 請求的處理。

但針對 接口層 的單元測試其實(shí)是可以五花八門的。它并不像邏輯層和數(shù)據(jù)層一樣的通用,對于它的測試往往有很多路可以走。

由于使用的 HTTP 框架不同,單元測試的實(shí)現(xiàn)方式則不同。 既可以通過程序來模擬 HTTP 請求,也可以通過真實(shí)的 HTTP 請求來測試,通過借助外部的一些測試工具來實(shí)現(xiàn)。

所以本文只能給出一種思路,具體的實(shí)現(xiàn)方式還是要根據(jù)實(shí)際的框架來實(shí)現(xiàn)。

環(huán)境

本文以常用的 gin 框架為例,使用一種個(gè)人比較喜歡也非常簡單的方式來實(shí)現(xiàn)單元測試。特點(diǎn)主要有:

  • 不需要啟動(dòng)路由服務(wù)
  • 復(fù)用已有的項(xiàng)目內(nèi)的請求結(jié)構(gòu)

代碼

由于之前已經(jīng)貼過,所以 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 的單元測試已經(jīng)通過,這次我們就需要 mock 的是 service 層的接口 mockgen -source=./user.go -destination=../mock/user_service_mock.go -package=mock
  • 這里我將請求和返回的結(jié)構(gòu) 如:GetUserRequest、GetUserResponse 放在了這里僅僅是為了方便展示代碼

單元測試

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

工具方法

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

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()
}

// 將結(jié)構(gòu)體轉(zhuǎn)換為 URL 參數(shù)
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 請求常常參數(shù)并不會特別復(fù)雜,通常的幾種類型就應(yīng)該可以包括,有需要可以繼續(xù)添加
func valueToString(v interface{}) string {
    switch v := v.(type) {
    case int:
        return strconv.Itoa(v)
    case string:
        return v
    default:
        return ""
    }
}

既然我們不想啟動(dòng)路由,其實(shí)最關(guān)鍵的問題就在如何構(gòu)建一個(gè) gin.Context 來模擬正常的請求。

  • 通過 gin.CreateTestContext 創(chuàng)建一個(gè)我們需要模擬的 context
  • 通過 http.NewRequest 來創(chuàng)建我們需要的請求結(jié)構(gòu)

單元測試

有了我們的工具方法,那么編寫單元測試的時(shí)候就非常方便了,mock 方法和之前類似,剩下要調(diào)用對應(yīng)的方法就可以了。并且這里可以復(fù)用我們已經(jīng)在原有程序中使用的 請求結(jié)構(gòu) 如 GetUserRequest 這樣就可以不需要重新勞動(dòng)了。

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)
}

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

問題

當(dāng)然以上述方式來實(shí)現(xiàn)單元測試的話,是會遺漏一些問題,畢竟偷懶是要有代價(jià)的。

  • 路由路徑的問題:可以看到上述的單元測試中并沒有注冊對應(yīng)的 url 地址,那么實(shí)際中可能會由于代碼路由的書寫錯(cuò)誤而導(dǎo)致 404 的情況
  • 請求結(jié)構(gòu)字段錯(cuò)誤:由于我們復(fù)用了原有代碼中的請求結(jié)構(gòu),即使單詞拼寫錯(cuò)誤依然能成功,因?yàn)閮蛇叾家粯渝e(cuò),所以即使字段名稱與接口文檔不一致也無法發(fā)現(xiàn)。

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

優(yōu)化點(diǎn)

當(dāng)然,這里的舉例還是過于簡單,實(shí)際中的請求往往會比較復(fù)雜。

  • 實(shí)際場景往往一些請求需要鑒權(quán),這個(gè)可以在根據(jù)實(shí)際你的鑒權(quán)方式在前面添加中間件統(tǒng)一來處理登錄就可以
  • 其他類型的請求也是類似的如 PUT、DELETE 等
  • 當(dāng)前只是簡單的處理了正常的 200 HTTP Code 還會出現(xiàn)其他異常的情況也需要按實(shí)際接口進(jìn)行處理

總結(jié)

通常從現(xiàn)象來說,這一層的測試往往發(fā)現(xiàn)的問題比較少,是由于這一層的邏輯少,測試下來最常見的問題往往就是字段名稱和限制條件不滿足需求。所以其實(shí)從性價(jià)比的角度來說,單獨(dú)對這層拿出來測試往往比較低,故實(shí)際中見到的比較少。

不過話又說回來了,本文的目的不僅僅是為了讓你了解到可以這樣寫單元測試,其中使用的方法往往還能再某些時(shí)候讓你復(fù)用 handler 的方法來保證系統(tǒng)的一致性。

到此這篇關(guān)于Golang實(shí)現(xiàn)單元測試中的接口層的文章就介紹到這了,更多相關(guān)Golang單元測試內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang常用的幾種密碼加密方式分享

    Golang常用的幾種密碼加密方式分享

    這篇文章給大家介紹了Golang常用的幾種密碼加密方式,加密有兩種方式,一種是直接加密,一種是鹽值加密,直接加密指的是將原始密碼直接進(jìn)行加密,鹽值加密則是在進(jìn)行密碼加密之前,文中有詳細(xì)的代碼示例,需要的朋友可以參考下
    2023-08-08
  • go實(shí)現(xiàn)腳本解釋器gscript

    go實(shí)現(xiàn)腳本解釋器gscript

    這篇文章主要為大家介紹了go實(shí)現(xiàn)腳本解釋器gscript示例代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • Golang壓縮Jpeg圖片和PNG圖片的操作

    Golang壓縮Jpeg圖片和PNG圖片的操作

    這篇文章主要介紹了Golang壓縮Jpeg圖片和PNG圖片的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go語言基本的語法和內(nèi)置數(shù)據(jù)類型初探

    Go語言基本的語法和內(nèi)置數(shù)據(jù)類型初探

    這篇文章主要介紹了Go語言基本的語法和內(nèi)置數(shù)據(jù)類型,是golang入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下
    2015-10-10
  • 使用goland調(diào)試遠(yuǎn)程代碼的操作步驟

    使用goland調(diào)試遠(yuǎn)程代碼的操作步驟

    大家都知道如何在goland調(diào)試遠(yuǎn)程代碼嗎?今天小編給大家分享一篇教程幫助大家學(xué)習(xí)goland調(diào)試遠(yuǎn)程代碼的操作步驟,感興趣的朋友跟隨小編一起看看吧
    2021-06-06
  • 在Colaboratory上運(yùn)行Go程序的詳細(xì)過程

    在Colaboratory上運(yùn)行Go程序的詳細(xì)過程

    這篇文章主要介紹了在Colaboratory上運(yùn)行Go程序,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-08-08
  • golang gin框架獲取參數(shù)的操作

    golang gin框架獲取參數(shù)的操作

    這篇文章主要介紹了golang gin框架獲取參數(shù)的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • golang 如何獲取文件夾下面的文件列表

    golang 如何獲取文件夾下面的文件列表

    這篇文章主要介紹了golang 獲取文件夾下面的文件列表方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • Go語言學(xué)習(xí)教程之反射的示例詳解

    Go語言學(xué)習(xí)教程之反射的示例詳解

    這篇文章主要通過記錄對reflect包的簡單使用,來對反射有一定的了解。文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Go語言有一定幫助,需要的可以參考一下
    2022-09-09
  • Go語言通過smtp發(fā)送郵件的方法

    Go語言通過smtp發(fā)送郵件的方法

    這篇文章主要介紹了Go語言通過smtp發(fā)送郵件的方法,涉及Go語言發(fā)送郵件的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02

最新評論

固阳县| 高唐县| 错那县| 黄山市| 色达县| 磴口县| 福海县| 西乌珠穆沁旗| 桐柏县| 房产| 北川| 安顺市| 湾仔区| 丁青县| 五河县| 海安县| 长白| 香河县| 达日县| 绥化市| 沈阳市| 红桥区| 石门县| 开化县| 郁南县| 乐亭县| 平南县| 潞城市| 西吉县| 龙里县| 涞水县| 岳普湖县| 治县。| 上林县| 大竹县| 吉水县| 遂宁市| 临清市| 合水县| 昆山市| 蒲江县|