使用示例示例(1)使用方法一全局函数调用,其余使用结构体方法调用。
gomonkey.ApplyFunc()
相关代码在gitee代码仓库的示例代码中,仓库地址请看博客开头
参数示例:
// 第一个参数:函数名 |
// 第二个参数:打桩函数,入参和出参要保持和被打桩函数保持一致 |
func ApplyFunc(target, double interface{}) *Patches { |
return create().ApplyFunc(target, double) |
} |
user.go
package func_demo |
import "tracer/model/sqlmock_demo" |
// GetUserInfo 查询所有用户信息 |
func GetUserInfo() (interface{}, error) { |
obj, err := sqlmock_demo.GetAllUser() |
if err != nil { |
return nil, err |
} |
return obj, nil |
} |
user_test.go
package func_demo |
import ( |
"errors" |
"fmt" |
"github.com/agiledragon/gomonkey/v2" |
"gorm.io/gorm" |
"testing" |
"tracer/model" |
"tracer/model/sqlmock_demo" |
) |
// TestUserInfo 单个函数通过循环覆盖所有测试场景 |
func TestUserInfo(t *testing.T) { |
// 1. 定义测试用例结构体:封装输入(打桩参数)和预期输出 |
type testCase struct { |
name string // 用例名称,便于排查错误 |
mockUsers []sqlmock_demo.UserInfo // 打桩 GetAllUser 返回的用户列表 |
mockErr error // 打桩 GetAllUser 返回的错误 |
expectedErr error // 预期 UserInfoDao 返回的错误 |
expectedNil bool // 预期 UserInfoDao 返回的数据是否为 nil |
expectedCount int // 预期返回的用户数量(正常场景有效) |
} |
// 2. 构造所有测试用例(正常场景 + 异常场景) |
testCases := []testCase{ |
{ |
name: "正常场景-返回2个用户", |
mockUsers: []sqlmock_demo.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: []sqlmock_demo.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 { |
model.InitDb() |
// t.Run:为每个用例创建独立的测试上下文,互不干扰,便于定位用例错误 |
t.Run(tc.name, func(t *testing.T) { |
// 步骤1:对 GetAllUser 进行动态打桩(每个用例独立打桩,避免相互影响) |
// 使用ApplyFunc打桩跨包函数 |
patches := gomonkey.ApplyFunc(sqlmock_demo.GetAllUser, func() ([]sqlmock_demo.UserInfo, error) { |
// 返回当前用例预设的模拟数据和错误 |
return tc.mockUsers, tc.mockErr |
}) |
defer patches.Reset() // 每个用例执行完毕后重置打桩,避免污染其他用例 |
// 步骤2:执行待测试函数 GetUserInfo |
_, err := GetUserInfo() |
if err != nil { |
fmt.Println(err) |
} |
}) |
} |
} |
命令行执行命令
go test -cover -gcflags=all=-l -covermode=atomic
结果:
PS D:\wyl\workspace\go\tracer\logic\func_demo> go test -cover |
PASS |
coverage: 75.0% of statements |
ok tracer/logic/func_demo 0.088s |
如果报错,这个问题是数据库中不存在表:
Error 1146 (42S02): Table 'tracer.user_info' doesn't exist |
(2)结构体方法打桩方法
gomonkey.ApplyMethod()、gomonkey.ApplyMethodFunc()
区别:
- 匹配方式不同:
ApplyMethod是名称匹配,ApplyMethodFunc是函数本体匹配 - 传参核心不同:
ApplyMethod必须传reflect.Type+方法名字符串;ApplyMethodFunc直接传原方法函数,不需要反射 - 底层逻辑不同:
ApplyMethod是「反射查找方法」,ApplyMethodFunc是「直接绑定方法函数」,后者性能更高
gomonkey.ApplyMethod() 参数示例:
// 第一个参数:要打桩的方法所属的类型,通过 reflect.TypeOf(实例) 获取,区分值接收者 / 指针接收者 |
// 第二个参数:要打桩的方法名,字符串格式、大小写敏感,必须和原方法名完全一致 |
// 第三个参数:打桩方法,入参和出参要保持和被打桩方法保持一致,但需注意需要额外传入结构体类型且必须是第一个参数 |
func ApplyMethod(target interface{}, methodName string, double interface{}) *Patches { |
return create().ApplyMethod(target, methodName, double) |
} |
gomonkey.ApplyMethodFunc() 参数示例:
// 第一个参数:要打桩的方法所属的类型,通过 reflect.TypeOf(实例) 获取,区分值接收者 / 指针接收者 |
// 第二个参数:要打桩的方法名,字符串格式、大小写敏感,必须和原方法名完全一致 |
// 第三个参数:打桩方法,入参和出参要保持和被打桩方法保持一致,不需要额外参数 |
func ApplyMethodFunc(target interface{}, methodName string, doubleFunc interface{}) *Patches { |
return create().ApplyMethodFunc(target, methodName, doubleFunc) |
} |
注意:可以不用写reflect.TypeOf(实例),如果了解方法所属类型,可以直接写方法类型,而不用reflect.TypeOf()在获取一次
gomonkey.ApplyMethod()
method_demo.go
type MethodDemo struct { |
} |
func (m MethodDemo) MethodDemo(ret string) { |
fmt.Println("MethodDemo:", ret) |
} |
method_demo_test.go
// ========== 基于 gomonkey.ApplyMethod() 的单元测试 ========== |
func TestMethodDemo(t *testing.T) { |
// 1. 初始化结构体实例(当前结构体无成员变量,直接实例化即可) |
md := MethodDemo{} |
patches := gomonkey.NewPatches() |
// 铁律:延迟撤销打桩,防止污染其他测试用例,必写! |
defer patches.Reset() |
// 2. 核心:使用 gomonkey.ApplyMethod() 对【值接收者方法】打桩 |
// 第一个参数:reflect.TypeOf(实例) → 因为是值接收者,直接传值类型实例即可 |
// 第二个参数:被打桩的方法名字符串(严格和原方法名一致,大小写敏感) |
// 第三个参数:mock桩函数 → 入参/返回值 必须和原方法完全一致 |
patches.ApplyMethod( |
// 可以直接写 MethodDemo{} |
reflect.TypeOf(md), |
"MethodDemo", |
func(m MethodDemo, ret string) { |
// 自定义的mock逻辑,替代原方法的 fmt.Println 逻辑 |
t.Log("mock执行成功,入参ret:", ret) |
}, |
) |
// 3. 调用原方法,验证打桩是否生效 |
md.MethodDemo("hello gomonkey") |
} |
命令行执行命令
go test -run "^TestMethodDemo$" -cover
结果:
PS D:\wyl\workspace\go\tracer\logic\method_demo> go test -run "^TestMethodDemo$" -cover |
MethodDemo: hello gomonkey |
PASS |
coverage: 50.0% of statements |
ok tracer/logic/method_demo 0.303s |
gomonkey.ApplyMethodFunc()
method_func_demo.go
type MethodFuncDemo struct { |
} |
func (m MethodFuncDemo) MethodFuncDemo(ret string) { |
fmt.Println("MethodFuncDemo:", ret) |
} |
method_func_demo_test.go
// ========== 【结构体值类型绑定】对应的单元测试(纯值类型,无任何指针语法) ========== |
func TestMethodFuncDemo(t *testing.T) { |
// 1. 初始化【结构体值类型实例】 核心✅ 无指针&,纯结构体类型绑定 |
mfd := MethodFuncDemo{} |
patches := gomonkey.NewPatches() |
// 铁律:延迟撤销打桩,防止污染其他测试用例,必写! |
defer patches.Reset() |
// 2. 核心:gomonkey.ApplyMethodFunc 三参数打桩【结构体值类型绑定】 |
// 三参数固定规则:值类型 = 值实例 + 方法名字符串 + 值类型桩函数 |
patches.ApplyMethodFunc( |
mfd, // 参数1:结构体值类型实例(核心,纯值绑定) |
"MethodFuncDemo", // 参数2:方法名字符串(和值接收者方法名一致) |
func(ret string) { // 参数3:桩函数【无*号,纯结构体值类型入参】✅必匹配 |
// 桩函数第一个入参必须是:纯结构体类型 MethodFuncDemo,无任何指针 |
t.Log("✅ 结构体值类型绑定打桩生效,入参ret = ", ret) |
}, |
) |
// 3. 调用【值接收者方法】,验证结构体值类型绑定打桩结果 |
mfd.MethodFuncDemo("hello gomonkey 结构体值类型绑定") |
} |
命令行执行命令
go test -run "^TestMethodFuncDemo$" -gcflags=all=-l
结果:
PS D:\wyl\workspace\go\tracer\logic\method_demo> go test -run "^TestMethodFuncDemo$" |
MethodFuncDemo: hello gomonkey 结构体值类型绑定 |
PASS |
ok tracer/logic/method_demo 0.243s |
PS D:\wyl\workspace\go\tracer\logic\method_demo> go test -run "^TestMethodFuncDemo$" -gcflags=all=-l |
PASS |
ok tracer/logic/method_demo 0.219s |