亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解Go語言單元測(cè)試中如何解決MySQL存儲(chǔ)依賴問題

 更新時(shí)間:2023年07月31日 09:19:01   作者:江湖十年  
MySQL?存儲(chǔ)就是一個(gè)非常常見的外部依賴,這篇文章主要來和大家一起探討在?Go?語言中編寫單元測(cè)試時(shí),如何解決?MySQL?存儲(chǔ)依賴,需要的可以參考一下

在編寫單元測(cè)試的過程中,如果被測(cè)試代碼有外部依賴,為了便于測(cè)試,我們就要想辦法來解決這些外部依賴問題。在做 Web 開發(fā)時(shí),MySQL 存儲(chǔ)就是一個(gè)非常常見的外部依賴,本文就來探討在 Go 語言中編寫單元測(cè)試時(shí),如何解決 MySQL 存儲(chǔ)依賴。

HTTP 服務(wù)程序示例

假設(shè)我們有一個(gè) HTTP 服務(wù)程序?qū)ν馓峁┓?wù),代碼如下:

main.go

package main
import (
	"encoding/json"
	"fmt"
	"gorm.io/gorm"
	"io"
	"net/http"
	"strconv"
	"github.com/julienschmidt/httprouter"
	"github.com/jianghushinian/blog-go-example/test/mysql/store"
)
func NewUserHandler(db *gorm.DB) *UserHandler {
	return &UserHandler{
		store: store.NewUserStore(db),
	}
}
type UserHandler struct {
	store store.UserStore
}
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	...
}
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	...
}
func setupRouter(handler *UserHandler) *httprouter.Router {
	router := httprouter.New()
	router.POST("/users", handler.CreateUser)
	router.GET("/users/:id", handler.GetUser)
	return router
}
func main() {
	mysqlDB, _ := store.NewMySQLDB("localhost", "3306", "user", "password", "test")
	handler := NewUserHandler(mysqlDB)
	router := setupRouter(handler)
	_ = http.ListenAndServe(":8000", router)
}

這個(gè)服務(wù)監(jiān)聽 8000 端口,分別提供了兩個(gè) HTTP 接口:

POST /users 用來創(chuàng)建用戶。

GET /users/:id 用來獲取指定 ID 對(duì)應(yīng)的用戶信息。

UserHandler 是一個(gè)結(jié)構(gòu)體,它依賴外部存儲(chǔ)接口 store.UserStore,這個(gè)接口定義如下:

store/store.go

package store
import "gorm.io/gorm"
type UserStore interface {
	Create(user *User) error
	Get(id int) (*User, error)
}
func NewUserStore(db *gorm.DB) UserStore {
	return &userStore{db}
}
type userStore struct {
	db *gorm.DB
}
func (s *userStore) Create(user *User) error {
	return s.db.Create(user).Error
}
func (s *userStore) Get(id int) (*User, error) {
	var user User
	err := s.db.First(&user, id).Error
	return &user, err
}

store.UserStore 定義了兩個(gè)方法,分別用來創(chuàng)建、獲取用戶信息。

User 模型定義如下:

store/model.go

type User struct {
	ID   int    `gorm:"id"`
	Name string `gorm:"name"`
}

store.userStore 結(jié)構(gòu)體則實(shí)現(xiàn)了 store.UserStore 接口。

store.userStore 結(jié)構(gòu)體又依賴了 GORM 庫(kù)的 *gorm.DB 類型,表示一個(gè)數(shù)據(jù)庫(kù)連接對(duì)象。

我們可以使用 NewMySQLDB 建立數(shù)據(jù)庫(kù)連接得到 *gorm.DB 對(duì)象:

store/mysql.go

func NewMySQLDB(host, port, user, pass, dbname string) (*gorm.DB, error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		user, pass, host, port, dbname)
	return gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

至此,這個(gè) HTTP 服務(wù)程序整體邏輯就基本介紹完了。

其目錄結(jié)構(gòu)如下:

$ tree
.
├── go.mod
├── go.sum
├── main.go
└── store
    ├── model.go
    ├── mysql.go
    └── store.go

為了保證業(yè)務(wù)的正確性,我們應(yīng)該對(duì) (*UserHandler).CreateUser(*UserHandler).GetUser 這兩個(gè) Handler 進(jìn)行單元測(cè)試。

這兩個(gè) Handler 定義如下:

func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	w.Header().Set("Content-Type", "application/json")
	body, err := io.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		_, _ = fmt.Fprintf(w, `{"msg":"%s"}`, err.Error())
		return
	}
	defer func() { _ = r.Body.Close() }()
	u := store.User{}
	if err := json.Unmarshal(body, &u); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = fmt.Fprintf(w, `{"msg":"%s"}`, err.Error())
		return
	}
	if err := h.store.Create(&u); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = fmt.Fprintf(w, `{"msg":"%s"}`, err.Error())
		return
	}
	w.WriteHeader(http.StatusCreated)
}
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	id := ps[0].Value
	uid, _ := strconv.Atoi(id)
	w.Header().Set("Content-Type", "application/json")
	u, err := h.store.Get(uid)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = fmt.Fprintf(w, `{"msg":"%s"}`, err.Error())
		return
	}
	_, _ = fmt.Fprintf(w, `{"id":%d,"name":"%s"}`, u.ID, u.Name)
}

不過,由于文章篇幅所限,我這里僅以測(cè)試 (*UserHandler).GetUser 方法為例,演示如何在測(cè)試過程中解決 MySQL 依賴問題,對(duì) (*UserHandler).CreateUser 方法的測(cè)試就當(dāng)做作業(yè)留給你自己來完成了(當(dāng)然,你也可以到我的 GitHub 上查看我的實(shí)現(xiàn))。

Fake 測(cè)試

我們要為 (*UserHandler).GetUser 方法編寫單元測(cè)試,首先就要分析下這個(gè)方法的外部依賴。

func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	id := ps[0].Value
	uid, _ := strconv.Atoi(id)
	w.Header().Set("Content-Type", "application/json")
	u, err := h.store.Get(uid)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = fmt.Fprintf(w, `{"msg":"%s"}`, err.Error())
		return
	}
	_, _ = fmt.Fprintf(w, `{"id":%d,"name":"%s"}`, u.ID, u.Name)
}

UserHandler 結(jié)構(gòu)本身依賴了 store.UserStore,這是一個(gè)接口,定義了創(chuàng)建和獲取用戶信息的兩個(gè)方法。

我們使用實(shí)現(xiàn)了 store.UserStore 接口的 store.userStore 結(jié)構(gòu)體來初始化 UserHandler

func NewUserHandler(db *gorm.DB) *UserHandler {
	return &UserHandler{
		store: store.NewUserStore(db),
	}
}
func NewUserStore(db *gorm.DB) UserStore {
	return &userStore{db}
}

store.userStore 結(jié)構(gòu)體會(huì)使用 GORM 來完成對(duì) MySQL 數(shù)據(jù)庫(kù)的操作。所以,我們分析出 GetUser 方法的第一個(gè)外部依賴實(shí)際上就是 MySQL 存儲(chǔ)。

GetUser 方法還接收三個(gè)參數(shù),它們都屬于 HTTP 網(wǎng)絡(luò)相關(guān)的外部依賴,你可以在我的另一篇文章《在 Go 語言單元測(cè)試中如何解決 HTTP 網(wǎng)絡(luò)依賴問題》中找到解決方案,就不在本文中進(jìn)行講解了。

所以,我們現(xiàn)在重點(diǎn)要關(guān)注的就只有一個(gè)問題,如何解決 MySQL 存儲(chǔ)依賴。

我們來整理下 MySQL 外部依賴的程序調(diào)用鏈:

可以發(fā)現(xiàn),store.UserStore 接口是 UserHandlerstore.userStore 結(jié)構(gòu)體建立連接的橋梁,我們可以將它作為突破口,實(shí)現(xiàn)一個(gè) Fake object,來替換 store.userStore 結(jié)構(gòu)體。

所謂 Fake object,其實(shí)就是我們同樣要定義一個(gè)結(jié)構(gòu)體,并實(shí)現(xiàn) CreateGet 兩個(gè)方法,以此來實(shí)現(xiàn) store.UserStore 接口。

type fakeUserStore struct{}
func (f *fakeUserStore) Create(user *store.User) error {
	return nil
}
func (f *fakeUserStore) Get(id int) (*store.User, error) {
	return &store.User{ID: id, Name: "test"}, nil
}

store.userStore 結(jié)構(gòu)體不同,fakeUserStore 并不依賴 *gorm.DB,也就不涉及 MySQL 數(shù)據(jù)庫(kù)操作了,這樣就解決了 MySQL 外部存儲(chǔ)依賴。

(*fakeUserStore).Create 方法沒做任何操作,直接返回 nil,(*fakeUserStore).Get 方法則根據(jù)傳進(jìn)來的 id 返回固定的 User 信息。這也是 Fake object 的特點(diǎn),為真實(shí)對(duì)象實(shí)現(xiàn)一個(gè)簡(jiǎn)化版本。

這樣,我們?cè)诰帉憸y(cè)試代碼時(shí),只需要取代 store.userStore 結(jié)構(gòu)體,使用 fakeUserStore 來實(shí)例化 UserHandler,就可以避免與 MySQL 數(shù)據(jù)庫(kù)打交道了。

handler := &UserHandler{store: &fakeUserStore{}}

(*UserHandler).GetUser 方法編寫的單元測(cè)試完整代碼如下:

func TestUserHandler_GetUser_by_fake(t *testing.T) {
	handler := &UserHandler{store: &fakeUserStore{}}
	router := setupRouter(handler)
	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/users/1", nil)
	router.ServeHTTP(w, req)
	assert.Equal(t, 200, w.Code)
	assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
	assert.Equal(t, `{"id":1,"name":"test"}`, w.Body.String())
}

現(xiàn)在被測(cè)試的 (*UserHandler).GetUser 方法中通過 h.store.Get(uid) 從數(shù)據(jù)庫(kù)中獲取用戶信息時(shí),就不用再去查詢 MySQL 了,而是由 (*fakeUserStore).Get 方法直接返回 Fake 數(shù)據(jù)。

使用 go test 來執(zhí)行測(cè)試函數(shù):

$ go test -v -run="TestUserHandler_GetUser_by_fake"
=== RUN   TestUserHandler_GetUser_by_fake
--- PASS: TestUserHandler_GetUser_by_fake (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/mysql    0.465s

測(cè)試通過。

可以發(fā)現(xiàn),使用 Fake 測(cè)試來解決 MySQL 外部依賴還是比較簡(jiǎn)單的,我們僅需要參考 store.userStore 實(shí)現(xiàn)一個(gè)簡(jiǎn)化版本的 fakeUserStore,然后在測(cè)試過程中,使用簡(jiǎn)化版本的 fakeUserStore 對(duì)象替換掉 store.userStore 即可。

Mock 測(cè)試

前文中,我們使用 fakeUserStore 來替換 store.userStore,以此來接口 MySQL 依賴問題。

不過,這種使用 Fake object 來解決外部依賴的方式存在兩個(gè)較為常見的弊端:

一個(gè)是使用 Fake object 需要手動(dòng)編寫大量代碼,這里的 store.UserStore 接口僅定義了兩個(gè)方法還好,但一個(gè)線上的復(fù)雜業(yè)務(wù),可能有幾十個(gè)接口,每個(gè)接口又有幾十個(gè)方法,此時(shí)如果還是手動(dòng)來編寫這些代碼,需要消耗大量時(shí)間。

另一個(gè)是 Fake object 返回結(jié)果比較固定,如果想測(cè)試其他情況,比如查詢的 User 不存在,需要報(bào)錯(cuò)的情況,就得在 (*fakeUserStore).Get 方法中編寫更多的邏輯,這增加了實(shí)現(xiàn) Fake object 的復(fù)雜度。

那么有沒有一種替代方案,來彌補(bǔ) Fake object 的這兩個(gè)弊端呢?

答案是使用 Mock 測(cè)試。

Mock 和 Fake 類似,本質(zhì)上都是使用一個(gè)對(duì)象,去替代另一個(gè)對(duì)象。Fake 測(cè)試是實(shí)現(xiàn)了一個(gè)真實(shí)對(duì)象(store.userStore)的簡(jiǎn)化版本(fakeUserStore),Mock 測(cè)試則是使用模擬對(duì)象來斷言真實(shí)對(duì)象被調(diào)用時(shí)的輸入符合預(yù)期,然后通過模擬對(duì)象返回指定輸出。

在 Go 中,我們可以使用 gomock 來實(shí)現(xiàn) Mock 測(cè)試。

gomock 項(xiàng)目起源于 Google 的 golang/mock 倉(cāng)庫(kù)。不幸的是,谷歌不再維護(hù)這個(gè)項(xiàng)目了。幸運(yùn)的是,這個(gè)項(xiàng)目由 Uber fork 了一份,并繼續(xù)維護(hù)。

gomock 包含兩個(gè)部分:gomock 包和 mockgen 命令行工具。gomock 包用來完成對(duì)被 Mock 對(duì)象的生命周期管理,mockgen 工具則用來自動(dòng)生成 Mock 代碼。

可以通過如下方式來安裝 gomock 包和 mockgen 工具:

$ go get go.uber.org/mock/gomock@latest
$ go install go.uber.org/mock/mockgen@latest

注意:在項(xiàng)目根目錄下通過 go get 命令獲取 gomock 包后,不要急著執(zhí)行 go mod tidy,因?yàn)楝F(xiàn)在 gomock 包屬于 indirect 依賴,還沒有被使用。當(dāng)通過 mockgen 工具生成了 Mock 代碼以后,再來執(zhí)行 go mod tidy,go.mod 文件中才不會(huì)丟失 gomock 依賴。

要想使用 gomock 來模擬 store.UserStore 接口的實(shí)現(xiàn),我們先要使用 mockgen 工具來生成 Mock 代碼:

 $ mockgen -source store/store.go -destination store/mocks/gomock.go -package mocks

-source 參數(shù)指明需要 Mock 的接口文件路徑,即 store.UserStore 接口所在文件。

-destination 參數(shù)指明生成的 Mock 文件路徑。

-package 參數(shù)指明生成的 Mock 文件包名。

在項(xiàng)目根目錄下執(zhí)行 mockgen 命令,即可生成 Mock 文件:

// Code generated by MockGen. DO NOT EDIT.
// Source: store/store.go
// Package mocks is a generated GoMock package.
package mocks
import (
	reflect "reflect"
	store "github.com/jianghushinian/blog-go-example/test/mysql/store"
	gomock "go.uber.org/mock/gomock"
)
// MockUserStore is a mock of UserStore interface.
type MockUserStore struct {
	ctrl     *gomock.Controller
	recorder *MockUserStoreMockRecorder
}
// MockUserStoreMockRecorder is the mock recorder for MockUserStore.
type MockUserStoreMockRecorder struct {
	mock *MockUserStore
}
// NewMockUserStore creates a new mock instance.
func NewMockUserStore(ctrl *gomock.Controller) *MockUserStore {
	mock := &MockUserStore{ctrl: ctrl}
	mock.recorder = &MockUserStoreMockRecorder{mock}
	return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockUserStore) EXPECT() *MockUserStoreMockRecorder {
	return m.recorder
}
// Create mocks base method.
func (m *MockUserStore) Create(user *store.User) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Create", user)
	ret0, _ := ret[0].(error)
	return ret0
}
// Create indicates an expected call of Create.
func (mr *MockUserStoreMockRecorder) Create(user interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockUserStore)(nil).Create), user)
}
// Get mocks base method.
func (m *MockUserStore) Get(id int) (*store.User, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Get", id)
	ret0, _ := ret[0].(*store.User)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockUserStoreMockRecorder) Get(id interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockUserStore)(nil).Get), id)
}

提示:生成的 mocks 包代碼你無需全部看懂,僅知道它大概生成了什么內(nèi)容,如何使用即可。

可以發(fā)現(xiàn),mockgen 為我們生成了 mocks.MockUserStore 結(jié)構(gòu)體,并且實(shí)現(xiàn)了 Create、Get 兩個(gè)方法,即實(shí)現(xiàn)了 store.UserStore 接口。

現(xiàn)在,我們就可以使用生成的 Mock 對(duì)象來編寫單元測(cè)試代碼了:

func TestUserHandler_GetUser_by_mock(t *testing.T) {
	ctrl := gomock.NewController(t)
	// 斷言 mockUserStore.Get 方法會(huì)被調(diào)用
	defer ctrl.Finish()
	mockUserStore := mocks.NewMockUserStore(ctrl)
	mockUserStore.EXPECT().Get(2).Return(&store.User{
		ID:   2,
		Name: "user2",
	}, nil)
	handler := &UserHandler{store: mockUserStore}
	router := setupRouter(handler)
	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/users/2", nil)
	router.ServeHTTP(w, req)
	assert.Equal(t, 200, w.Code)
	assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
	assert.Equal(t, `{"id":2,"name":"user2"}`, w.Body.String())
}

gomock.NewController(t) 用來創(chuàng)建一個(gè) Mock 控制器,該對(duì)象可以控制整個(gè) Mock 生命周期。

ctrl.Finish() 用來斷言 Mock 對(duì)象使用 EXPECT() 方法設(shè)置的期待執(zhí)行方法會(huì)被調(diào)用,一般使用 defer 語句來調(diào)用,防止最后忘記。不過,如果你使用的 Go 版本大于 1.14,則可以不必顯式調(diào)用 ctrl.Finish()

mocks.NewMockUserStore(ctrl) 使用 Mock 控制器創(chuàng)建了 *mocks.MockUserStore 對(duì)象,有了它,我們就可以模擬調(diào)用 store.UserStore 接口對(duì)應(yīng)方法的邏輯了:

mockUserStore.EXPECT().Get(2).Return(&store.User{
    ID:   2,
    Name: "user2",
}, nil)

mockUserStore 對(duì)象就相當(dāng)于我們前文中實(shí)現(xiàn)的 fakeUserStore。

Mock 對(duì)象的 EXPECT() 方法用來設(shè)置預(yù)期被調(diào)用的方法,以及被調(diào)用方法所期望的輸入,它支持鏈?zhǔn)秸{(diào)用,.Get(2) 表示期望在測(cè)試中調(diào)用 Mock 對(duì)象 mockUserStoreGet 方法時(shí),輸入?yún)?shù)是 2,Return 方法用來設(shè)置輸出,即返回值內(nèi)容。

這就相當(dāng)于,我們實(shí)現(xiàn)了 fakeUserStoreGet 方法。

我們可以使用 mockUserStore 來實(shí)例化 UserHandler 對(duì)象。

req 請(qǐng)求中,我們?cè)O(shè)置請(qǐng)求的用戶 ID 值為 2,即 mockUserStore 對(duì)象斷言中的參數(shù),二者參數(shù)匹配,Mock 對(duì)象才能生效。

單元測(cè)試最后,斷言了返回結(jié)果為 {"id":2,"name":"user2"},即 mockUserStore 對(duì)象期望的返回結(jié)果。

現(xiàn)在我們就可以測(cè)試 (*UserHandler).GetUser 方法了。

使用 go test 來執(zhí)行測(cè)試函數(shù):

$ go test -v -run="TestUserHandler_GetUser_by_mock"
=== RUN   TestUserHandler_GetUser_by_mock
--- PASS: TestUserHandler_GetUser_by_mock (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/mysql    0.220s

測(cè)試通過。

使用 Mock 測(cè)試來解決 MySQL 外部依賴問題,我們無需手動(dòng)編寫 Mock 對(duì)象的代碼,可以使用 mockgen 工具為我們自動(dòng)生成,簡(jiǎn)化了 Fake 測(cè)試中編寫 fakeUserStore 的過程。

并且,如果想要測(cè)試其他情況,僅需要再次使用 Mock 對(duì)象的 EXPECT() 方法來設(shè)置 Get 方法的期望輸入和輸出即可。

比如設(shè)置預(yù)期查詢 ID 為 3 的用戶信息時(shí),返回 user not found 錯(cuò)誤:

mockUserStore.EXPECT().Get(3).Return(nil, errors.New("user not found"))

Mock 測(cè)試更方便我們測(cè)試不同業(yè)務(wù)場(chǎng)景。

gomock 更多用法

gomock 還有一些使用技巧值得分享。

mockgen

前文中,我們使用 mockgen 通過指定源碼文件形式生成了 Mock 代碼:

$ mockgen -source store/store.go -destination store/mocks/gomock.go -package mocks

mockgen 工具還支持通過反射模式來生成 Mock 代碼:

$ mockgen -package mocks -destination store/mocks/gomock.go github.com/jianghushinian/blog-go-example/test/mysql/store UserStore

命令最后的兩個(gè)參數(shù)分別代表需要生成 Mock 代碼的包的導(dǎo)入路徑和逗號(hào)分隔的接口列表。

執(zhí)行以上命令同樣能夠成功生成 Mock 代碼。

此外,我們還可以將 mockgen 命令寫到 Go 文件中,然后使用 Go generate 工具來生成 Mock 代碼:

store/generate.go

package store
//go:generate mockgen -package mocks -destination ./mocks/gomock.go . UserStore

這次我們的 mockgen 命令又有所不同,包的導(dǎo)入路徑僅為一個(gè) .,表示當(dāng)前目錄,這也是被支持的。

這時(shí)候,我們只需要在項(xiàng)目根目錄下執(zhí)行 go generate ./... 命令即可生成 Mock 代碼。./... 表示查找項(xiàng)目下全部文件,go generate 會(huì)自動(dòng)找到帶有 //go:generate 注釋的命令并執(zhí)行。

如果我們有多個(gè)源碼文件要生成 Mock 代碼,go generate 方式就非常合適,僅需要在 Go 文件中分多行依次寫出 mockgen 命令即可使用一條命令一次全部生成。

gomock

前文中,我們使用了 Mock 對(duì)象 mockUserStoreEXPECT() 方法來設(shè)置 Get 方法所期待的輸入和輸出。

mockUserStore.EXPECT().Get(2).Return(&store.User{
    ID:   2,
    Name: "user2",
}, nil)

有時(shí)候,EXPECT() 所作用的方法可能存在多個(gè)參數(shù),且有些參數(shù)不容易模擬,比如最常見的 context.Context 參數(shù),針對(duì)這些情況,gomock 提供了更多的參數(shù)匹配方法:

gomock.Any() 表示匹配任意參數(shù),適合參數(shù)模擬困難的情況。

gomock.Eq(x) 表示匹配與 x 相等的參數(shù)。

gomock.Not(x) 表示匹配與 x 不想等的參數(shù)。

gomock.Nil() 表示匹配 nil 參數(shù)。

gomock.Len(i) 表示匹配長(zhǎng)度為 i 的參數(shù)。

gomock.All(ms) 表示傳入的所有參數(shù)都想等才能匹配。

以上這些參數(shù)匹配方法都可以像如下這樣使用:

mockUserStore.EXPECT().Get(gomock.Eq(2)).Return(&store.User{
    ID:   2,
    Name: "user2",
}, nil)

此外,我們可以約束 EXPECT() 所作用方法的執(zhí)行次數(shù):

.Return(xxx).Times(2) // 預(yù)期方法會(huì)被調(diào)用 2 次
.Return(xxx).MaxTimes(2) // 預(yù)期方法最多執(zhí)行 2 次
.Return(xxx).MinTimes(2) // 預(yù)期方法至少執(zhí)行 2 次
.Return(xxx).AnyTimes() // 預(yù)期方法執(zhí)行任意次都能匹配

還可以約束 EXPECT() 所作用方法的執(zhí)行順序:

.Return(xxx).After(preReq) // 當(dāng)前預(yù)期方法在 preReq 預(yù)期方法執(zhí)行完成之后執(zhí)行

以上便是我認(rèn)為 gomock 中比較常用的功能講解,更多功能可參考官方文檔。

總結(jié)

本文向大家介紹了在 Go 中編寫單元測(cè)試時(shí),如何解決 MySQL 外部依賴的問題。

我們分別使用了 Fake object 和 Mock 兩種方式,來替換原有的外部依賴。

Web 服務(wù)的代碼不是隨意設(shè)計(jì)的,有意將 UserHandler 依賴的類型設(shè)為 store.UserStore 接口,而不是 store.userStore 結(jié)構(gòu)體,是為了解耦。通過使用接口,解決了 UserHandlerstore.userStore 結(jié)構(gòu)體強(qiáng)綁定的問題,這就給我們使用 fakeUserStoremockUserStore 來替代 store.userStore 創(chuàng)造了機(jī)會(huì)。

可以發(fā)現(xiàn),本文介紹的兩種方法其實(shí)不僅能夠用于解決 MySQL 外部依賴問題。任何使用接口編寫的代碼,在測(cè)試時(shí)都可以使用這兩種方式來替換依賴。這就是 Go 面向接口編程的好處。

以上就是詳解Go語言單元測(cè)試中如何解決MySQL存儲(chǔ)依賴問題的詳細(xì)內(nèi)容,更多關(guān)于Go單元測(cè)試解決MySQL存儲(chǔ)依賴的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang Http 驗(yàn)證碼示例實(shí)現(xiàn)

    Golang Http 驗(yàn)證碼示例實(shí)現(xiàn)

    這篇文章主要介紹了Golang Http 驗(yàn)證碼示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 一文詳解Go語言單元測(cè)試的原理與使用

    一文詳解Go語言單元測(cè)試的原理與使用

    Go語言中自帶有一個(gè)輕量級(jí)的測(cè)試框架testing和自帶的go?test命令來實(shí)現(xiàn)單元測(cè)試和性能測(cè)試。本文將通過示例詳細(xì)聊聊Go語言單元測(cè)試的原理與使用,需要的可以參考一下
    2022-09-09
  • Ubuntu安裝Go語言運(yùn)行環(huán)境

    Ubuntu安裝Go語言運(yùn)行環(huán)境

    由于最近偏愛Ubuntu,在加上作為一門開源語言,在Linux上從源代碼開始搭建環(huán)境更讓人覺得有趣味性。讓我們直接先從Go語言的環(huán)境搭建開始
    2015-04-04
  • Go語言如何通過通信共享內(nèi)存

    Go語言如何通過通信共享內(nèi)存

    這篇文章主要為大家介紹了Go語言如何通過通信共享內(nèi)存實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑

    gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑

    這篇文章主要為大家介紹了gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • go語言實(shí)現(xiàn)sqrt的方法

    go語言實(shí)現(xiàn)sqrt的方法

    這篇文章主要介紹了go語言實(shí)現(xiàn)sqrt的方法,實(shí)例分析了Go語言實(shí)現(xiàn)計(jì)算平方根的技巧,需要的朋友可以參考下
    2015-03-03
  • mayfly-go部署和使用詳解

    mayfly-go部署和使用詳解

    這篇文章主要介紹了mayfly-go部署和使用詳解,此處部署基于CentOS7.4部署,結(jié)合實(shí)例代碼圖文給大家講解的非常詳細(xì),需要的朋友可以參考下
    2022-09-09
  • GOLANG使用Context實(shí)現(xiàn)傳值、超時(shí)和取消的方法

    GOLANG使用Context實(shí)現(xiàn)傳值、超時(shí)和取消的方法

    這篇文章主要介紹了GOLANG使用Context實(shí)現(xiàn)傳值、超時(shí)和取消的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-01-01
  • 一文搞懂Go?Exec?僵尸與孤兒進(jìn)程

    一文搞懂Go?Exec?僵尸與孤兒進(jìn)程

    本文主要介紹了Go?Exec?僵尸與孤兒進(jìn)程,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • Go語言自定義linter靜態(tài)檢查工具

    Go語言自定義linter靜態(tài)檢查工具

    這篇文章主要介紹了Go語言自定義linter靜態(tài)檢查工具,Go語言是一門編譯型語言,編譯器將高級(jí)語言翻譯成機(jī)器語言,會(huì)先對(duì)源代碼做詞法分析,詞法分析是將字符序列轉(zhuǎn)換為Token序列的過程,文章詳細(xì)介紹需要的小伙伴可以參考一下
    2022-05-05

最新評(píng)論