使用示例示例(1)使用方法一全局函数调用,其余使用结构体方法调用。

📅 2026/6/30 1:56:46
使用示例示例(1)使用方法一全局函数调用,其余使用结构体方法调用。
gomonkey.ApplyFunc()相关代码在gitee代码仓库的示例代码中仓库地址请看博客开头参数示例// 第一个参数函数名// 第二个参数打桩函数入参和出参要保持和被打桩函数保持一致func ApplyFunc(target, double interface{}) *Patches {return create().ApplyFunc(target, double)}user.gopackage func_demoimport 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.gopackage func_demoimport (errorsfmtgithub.com/agiledragon/gomonkey/v2gorm.io/gormtestingtracer/modeltracer/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 返回的数据是否为 nilexpectedCount int // 预期返回的用户数量正常场景有效}// 2. 构造所有测试用例正常场景 异常场景testCases : []testCase{{name: 正常场景-返回2个用户,mockUsers: []sqlmock_demo.UserInfo{{Model: gorm.Model{ID: 1},UserName: zhangsan,Password: 123456,Phone: 13800138000,Email: zhangsantest.com,},{Model: gorm.Model{ID: 2},UserName: lisi,Password: 654321,Phone: 13900139000,Email: lisitest.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 -gcflagsall-l -covermodeatomic结果PS D:\wyl\workspace\go\tracer\logic\func_demo go test -coverPASScoverage: 75.0% of statementsok tracer/logic/func_demo 0.088s如果报错这个问题是数据库中不存在表Error 1146 (42S02): Table tracer.user_info doesnt exist2结构体方法打桩方法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.gotype 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$ -coverMethodDemo: hello gomonkeyPASScoverage: 50.0% of statementsok tracer/logic/method_demo 0.303sgomonkey.ApplyMethodFunc()method_func_demo.gotype 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$ -gcflagsall-l结果PS D:\wyl\workspace\go\tracer\logic\method_demo go test -run ^TestMethodFuncDemo$MethodFuncDemo: hello gomonkey 结构体值类型绑定PASSok tracer/logic/method_demo 0.243sPS D:\wyl\workspace\go\tracer\logic\method_demo go test -run ^TestMethodFuncDemo$ -gcflagsall-lPASSok tracer/logic/method_demo 0.219s