news 2026/6/30 1:55:26

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多次调用,顺序返回不同结果

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.go

package method_demo
import "fmt"
// ========== 1. 定义业务结构体和待打桩的方法 ==========
type Product struct {
Id int
Name string
Stock 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.go

package method_demo
import (
"errors"
"github.com/agiledragon/gomonkey/v2"
"testing"
)
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次调用,返回100,nil
{Values: gomonkey.Params{0, nil}, Times: 1}, // 规则2:第2次调用,返回0,nil
{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 -gcflags=all=-l -covermode=atomic

结果:

PS D:\wyl\workspace\go\tracer\logic\method_demo> go test -run "^TestProduct$" -cover -gcflags=all=-l -covermode=atomic
第1次查询库存: 100, err: <nil>
第2次查询库存: 0, err: <nil>
第3次查询库存: 0, err: 库存不足,无法下单
PASS
coverage: 54.5% of statements
ok tracer/logic/method_demo 0.330s

(5)结构体私有方法打桩方法

gomonkey.ApplyPrivateMethod()

私有方法打桩方案:

  • 方案一:【最优推荐,零侵入、Go 官方标准】测试文件和业务文件放在同一个包下(首选,无任何副作用)
  • 方案二:【妥协方案,少量侵入】把「需要 mock 的私有方法」改为包级私有函数(适合特殊场景)
  • 方案三:【不推荐,侵入性大】把私有方法改为公有方法(万不得已才用)

ApplyPrivateMethodApplyMethod打私有方法区别:功能上完全一致,同包下都能正常打桩、正常 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.go

package private_demo
type 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.go

package private_demo
import (
"github.com/agiledragon/gomonkey/v2"
"testing"
)
// 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("测试失败")
}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/30 1:54:22

用过才敢说!2026年亲测好用的专业AI论文网站

2026年AI论文写作工具已从“基础生成”升级为多场景适配的智能研究助手&#xff0c;核心评价维度涵盖文献真实性、格式合规性、长文本逻辑、查重降重、AIGC合规与跨语言支持。本次测评覆盖6款主流工具&#xff0c;涵盖中文与英文、全流程与专项功能、免费与付费版本&#xff0c…

作者头像 李华
网站建设 2026/6/30 1:53:30

信号链路——从采样电阻到电流数值

核心问题&#xff1a;三相电流从电机相线流出来&#xff0c;经过采样电阻、运放、ADC&#xff0c;最后变成代码里的i_fb——这条路每一步做了什么&#xff1f;怎么推导换算系数&#xff1f;我的板子参数&#xff1a;Rshunt1mΩ,Gain50,V_bias1.5V,Vs3.3V1.为什么需要偏置电压先…

作者头像 李华
网站建设 2026/6/30 1:49:31

目标定了,团队为什么总跑偏?

“年初定的目标是提升客户复购率&#xff0c;年中一看&#xff0c;团队都在忙着拉新。目标明明定得很清楚&#xff0c;为什么执行起来完全跑偏了&#xff1f;”这是我在一次企业家交流中听到的真实困惑。说话的是杭州一家做企业服务的老板&#xff0c;团队30多人&#xff0c;每…

作者头像 李华
网站建设 2026/6/30 1:49:16

Linux命令-quota(显示用户磁盘配额)

Linux命令-quota&#xff08;显示用户磁盘配额&#xff09;快速参考命令语法常用选项配额概念实战示例1. 基础查询2. 详细查询3. repquota&#xff1a;查看所有配额4. 配额配置流程5. 监控与告警6. edquota 交互式设置发行版差异配额相关命令全家桶总结快速参考 quota 命令用于…

作者头像 李华
网站建设 2026/6/30 1:48:48

[Android] iVCam(手机变电脑摄像头)专业版

[Android] iVCam&#xff08;手机变电脑摄像头&#xff09;专业版 链接&#xff1a;https://pan.xunlei.com/s/VOwIW2kkhk5-yP4fo6WYOZdoA1?pwdexkr# iVCam 可将安卓或苹果手机通过 Wi-Fi/USB 连接电脑&#xff0c;作为高清网络摄像头使用&#xff0c;适用于直播、网课、…

作者头像 李华