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

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

 更新時(shí)間:2023年03月10日 09:31:47   作者:LinkinStar  
前面我們完成了最麻煩的數(shù)據(jù)層的單元測試,今天我們來看看單元測試中最容易做的一層,數(shù)據(jù)邏輯層,也就是我們通常說的 service 或者 biz 等

前面我們完成了最麻煩的數(shù)據(jù)層的單元測試,今天我們來看看單元測試中最容易做的一層,數(shù)據(jù)邏輯層,也就是我們通常說的 service 或者 biz 等,是描述具體業(yè)務(wù)邏輯的地方,這一層包含我們業(yè)務(wù)最重要的邏輯。

所以它的測試非常重要,通常它測試的通過就意味著你的業(yè)務(wù)邏輯能正常運(yùn)行了。

而如何對它做單元測試呢? 因?yàn)?,這一層的依賴主要來源于數(shù)據(jù)層,通常這一層會(huì)調(diào)用數(shù)據(jù)層的接口來獲取或操作數(shù)據(jù)。 由于我們之前對于數(shù)據(jù)層已經(jīng)做了單元測試,所以這一次,我們需要 mock 的不是數(shù)據(jù)庫了,而是數(shù)據(jù)層。

Golang 提供了 github.com/golang/mock 來實(shí)現(xiàn) mock 接口的操作,本文就是使用它來完成我們的單元測試。

準(zhǔn)備工作

安裝 go install github.com/golang/mock/mockgen@v1.6.0

基本 case 代碼

首先我們還是基于上一次的例子,這里給出上一次例子中所用到的接口

package service

import (
    "context"
    "fmt"

    "go-demo/m/unit-test/entity"
)

type UserRepo interface {
    AddUser(ctx context.Context, user *entity.User) (err error)
    DelUser(ctx context.Context, userID int) (err error)
    GetUser(ctx context.Context, userID int) (user *entity.User, exist bool, err error)
}

type UserService struct {
    userRepo UserRepo
}

func NewUserService(userRepo UserRepo) *UserService {
    return &UserService{userRepo: userRepo}
}

func (us *UserService) AddUser(ctx context.Context, username string) (err error) {
    if len(username) == 0 {
        return fmt.Errorf("username not specified")
    }
    return us.userRepo.AddUser(ctx, &entity.User{Username: username})
}

func (us *UserService) GetUser(ctx context.Context, userID int) (user *entity.User, err error) {
    userInfo, exist, err := us.userRepo.GetUser(ctx, userID)
    if err != nil {
        return nil, err
    }
    if !exist {
        return nil, fmt.Errorf("user %d not found", userID)
    }
    return userInfo, nil
}

可以看到我們的目標(biāo)很明確,就是需要 mock 掉 UserRepo 接口的幾個(gè)方法,就可以測試我們 AddUser 和 GetUser 方法了

生成 mock 接口

使用 mockgen 命令可以生成我們所需要的 mock 接口

mockgen -source=./service/user.go -destination=./mock/user_mock.go -package=mock

參數(shù)名稱都很好理解,我這邊不贅述了。命令執(zhí)行完成之后,會(huì)在 destination 生成對于的 mock 接口,就可以使用了。

生成的代碼大致如下面的樣子,可以簡單瞄一眼:

// Code generated by MockGen. DO NOT EDIT.
// Source: ./user.go

// Package mock is a generated GoMock package.
package mock

import (
    context "context"
    entity "go-demo/m/unit-test/entity"
    reflect "reflect"

    gomock "github.com/golang/mock/gomock"
)

// MockUserRepo is a mock of UserRepo interface.
type MockUserRepo struct {
    ctrl     *gomock.Controller
    recorder *MockUserRepoMockRecorder
}

// MockUserRepoMockRecorder is the mock recorder for MockUserRepo.
type MockUserRepoMockRecorder struct {
    mock *MockUserRepo
}

// NewMockUserRepo creates a new mock instance.
func NewMockUserRepo(ctrl *gomock.Controller) *MockUserRepo {
    mock := &MockUserRepo{ctrl: ctrl}
    mock.recorder = &MockUserRepoMockRecorder{mock}
    return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockUserRepo) EXPECT() *MockUserRepoMockRecorder {
    return m.recorder
}

// AddUser mocks base method.
func (m *MockUserRepo) AddUser(ctx context.Context, user *entity.User) error {
    m.ctrl.T.Helper()
    ret := m.ctrl.Call(m, "AddUser", ctx, user)
    ret0, _ := ret[0].(error)
    return ret0
}

// AddUser indicates an expected call of AddUser.
func (mr *MockUserRepoMockRecorder) AddUser(ctx, user interface{}) *gomock.Call {
    mr.mock.ctrl.T.Helper()
    return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUser", reflect.TypeOf((*MockUserRepo)(nil).AddUser), ctx, user)
}

// DelUser mocks base method.
func (m *MockUserRepo) DelUser(ctx context.Context, userID int) error {
    m.ctrl.T.Helper()
    ret := m.ctrl.Call(m, "DelUser", ctx, userID)
    ret0, _ := ret[0].(error)
    return ret0
}

// DelUser indicates an expected call of DelUser.
func (mr *MockUserRepoMockRecorder) DelUser(ctx, userID interface{}) *gomock.Call {
    mr.mock.ctrl.T.Helper()
    return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DelUser", reflect.TypeOf((*MockUserRepo)(nil).DelUser), ctx, userID)
}

// GetUser mocks base method.
func (m *MockUserRepo) GetUser(ctx context.Context, userID int) (*entity.User, bool, error) {
    m.ctrl.T.Helper()
    ret := m.ctrl.Call(m, "GetUser", ctx, userID)
    ret0, _ := ret[0].(*entity.User)
    ret1, _ := ret[1].(bool)
    ret2, _ := ret[2].(error)
    return ret0, ret1, ret2
}

// GetUser indicates an expected call of GetUser.
func (mr *MockUserRepoMockRecorder) GetUser(ctx, userID interface{}) *gomock.Call {
    mr.mock.ctrl.T.Helper()
    return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockUserRepo)(nil).GetUser), ctx, userID)
}

編寫單元測試

gomock 的單元測試編寫起來也很方便,只需要調(diào)用 EXPECT() 方法,將需要 mock 的接口對應(yīng)需要的返回值就可以了。我們直接來看例子:

package service

import (
    "context"
    "testing"

    "github.com/golang/mock/gomock"
    "github.com/stretchr/testify/assert"
    "go-demo/m/unit-test/entity"
    "go-demo/m/unit-test/mock"
)

func TestUserService_AddUser(t *testing.T) {
    ctl := gomock.NewController(t)
    defer ctl.Finish()

    mockUserRepo := mock.NewMockUserRepo(ctl)
    userInfo := &entity.User{Username: "LinkinStar"}
    // 無論對 AddUser 方法輸入任意參數(shù),均會(huì)返回 userInfo 信息
    mockUserRepo.EXPECT().AddUser(gomock.Any(), gomock.Any()).Return(nil)

    userService := NewUserService(mockUserRepo)
    err := userService.AddUser(context.TODO(), userInfo.Username)
    assert.NoError(t, err)
}

func TestUserService_GetUser(t *testing.T) {
    ctl := gomock.NewController(t)
    defer ctl.Finish()

    userID := 1
    username := "LinkinStar"

    mockUserRepo := mock.NewMockUserRepo(ctl)
    // 只有當(dāng)對于 GetUser 傳入 userID 為 1 時(shí)才會(huì)返回 user 信息
    mockUserRepo.EXPECT().GetUser(context.TODO(), userID).Return(&entity.User{
        ID:       userID,
        Username: username,
    }, true, nil)

    userService := NewUserService(mockUserRepo)
    userInfo, err := userService.GetUser(context.TODO(), userID)
    assert.NoError(t, err)
    assert.Equal(t, username, userInfo.Username)
}

與之前一樣,我們依舊使用 github.com/stretchr/testify 做斷言來驗(yàn)證最終結(jié)果??梢钥吹剑瑔卧獪y試編寫起來并不難。

優(yōu)化

當(dāng)然,如果我們每次修改接口或者新增接口都需要重新執(zhí)行一次命令,一個(gè)文件還好,當(dāng)有很多文件的時(shí)候肯定是非常困難的。所以我們需要使用 go:generate 來優(yōu)化一下。

我們可以在需要 mock 的接口上方加入注釋(注意這里寫的路徑要和實(shí)際路徑相符合):

//go:generate mockgen -source=./user.go -destination=../mock/user_mock.go -package=mock
type UserRepo interface {
    AddUser(ctx context.Context, user *entity.User) (err error)
    DelUser(ctx context.Context, userID int) (err error)
    GetUser(ctx context.Context, userID int) (user *entity.User, exist bool, err error)
}

然后只需要使用命令

go generate ./...

就可以生成全部的 mock 嘞,所以及時(shí)文件很多,只需要利用好 go:generate 也能一次搞定

mockgen

比如針對指定參數(shù),我們偷懶可以都用 Any,但常常還需要用 gomock.Eq() 或 gomock.Not("Sam")有關(guān) gomock 還有很多方法在測試的使用也很有用,詳細(xì)的文檔在:https://pkg.go.dev/github.com/golang/mock/gomock#pkg-index

有關(guān)于 github.com/golang/mock 的使用,官方給出了一些例子,可以參考 https://github.com/golang/mock/tree/main/sample

總結(jié)

其實(shí)通常來說數(shù)據(jù)邏輯層的測試反而不容易出現(xiàn)問題,原因是:我們 mock 的數(shù)據(jù)都是我們想要的數(shù)據(jù)。

所以對于嚴(yán)格的單元測試來說,需要多組數(shù)據(jù)的測試來保證我們在一些特殊場景上能正常運(yùn)行,或者滿足期望運(yùn)行。

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

相關(guān)文章

  • Golang解析yaml文件操作指南

    Golang解析yaml文件操作指南

    之前一直從事java開發(fā),習(xí)慣了使用yaml文件的格式,尤其是清晰的層次結(jié)構(gòu)、注釋,下面這篇文章主要給大家介紹了關(guān)于Golang解析yaml文件的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-09-09
  • 一文帶你全面掌握Go語言中的正則表達(dá)式

    一文帶你全面掌握Go語言中的正則表達(dá)式

    正則表達(dá)式是一種強(qiáng)大的模式匹配工具,能夠在文本中進(jìn)行靈活的搜索和替換操作,本文將介紹?Golang?中的正則表達(dá)式語法,包括常用的匹配符號(hào)、模式修飾符以及示例應(yīng)用,希望對大家有所幫助
    2023-05-05
  • Golang實(shí)現(xiàn)獲取與解析命令行參數(shù)

    Golang實(shí)現(xiàn)獲取與解析命令行參數(shù)

    這篇文章主要為大家詳細(xì)介紹了Golang如何實(shí)現(xiàn)獲取與解析命令行參數(shù),文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以參考一下
    2024-01-01
  • 快速解決Golang Map 并發(fā)讀寫安全的問題

    快速解決Golang Map 并發(fā)讀寫安全的問題

    這篇文章主要介紹了快速解決Golang Map 并發(fā)讀寫安全的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 詳解如何使用Golang實(shí)現(xiàn)自定義規(guī)則引擎

    詳解如何使用Golang實(shí)現(xiàn)自定義規(guī)則引擎

    規(guī)則引擎的功能可以簡化為當(dāng)滿足一些條件時(shí)觸發(fā)一些操作,通常使用 DSL 自定義語法來表述,本文給大家介紹了如何使用Golang實(shí)現(xiàn)自定義規(guī)則引擎,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下
    2024-05-05
  • Go 控制協(xié)程(goroutine)的并發(fā)數(shù)量

    Go 控制協(xié)程(goroutine)的并發(fā)數(shù)量

    控制協(xié)程goroutine的并發(fā)數(shù)量是一個(gè)常見的需求,本文就來介紹一下Go 控制協(xié)程的并發(fā)數(shù)量,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-02-02
  • golang打包成帶圖標(biāo)的exe可執(zhí)行文件

    golang打包成帶圖標(biāo)的exe可執(zhí)行文件

    這篇文章主要給大家介紹了關(guān)于golang打包成帶圖標(biāo)的exe可執(zhí)行文件的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2023-06-06
  • Go基于雪花算法生成隨機(jī)id

    Go基于雪花算法生成隨機(jī)id

    雪花算法是twitter開源的由64位整數(shù)組成的分布式ID,本文主要介紹了Go基于雪花算法生成隨機(jī)id,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-05-05
  • 淺析go語言如何實(shí)現(xiàn)協(xié)程的搶占式調(diào)度的

    淺析go語言如何實(shí)現(xiàn)協(xié)程的搶占式調(diào)度的

    go語言通過GMP模型實(shí)現(xiàn)協(xié)程并發(fā),為了避免單協(xié)程持續(xù)持有線程導(dǎo)致線程隊(duì)列中的其他協(xié)程饑餓問題,設(shè)計(jì)者提出了一個(gè)搶占式調(diào)度機(jī)制,本文會(huì)基于一個(gè)簡單的代碼示例對搶占式調(diào)度過程進(jìn)行深入講解剖析
    2024-04-04
  • go語言的變量定義示例詳解

    go語言的變量定義示例詳解

    這篇文章主要為大家介紹了go語言的變量定義示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12

最新評(píng)論

黄冈市| 三台县| 墨玉县| 宁海县| 巴塘县| 那坡县| 华池县| 蒙山县| 南城县| 定日县| 青阳县| 罗平县| 图木舒克市| 和田县| 衢州市| 焉耆| 鱼台县| 叶城县| 苏尼特左旗| 余姚市| 乳山市| 黔东| 稷山县| 七台河市| 巴林左旗| 翁牛特旗| 灵丘县| 新兴县| 文水县| 汕头市| 九龙坡区| 罗山县| 井陉县| 兰坪| 东山县| 当涂县| 乌拉特前旗| 新安县| 古交市| 井研县| 辽源市|