多次调用,顺序返回不同结果

发布时间:2026/6/30 1:55:35

多次调用,顺序返回不同结果 gomonkey.ApplyMethodSeq()高频实用方法处理「结构体方法被多次调用需要返回不同结果」的核心方法完美解决方法第 1 次调用返回 A、第 2 次返回 B、第 N 次返回默认值 / 报错 这类高频业务场景。这个也和上面2和3一样支持两种写法函数和结构体方法写法类似这里就只写一个示例了参数示例// 存储单次调用的返回值集合type Params []interface{}type OutputCell struct {Values Params // 本次要返回的参数个数/类型必须和原方法返回值完全一致Times int // 生效次数-1永久生效匹配后后续调用全用这个0生效指定次数}// 第一个参数要打桩的方法所属的类型通过 reflect.TypeOf(实例) 获取区分值接收者 / 指针接收者// 第二个参数要打桩的方法名字符串格式、大小写敏感必须和原方法名完全一致// 第三个参数打桩方法返回值切片入参和出参要保持和被打桩方法保持一致func (this *Patches) ApplyMethodSeq(target interface{}, methodName string, outputs []OutputCell) *Patches {m, ok : castRType(target).MethodByName(methodName)if !ok {panic(retrieve method by name failed)}d : getDoubleFunc(m.Type, outputs)return this.ApplyCore(m.Func, d)}product.gopackage method_demoimport fmt// 1. 定义业务结构体和待打桩的方法 type Product struct {Id intName stringStock int}// 结构体公有方法首字母大写库存查询返回【库存数量、错误信息】func (p *Product) GetStock() (int, error) {// 真实业务逻辑查询数据库/缓存获取库存return p.Stock, nil}// 2. 业务逻辑连续调用3次GetStock方法 // 模拟业务中多次调用结构体方法的场景func QueryStockMultiTimes(p *Product) {// 第1次调用stock1, err1 : p.GetStock()fmt.Printf(第1次查询库存: %d, err: %v\n, stock1, err1)// 第2次调用stock2, err2 : p.GetStock()fmt.Printf(第2次查询库存: %d, err: %v\n, stock2, err2)// 第3次调用stock3, err3 : p.GetStock()fmt.Printf(第3次查询库存: %d, err: %v\n, stock3, err3)}product_test.gopackage method_demoimport (errorsgithub.com/agiledragon/gomonkey/v2testing)func TestProduct(t *testing.T) {// 1. 创建patches实例必须写defer Reset()保证打桩还原无残留patches : gomonkey.NewPatches()defer patches.Reset()// 2. 初始化结构体实例prod : Product{Id: 1, Name: 苹果手机, Stock: 200}// 3. 核心定义序列打桩规则stockSeq : []gomonkey.OutputCell{{Values: gomonkey.Params{100, nil}, Times: 1}, // 规则1第1次调用返回100nil{Values: gomonkey.Params{0, nil}, Times: 1}, // 规则2第2次调用返回0nil{Values: gomonkey.Params{0, errors.New(库存不足无法下单)}, Times: 1}, // 规则3永久生效-1这个参数会导致严重bug避免使用}// 4. 执行方法序列打桩patches.ApplyMethodSeq(prod, GetStock, stockSeq)// 5. 执行业务逻辑触发多次调用QueryStockMultiTimes(prod)}命令行执行命令go test -run ^TestProduct$ -cover -gcflagsall-l -covermodeatomic结果PS D:\wyl\workspace\go\tracer\logic\method_demo go test -run ^TestProduct$ -cover -gcflagsall-l -covermodeatomic第1次查询库存: 100, err: nil第2次查询库存: 0, err: nil第3次查询库存: 0, err: 库存不足无法下单PASScoverage: 54.5% of statementsok tracer/logic/method_demo 0.330s5结构体私有方法打桩方法gomonkey.ApplyPrivateMethod()私有方法打桩方案方案一【最优推荐零侵入、Go 官方标准】测试文件和业务文件放在同一个包下首选无任何副作用方案二【妥协方案少量侵入】把「需要 mock 的私有方法」改为包级私有函数适合特殊场景方案三【不推荐侵入性大】把私有方法改为公有方法万不得已才用ApplyPrivateMethod和ApplyMethod打私有方法区别功能上完全一致同包下都能正常打桩、正常 mock 逻辑。唯一的区别是ApplyPrivateMethod语义精准一看就知道是打「私有方法」符合官方设计ApplyMethod语义模糊它的设计初衷是打「公有方法」打私有方法只是兼容生效。这个也和上面2和3一样支持两种写法函数和结构体方法写法类似这里就只写一个示例了参数示例// 第一个参数要打桩的方法所属的类型通过 reflect.TypeOf(实例) 获取区分值接收者 / 指针接收者// 第二个参数要打桩的方法名字符串格式、大小写敏感必须和原方法名完全一致// 第三个参数打桩方法入参和出参要保持和被打桩方法保持一致func (this *Patches) ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches {m, ok : creflect.MethodByName(castRType(target), methodName)if !ok {panic(retrieve method by name failed)}d : reflect.ValueOf(double)return this.ApplyCoreOnlyForPrivateMethod(m, d)}student.gopackage private_demotype Student struct{}// 公有方法对外暴露func (s Student) Study(course string) bool {// 内部调用了私有方法if s.checkCourse(course) { // 私有方法首字母小写s.recordLog(course) // 私有方法return true}return false}// 私有方法内部逻辑func (s Student) checkCourse(course string) bool {return course ! }func (s Student) recordLog(course string) {}student_test.gopackage private_demoimport (github.com/agiledragon/gomonkey/v2testing)// TODO: TestStudy有问题暂时不确定是因为不支持这种写法还是代码有问题func TestStudy(t *testing.T) {s : Student{}patches : gomonkey.NewPatches()defer patches.Reset()// ✅ 可以直接用gomonkey打桩【私有方法】checkCourse零权限问题patches.ApplyMethod(Student{}, checkCourse, func(_ *Student, course string) bool {return true // mock私有方法返回true})res : s.Study(math)if !res {t.Error(测试失败)}}// 测试代码指针接收者的私有方法打桩func TestStudyPrivate(t *testing.T) {s : Student{}patches : gomonkey.NewPatches()defer patches.Reset()patches.ApplyPrivateMethod((*Student)(nil), // ✅ 指针接收者传 (*结构体)(nil)checkCourse, // 私有方法名不变func(_ *Student, course string) bool { // ✅ 桩函数接收者是指针类型return true},)res : s.Study(math)if !res {t.Error(测试失败)}

相关新闻