項目demo地址:go-test
目前只描述了簡單的方法,文檔持續更新中...
本文主要針對golang語言的單元測試工具,博客內容也會涉及一些單元相關的內容
什麼是單元測試:單元測試是軟件測試體系中最基礎、最核心的測試類型,它聚焦於對軟件系統中最小的 “可測試單元” 進行獨立驗證,確保該單元的功能符合預期設計。
簡單描述下前因後果:工作需要對項目代碼系統化執行單元測試,要求覆蓋率達到95%以上,因為不同人的開發風格和代碼習慣,外加項目框架和架構的一些要求。單元測試,這個東西一般情況都會讓人很痛苦,至於因為啥,我相信看到我這篇博客的各位,都是有不同程度的感同身受的,我這裏介紹三種單測工具,基礎的單元測試書寫和使用就不過多贅述了。
一、單元測試的核心方式
注意:這塊作為擴展,如需直接瞭解工具可忽略這部分
單元測試的實現方式可從核心分類維度展開,結合具體落地實踐和技術選型,不同方式適用於不同場景和項目規模。
1、按測試編寫時機劃分(核心流程維度)
從開發流程角度區分的兩種核心方式,決定了單元測試與業務代碼的協作關係。
1.後寫單元測試(傳統方式,最常用)
- 核心定義:先編寫業務功能代碼,待功能實現完成後,再針對性補寫對應的單元測試用例,驗證已實現的代碼邏輯是否符合預期。
- 適用場景:大部分傳統開發場景、快速迭代的小型需求、開發者對 TDD 模式不熟悉的項目。
- 優勢:符合開發者 “先實現功能再驗證” 的直覺,上手成本低,無需提前設計詳細的測試用例。
- 劣勢:可能遺漏部分邊界場景的測試,且容易因業務代碼耦合度高,導致測試難以編寫(後期補測時,修改代碼解耦的成本更高)。
2.測試驅動開發(TDD,Test-Driven Development,進階方式)
- 核心定義:遵循 “先寫測試,再寫業務代碼,最後重構” 的循環流程,測試用例先定義好被測單元的預期行為(輸入、輸出、異常場景),再編寫滿足測試用例的業務代碼,最終優化代碼結構。
- 核心流程(紅 - 綠 - 重構循環):
- 紅(Red):編寫一個失敗的測試用例(此時業務代碼未實現,測試必然失敗);
- 綠(Green):編寫最少的業務代碼,僅滿足讓該測試用例通過(不追求代碼優雅,只保證功能達標);
- 重構(Refactor):在測試用例保駕護航的前提下,優化業務代碼的結構、可讀性、性能等,確保重構後測試用例仍能通過。
- 適用場景:對代碼質量要求高的核心模塊、複雜業務邏輯、需要長期維護的大型項目。
- 優勢:
- 強制開發者提前梳理需求和接口設計,減少後期需求偏差;
- 測試用例覆蓋率更高,天然覆蓋正常、邊界、異常場景;
- 重構無風險,測試用例作為 “安全網”,確保重構不破壞原有功能;
- 代碼耦合度更低,因為先寫測試會倒逼開發者設計可測試的代碼(如依賴接口而非具體實現)。
2、按依賴處理方式劃分(技術實現維度)
這是單元測試落地的核心技術維度,決定了如何隔離外部依賴,保證測試的獨立性。
1. 基於 Mock/Stub 的單元測試(主流方式)
-
核心定義:當被測單元依賴外部資源(數據庫、RPC 服務、HTTP 接口、文件系統等)時,通過 ** 模擬(Mock)或樁(Stub)** 實現替代真實依賴,預設返回值或行為,從而脱離外部環境限制,專注測試業務邏輯。
-
Mock vs Stub 區別(通俗理解):
類型 核心特徵 適用場景 Stub 僅預設固定返回值,無行為驗證 只需依賴返回值完成業務邏輯測試 Mock 不僅預設返回值,還可驗證依賴方法是否被調用、調用次數、參數是否正確 需要驗證業務邏輯對依賴的調用行為 -
實現方式:
- 手動編寫 Mock/Stub(簡單場景):如之前 Go 示例中,手動實現
UserDB接口的MockUserDB,預設返回值; - 工具自動生成 Mock(複雜場景):Go 生態的
gomock+mockgen、Java 生態的Mockito、Python 生態的unittest.mock,可根據接口自動生成 Mock 代碼,支持更靈活的行為驗證。
- 手動編寫 Mock/Stub(簡單場景):如之前 Go 示例中,手動實現
-
優勢:測試獨立、快速、可復現,不受外部資源狀態影響,能覆蓋各種異常場景(如依賴服務報錯、超時)。
-
Go 語言工具示例(gomock):
(1)先安裝工具:
go get github.com/golang/mock/gomockgo install github.com/golang/mock/mockgen@latest(2)自動生成 Mock 代碼,無需手動編寫,支持驗證調用行為。
2. 真實依賴單元測試(小眾場景)
- 核心定義:不使用模擬實現,直接使用真實的外部依賴(如真實數據庫、真實 HTTP 服務)進行單元測試,驗證被測單元與真實依賴的協作是否正常。
- 適用場景:依賴邏輯簡單、真實依賴易於搭建和控制(如本地輕量數據庫 SQLite)、對依賴協作正確性要求極高的場景。
- 優勢:測試結果更貼近生產環境,能發現與真實依賴協作的潛在問題(如 SQL 語法錯誤、接口參數不匹配)。
- 劣勢:
- 測試執行速度慢,依賴外部資源啓動和初始化;
- 測試結果不穩定,受外部資源狀態影響(如數據庫數據被修改導致測試失敗);
- 測試環境搭建複雜,需要統一管理依賴配置(如數據庫連接信息、服務地址)。
- 示例:測試數據庫查詢函數時,直接連接本地測試 MySQL 數據庫,預先插入測試數據,執行查詢後驗證結果,最後清理測試數據。
3、個人理解
上面的描述是大模型系統化生成的內容,下面是博主自行整理的,至於為什麼會有這樣一段贅述,是和下面的工具有些關聯
單元測試兩種開發方式:
1.先開發業務代碼,後寫單元測試代碼(常用)
-
interface單元測試
-
核心優勢:
- 完全解耦外部依賴,實現 “純淨” 測試
- 靈活覆蓋全量業務場景,無測試死角
- 測試執行效率極高,支持高頻執行
- 為代碼重構提供 “安全網”,降低重構風險
- 倒逼良好的代碼設計,提升代碼質量
- 可驗證依賴方法的調用行為(進階優勢)
-
缺點:
- 增加前期開發成本,引入額外代碼量
- 存在 “過度抽象” 的風險
- 無法驗證真實依賴的協作正確性
- Mock 與真實實現可能存在 “行為不一致”
- 對簡單場景 “殺雞用牛刀”,性價比不高
-
總結:
如果代碼開發的時候考慮到需要進行單元測試功能開發,可以直接在業務功能開發時進行單元測試的預先埋點處理,做好接口的開發,不過一般情況下大家的開發習慣都不會考慮單元測試這種情況,這時候在想要回去處理單元測試,interface這種方式就會極為麻煩和笨重,單測時間成本成指數級增長。
-
-
使用單元測試工具
-
內置核心工具:testing包(基礎基石)
-
工具包(具體使用方法和功能下面介紹)
- 接口測試工具:httptest
- 數據庫測試工具:go-sqlmock
- 打樁測試工具:gomonkey
-
優點:
在業務邏輯代碼開發完成後幾乎可以不調整原始邏輯代碼進行單元測試代碼開發
-
2.先開發單元測試代碼,後寫邏輯代碼(很少見,不介紹)
二、單元測試工具
主要介紹三個工具:httptest、go-sqlmock、gomonkey
1、httptest
介紹:Go 內置標準庫net/http/httptest,核心用途用於測試net/http構建的HTTP服務(如API接口、Web服務等),它可以模擬HTTP請求發送和HTTP響應的接收,無需啓動真實的HTTP服務器即可完成接口測試,極大提升了測試的便捷性和執行效率
優點:
- 無需啓動真實服務器:無需調用
http.ListenAndServe啓動端口監聽,直接測試 HTTP 處理器(Handler/HandlerFunc),測試執行更高效。 - 脱離網絡依賴:模擬 HTTP 請求與響應的完整生命週期,不受網絡波動、端口占用等外部因素影響,測試結果穩定可復現。
- 精準捕獲響應細節:可完整獲取響應狀態碼、響應頭、響應體等所有信息,便於精準斷言驗證。
- 支持兩種核心測試場景:
- 測試 HTTP 處理器(直接調用
ServeHTTP,最常用) - 啓動臨時測試服務器(模擬真實服務端,用於客户端測試或集成測試)
- 測試 HTTP 處理器(直接調用
1.安裝
內置工具可以直接使用
2.使用示例
相關代碼在gitee代碼倉庫的示例代碼中,倉庫地址請看博客開頭
blog.go
package blog
import (
"errors"
"fmt"
"io"
"net/http"
)
func SearchHttp(targetURL string) (interface{}, error) {
resp, err := http.Get(targetURL)
if err != nil {
errMsg := fmt.Sprintf("發送 GET 請求失敗:%v", err)
fmt.Println(errMsg)
return nil, errors.New(errMsg)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
errMsg := fmt.Sprintf("請求失敗,狀態碼:%d,狀態信息:%s", resp.StatusCode, resp.Status)
fmt.Println(errMsg)
return nil, errors.New(errMsg)
}
bodyBytes, _ := io.ReadAll(resp.Body)
return string(bodyBytes), nil
}
blog_test.go
package blog
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestSearchHttp(t *testing.T) {
// --------------- 1. 定義所有測試場景的表驅動用例(循環遍歷執行) ---------------
testCases := []struct {
name string // 用例名稱
prepareFunc func() string // 前置準備:創建模擬服務器/構造URL,返回待請求的URL
expectedErr bool // 是否預期返回錯誤
errContains string // 預期錯誤信息包含的關鍵字(非空則驗證)
expectedNonEmpty bool // 正常場景下,是否預期返回非空字符串
}{
// 場景1:正常請求(200 OK,響應體正常)
{
name: "正常請求-狀態碼200",
prepareFunc: func() string {
// 啓動模擬HTTP服務器,返回200和測試響應體
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mockBody := "<!DOCTYPE html><html><title>百度一下</title></html>"
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(mockBody))
}))
// 關鍵:將mockServer放入測試上下文,確保後續能關閉(避免資源泄露)
t.Cleanup(func() { mockServer.Close() })
return mockServer.URL
},
expectedErr: false,
errContains: "",
expectedNonEmpty: true,
},
// 場景2:請求失敗(無效URL,模擬網絡異常)
{
name: "異常場景-無效URL請求失敗",
prepareFunc: func() string {
// 返回一個無效的URL,觸發http.Get請求失敗
return "http://invalid-xxx-url-12345/"
},
expectedErr: true,
errContains: "發送 GET 請求失敗",
expectedNonEmpty: false,
},
// 場景3:狀態碼非200(模擬404 Not Found)
{
name: "異常場景-狀態碼404",
prepareFunc: func() string {
// 啓動模擬HTTP服務器,返回404狀態碼
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte("頁面不存在"))
}))
t.Cleanup(func() { mockServer.Close() })
return mockServer.URL
},
expectedErr: true,
errContains: "請求失敗,狀態碼:404",
expectedNonEmpty: false,
},
// 場景4:狀態碼非200(模擬500服務器內部錯誤)
{
name: "異常場景-狀態碼500",
prepareFunc: func() string {
// 啓動模擬HTTP服務器,返回500狀態碼
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("服務器內部錯誤"))
}))
t.Cleanup(func() { mockServer.Close() })
return mockServer.URL
},
expectedErr: true,
errContains: "請求失敗,狀態碼:500",
expectedNonEmpty: false,
},
}
// --------------- 2. 循環遍歷所有測試用例,統一執行驗證 ---------------
for _, tc := range testCases {
// 循環內使用t.Run創建子用例(便於精準定位失敗場景,不影響其他用例)
t.Run(tc.name, func(t *testing.T) {
// 步驟1:執行前置準備,獲取待請求的URL
targetURL := tc.prepareFunc()
// 步驟2:調用被測函數
result, err := SearchHttp(targetURL)
// 步驟3:統一斷言驗證
// 3.1 驗證錯誤是否符合預期
if (err != nil) != tc.expectedErr {
t.Fatalf("錯誤預期不符:預期是否錯誤[%t],實際是否錯誤[%t],錯誤信息[%v]",
tc.expectedErr, err != nil, err)
}
// 3.2 若預期錯誤,驗證錯誤信息是否包含指定關鍵字
if tc.expectedErr && tc.errContains != "" {
if !strings.Contains(err.Error(), tc.errContains) {
t.Errorf("錯誤信息不符:預期包含[%s],實際錯誤[%v]", tc.errContains, err)
}
}
// 3.3 驗證返回值是否符合預期
if tc.expectedErr {
// 異常場景:預期返回nil
if result != nil {
t.Errorf("異常場景預期返回nil,實際返回[%v],類型[%T]", result, result)
}
} else {
// 正常場景:驗證返回值是string類型,且非空(若預期非空)
resultStr, ok := result.(string)
if !ok {
t.Fatalf("正常場景預期返回string類型,實際返回[%T]", result)
}
if tc.expectedNonEmpty && len(resultStr) == 0 {
t.Error("正常場景預期返回非空字符串,實際返回空字符串")
}
}
})
}
}
命令行執行命令
go test -cover
結果:
PS D:\wyl\workspace\go\tracer\dao\blog> go test -cover
發送 GET 請求失敗:Get "http://invalid-xxx-url-12345/": dial tcp: lookup invalid-xxx-url-12345: no such host
請求失敗,狀態碼:404,狀態信息:404 Not Found
請求失敗,狀態碼:500,狀態信息:500 Internal Server Error
PASS
coverage: 100.0% of statements
ok tracer/dao/blog 1.695s
PS D:\wyl\workspace\go\tracer\dao\blog>
2、go-sqlmock
介紹:gosqlmock是一個用於模擬數據庫 /sql 驅動的庫,核心作用是在不依賴真實數據庫實例的情況下,對數據庫相關邏輯進行單元測試,避免測試過程中操作真實數據、產生髒數據或依賴數據庫服務可用性。
優點:
- 解除真實數據庫依賴,保證測試獨立、穩定、無髒數據
- 精準控制數據庫行為,覆蓋常規 / 異常全量測試場景
- 兼容
database/sql標準庫和主流 ORM,無侵入式集成 - 嚴格驗證預期行為,提升測試準確性,發現隱藏問題
- 輕量級無冗餘,內存級執行,測試性能優異
- 支持正則匹配,靈活適配複雜 SQL 場景
1.安裝
go get github.com/DATA-DOG/go-sqlmock
2.使用示例
相關代碼在gitee代碼倉庫的示例代碼中,倉庫地址請看博客開頭
price_policy.go
package price
import (
"gorm.io/gorm"
"tracer/model"
)
type PricePolicy struct {
gorm.Model
Catogory string `gorm:"type:varchar(64)" json:"catogory" label:"收費類型"`
Title string `gorm:"type:varchar(64)" json:"title" label:"標題"`
Price uint64 `gorm:"type:int(5)" json:"price" label:"價格"`
ProjectNum uint64 `json:"project_num" label:"項目數量"`
ProjectMember uint64 `json:"project_member" label:"項目成員人數"`
ProjectSpace uint64 `json:"project_space" label:"每個項目空間" help_text:"單位是M"`
PerFileSize uint64 `json:"per_file_size" label:"單文件大小" help_text:"單位是M"`
}
// GetAllBlog 查詢所有博客信息
func GetAllBlog() PricePolicy {
var allBlog PricePolicy
model.DB.Find(&allBlog)
return allBlog
}
// TypeBlog 根據類型查找博客
func TypeBlog(tyb string) PricePolicy {
var typeBlog PricePolicy
model.DB.Where("type=?", tyb).Find(&typeBlog)
return typeBlog
}
// TopBlog 置頂博客查詢
func TopBlog(top string) PricePolicy {
var topBlog PricePolicy
model.DB.Where("top=?", top).Find(&topBlog)
return topBlog
}
price_policy_test.go
package price
import (
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"testing"
"time"
"tracer/model"
)
// TestGetAllBlog GetAllBlog 函數單元測試
func TestGetAllBlog(t *testing.T) {
// 步驟1:創建 sqlmock 模擬連接(內存級,無真實數據庫依賴)
// sqlmock.New() 返回 mockDB(*sql.DB)、mock(sqlmock.Sqlmock)、error
mockSqlDB, mock, err := sqlmock.New()
assert.NoError(t, err, "創建 sqlmock 連接失敗")
defer mockSqlDB.Close() // 測試結束關閉模擬連接
// 步驟2:將 sqlmock 連接適配為 GORM 可用的 DB 實例
// 關鍵:使用 gorm mysql 驅動,傳入 mock 的 *sql.DB 實例
gormDB, err := gorm.Open(mysql.New(mysql.Config{
Conn: mockSqlDB, // 綁定 sqlmock 的連接
SkipInitializeWithVersion: true, // 跳過 MySQL 版本檢測(模擬連接無需版本信息)
}), &gorm.Config{})
assert.NoError(t, err, "GORM 綁定 sqlmock 連接失敗")
// 步驟3:替換全局 DB 為 mock 的 GORM DB(核心:讓業務函數使用 mock 連接)
model.DB = gormDB
// 步驟4:構造模擬返回數據(與 PricePolicy 字段對應,需包含 gorm.Model 的默認字段)
expectedPolicy := PricePolicy{
Model: gorm.Model{
ID: 1,
CreatedAt: time.Time{}, // 測試中可忽略時間字段,若需精確匹配可賦值 time.Time 實例
UpdatedAt: time.Time{},
DeletedAt: gorm.DeletedAt{},
},
Catogory: "個人版",
Title: "基礎收費套餐",
Price: 99,
ProjectNum: 5,
ProjectMember: 10,
ProjectSpace: 1024,
PerFileSize: 50,
}
// 步驟5:設置 sqlmock 預期(關鍵:匹配 GORM 自動生成的 SQL 語句)
// GORM 的 Find(&allBlog) 會生成 SELECT * FROM `price_policies` 語句(表名默認是結構體小寫複數)
// 使用正則匹配,忽略無關空格和潛在的字段順序差異
rows := sqlmock.NewRows([]string{
"id", "created_at", "updated_at", "deleted_at",
"catogory", "title", "price", "project_num",
"project_member", "project_space", "per_file_size",
}).AddRow(
expectedPolicy.ID, expectedPolicy.CreatedAt, expectedPolicy.UpdatedAt, expectedPolicy.DeletedAt,
expectedPolicy.Catogory, expectedPolicy.Title, expectedPolicy.Price, expectedPolicy.ProjectNum,
expectedPolicy.ProjectMember, expectedPolicy.ProjectSpace, expectedPolicy.PerFileSize,
)
// 預設查詢預期:匹配 GORM 生成的 SELECT 語句
mock.ExpectQuery("^SELECT \\* FROM `price_policies`").
WillReturnRows(rows) // 設置查詢返回的模擬數據
// 步驟6:執行待測試函數
actualPolicy := GetAllBlog()
// 步驟7:驗證結果
// 驗證核心字段是否一致(gorm.Model 時間字段若未賦值,可忽略或單獨校驗)
assert.Equal(t, expectedPolicy.ID, actualPolicy.ID, "ID 不匹配")
assert.Equal(t, expectedPolicy.Catogory, actualPolicy.Catogory, "收費類型不匹配")
assert.Equal(t, expectedPolicy.Title, actualPolicy.Title, "標題不匹配")
assert.Equal(t, expectedPolicy.Price, actualPolicy.Price, "價格不匹配")
assert.Equal(t, expectedPolicy.ProjectNum, actualPolicy.ProjectNum, "項目數量不匹配")
assert.Equal(t, expectedPolicy.ProjectMember, actualPolicy.ProjectMember, "項目成員人數不匹配")
assert.Equal(t, expectedPolicy.ProjectSpace, actualPolicy.ProjectSpace, "項目空間不匹配")
assert.Equal(t, expectedPolicy.PerFileSize, actualPolicy.PerFileSize, "單文件大小不匹配")
// 關鍵:驗證所有 sqlmock 預期都已被執行(無遺漏、無多餘操作)
assert.NoError(t, mock.ExpectationsWereMet(), "存在未滿足的 sqlmock 預期")
}
命令行執行命令
go test -cover
結果:
PS D:\wyl\workspace\go\tracer\model\price> go test -cover
PASS
coverage: 33.3% of statements
ok tracer/model/price 0.076s
3、gomonkey
介紹:gomonkey是一款強大的運行時打樁(Mock)工具,能夠在不修改源代碼的前提下,對函數、方法、全局變量等進行動態替換,廣泛用於單元測試場景
優點:
- 無侵入式打樁,無需修改業務代碼
- 功能全面,支持函數、方法、全局變量等多種打樁場景
- 支持私有成員打樁,適配遺留項目
- 輕量級易用,API 簡潔,兼容主流框架
- 靈活控制打樁生命週期,精準適配測試需求
1.安裝
go get github.com/agiledragon/gomonkey/v2
2.使用示例
相關代碼在gitee代碼倉庫的示例代碼中,倉庫地址請看博客開頭
blog.go
package user
import "tracer/model/user"
// UserInfoDao 查詢所有用户信息
func UserInfoDao() (interface{}, error) {
obj, err := user.GetAllUser()
if err != nil {
return nil, err
}
return obj, nil
}
blog_test.go
package user
import (
"errors"
"fmt"
"github.com/agiledragon/gomonkey/v2"
"gorm.io/gorm"
"testing"
"tracer/dao"
"tracer/model/user"
)
// TestUserInfoDao_AllScenarios 單個函數通過循環覆蓋所有測試場景
func TestUserInfoDao_AllScenarios(t *testing.T) {
// 1. 定義測試用例結構體:封裝輸入(打樁參數)和預期輸出
type testCase struct {
name string // 用例名稱,便於排查錯誤
mockUsers []user.UserInfo // 打樁 GetAllUser 返回的用户列表
mockErr error // 打樁 GetAllUser 返回的錯誤
expectedErr error // 預期 UserInfoDao 返回的錯誤
expectedNil bool // 預期 UserInfoDao 返回的數據是否為 nil
expectedCount int // 預期返回的用户數量(正常場景有效)
}
// 2. 構造所有測試用例(正常場景 + 異常場景)
testCases := []testCase{
{
name: "正常場景-返回2個用户",
mockUsers: []user.UserInfo{
{
Model: gorm.Model{ID: 1},
UserName: "zhangsan",
Password: "123456",
Phone: "13800138000",
Email: "zhangsan@test.com",
},
{
Model: gorm.Model{ID: 2},
UserName: "lisi",
Password: "654321",
Phone: "13900139000",
Email: "lisi@test.com",
},
},
mockErr: nil,
expectedErr: nil,
expectedNil: false,
expectedCount: 2,
},
{
name: "正常場景-返回空用户列表",
mockUsers: []user.UserInfo{},
mockErr: nil,
expectedErr: nil,
expectedNil: false,
expectedCount: 0,
},
{
name: "異常場景-GORM記錄不存在錯誤",
mockUsers: nil,
mockErr: gorm.ErrRecordNotFound,
expectedErr: gorm.ErrRecordNotFound,
expectedNil: true,
expectedCount: 0,
},
{
name: "異常場景-自定義查詢錯誤",
mockUsers: nil,
mockErr: errors.New("數據庫連接超時"),
expectedErr: errors.New("數據庫連接超時"),
expectedNil: true,
expectedCount: 0,
},
}
// 3. 循環執行所有測試用例
for _, tc := range testCases {
dao.InitDb()
// t.Run:為每個用例創建獨立的測試上下文,互不干擾,便於定位用例錯誤
t.Run(tc.name, func(t *testing.T) {
// 步驟1:對 GetAllUser 進行動態打樁(每個用例獨立打樁,避免相互影響)
// 使用ApplyFunc打樁跨包函數
patches := gomonkey.ApplyFunc(user.GetAllUser, func() ([]user.UserInfo, error) {
// 返回當前用例預設的模擬數據和錯誤
return tc.mockUsers, tc.mockErr
})
defer patches.Reset() // 每個用例執行完畢後重置打樁,避免污染其他用例
// 步驟2:執行待測試函數 UserInfoDao
_, err := UserInfoDao()
if err != nil {
fmt.Println(err)
}
})
}
}
命令行執行命令
go test -cover
結果:
PS D:\wyl\workspace\go\tracer\dao\user> go test -cover
PASS
coverage: 75.0% of statements
ok tracer/dao/user 0.097s
如果報錯,這個問題是數據庫中不存在表:
Error 1146 (42S02): Table 'tracer.user_info' doesn't exist