溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時(shí)間:2023-03-10 10:32:16 來(lái)源:億速云 閱讀:111 作者:iii 欄目:開(kāi)發(fā)技術(shù)

本篇內(nèi)容介紹了“Golang如何實(shí)現(xiàn)單元測(cè)試中的邏輯層”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

準(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è)方法,就可以測(cè)試我們 AddUser 和 GetUser 方法了

生成 mock 接口

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

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

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

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

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

編寫(xiě)單元測(cè)試

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

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"}
    // 無(wú)論對(duì) 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)對(duì)于 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 做斷言來(lái)驗(yàn)證最終結(jié)果??梢钥吹?,單元測(cè)試編寫(xiě)起來(lái)并不難。

優(yōu)化

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

我們可以在需要 mock 的接口上方加入注釋(注意這里寫(xiě)的路徑要和實(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 也能一次搞定。

“Golang如何實(shí)現(xiàn)單元測(cè)試中的邏輯層”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI