Go语言中同一对象成员函数互调时的单元测试与Mock方法
Go中同一对象成员函数调用的单元测试与Mock方案
问题场景
在Go开发中,常会遇到同一对象的成员函数调用另一成员函数的场景,示例代码如下:
type Obj struct{} func (o Obj) First() { // 执行核心逻辑 o.Second() } func (o Obj) Second() { // 执行子逻辑 }
需要解决的问题:
- 如何为
First编写单元测试? - 测试
First时,如何MockSecond的行为?
我梳理的初步思路
- 职责拆分:
Second()的职责可能属于其他对象,说明Obj职责过重,应该拆分(但当前场景下无法抽离) - 临时参数传入:将对象作为参数传入
First(),但这种方案不优雅,容易遗留技术债务:func First(o Obj) { // do something o.Second() } - 接口依赖注入:为
First()添加一个包含Second()行为的接口类型参数(但当前场景下Second()无法抽离)
针对「无法抽离Second」场景的可行方案
方案1:嵌入类型+接口Mock
利用Go的接口特性和类型嵌入,创建测试替身来MockSecond方法:
// 定义包含Second方法的接口 type SecondExecutor interface { Second() } // Obj天然实现SecondExecutor接口 type Obj struct{} func (o *Obj) First() { // 建议将接收者改为指针类型,便于Mock // 执行核心逻辑 o.Second() } func (o *Obj) Second() { // 原逻辑 } // 测试用Mock类型,嵌入原Obj,重写Second方法 type MockObj struct { *Obj SecondCalled bool } func (m *MockObj) Second() { m.SecondCalled = true // 自定义Mock逻辑 } // 单元测试 func TestFirst(t *testing.T) { mock := &MockObj{Obj: &Obj{}} mock.First() if !mock.SecondCalled { t.Fatal("Second方法未被First调用") } }
核心逻辑:通过嵌入原对象继承First方法,同时重写Second方法实现Mock,利用Go的方法集规则确保调用时优先使用Mock的方法。
方案2:可替换的函数字段
在Obj中添加函数字段存储Second的实现,测试时替换该字段:
type Obj struct { secondImpl func() // 存储Second的实现 } // 初始化对象时设置默认实现 func NewObj() *Obj { obj := &Obj{} obj.secondImpl = obj.Second return obj } func (o *Obj) First() { // 执行核心逻辑 o.secondImpl() } func (o *Obj) Second() { // 原逻辑 } // 单元测试 func TestFirst(t *testing.T) { secondCalled := false obj := &Obj{ secondImpl: func() { secondCalled = true }, } obj.First() if !secondCalled { t.Fatal("Second逻辑未被执行") } }
这种方式无需定义接口,直接通过替换函数字段实现Mock,适合简单场景;如果Second需要访问对象内部状态,可将函数字段改为func(*Obj)。
方案3:测试替身类型继承
创建继承原类型的测试替身,重写Second方法:
type Obj struct{} func (o *Obj) First() { o.Second() } func (o *Obj) Second() { // 原逻辑 } // 测试替身类型 type TestObj struct { *Obj SecondInvokeCount int } func (t *TestObj) Second() { t.SecondInvokeCount++ } // 单元测试 func TestFirst(t *testing.T) { testObj := &TestObj{Obj: &Obj{}} testObj.First() if testObj.SecondInvokeCount != 1 { t.Fatalf("Second方法应被调用1次,实际调用%d次", testObj.SecondInvokeCount) } }
通过类型嵌入继承原对象的First方法,重写Second方法实现调用记录或自定义逻辑,适合需要跟踪调用次数的场景。
内容的提问来源于stack exchange,提问作者Vikas Kaushik
相关产品推荐
相关产品推荐

