[转]gomonkey学习
原文: https://www.jianshu.com/p/633b55d73ddd
.mock 方法 (interface)
type Student struct {}
func (s Student) Interesting(prodName string) bool{
return true
}
func testFunc(prodName string) bool{
st := &Student{}
result := st.Interesting(prodName)
if false == result {
fmt.Println("Student like product ", prodName)
}
return result
}
测试:
func Test_testFunc(t *testing.T) {
stu := Student{}
patch := ApplyMethod(reflect.TypeOf(stu), "Interesting", func(Student, string) bool {
return true
})
result := testFunc("pen")
So(result, ShouldEqual, true)
patch.Reset()
fmt.Println("CompareInt test success")
}
————————————————
版权声明:本文为CSDN博主「jiuweiC」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jiuweiC/article/details/110442937
----------------
gomonkey 介绍
gomonkey 是 golang 的一款打桩框架,目标是让用户在单元测试中低成本的完成打桩,从而将精力聚焦于业务功能的开发。gomonkey 接口友好,功能强大,目前已被很多项目使用,用户遍及世界多个国家。
gomonkey 1.0 特性列表
gomonkey 1.0 特性列表如下:
- 支持为一个函数打一个桩
- 支持为一个成员方法打一个桩
- 支持为一个全局变量打一个桩
- 支持为一个函数变量打一个桩
- 支持为一个函数打一个特定的桩序列
- 支持为一个成员方法打一个特定的桩序列
- 支持为一个函数变量打一个特定的桩序列
下载地址:
https://github.com/agiledragon/gomonkey/releases/tag/v1.0
习惯用法
gomonkey 提供的 API 包括函数接口和成员方法接口,接口契约一致,扩展性很强。
示例代码中用到了 goconvey,它是一款针对 golang 的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多 Web 界面特性。
ApplyFunc
ApplyFunc 接口定义如下:
func ApplyFunc(target, double interface{}) *Patches
func (this *Patches) ApplyFunc(target, double interface{}) *Patches
ApplyFunc 第一个参数是函数名,第二个参数是桩函数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
函数打桩的习惯用法:
import (
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"testing"
"github.com/agiledragon/gomonkey/test/fake"
"encoding/json"
)
var (
outputExpect = "xxx-vethName100-yyy"
)
func TestApplyFunc(t *testing.T) {
Convey("TestApplyFunc", t, func() {
Convey("one func for succ", func() {
patches := ApplyFunc(fake.Exec, func(_ string, _ ...string) (string, error) {
return outputExpect, nil
})
defer patches.Reset()
output, err := fake.Exec("", "")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, outputExpect)
})
Convey("one func for fail", func() {
patches := ApplyFunc(fake.Exec, func(_ string, _ ...string) (string, error) {
return "", fake.ErrActual
})
defer patches.Reset()
output, err := fake.Exec("", "")
So(err, ShouldEqual, fake.ErrActual)
So(output, ShouldEqual, "")
})
Convey("two funcs", func() {
patches := ApplyFunc(fake.Exec, func(_ string, _ ...string) (string, error) {
return outputExpect, nil
})
defer patches.Reset()
patches.ApplyFunc(fake.Belong, func(_ string, _ []string) bool {
return true
})
output, err := fake.Exec("", "")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, outputExpect)
flag := fake.Belong("", nil)
So(flag, ShouldBeTrue)
})
Convey("input and output param", func() {
patches := ApplyFunc(json.Unmarshal, func(_ []byte, v interface{}) error {
p := v.(*map[int]int)
*p = make(map[int]int)
(*p)[1] = 2
(*p)[2] = 4
return nil
})
defer patches.Reset()
var m map[int]int
err := json.Unmarshal(nil, &m)
So(err, ShouldEqual, nil)
So(m[1], ShouldEqual, 2)
So(m[2], ShouldEqual, 4)
})
})
}
ApplyMethod
ApplyMethod 接口定义如下:
func ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches
func (this *Patches) ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches
ApplyMethod 第一个参数是目标类的指针变量的反射类型,第二个参数是字符串形式的方法名,第三个参数是桩函数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
成员方法打桩的习惯用法:
import (
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"testing"
"reflect"
"github.com/agiledragon/gomonkey/test/fake"
)
func TestApplyMethod(t *testing.T) {
slice := fake.NewSlice()
var s *fake.Slice
Convey("TestApplyMethod", t, func() {
Convey("for succ", func() {
err := slice.Add(1)
So(err, ShouldEqual, nil)
patches := ApplyMethod(reflect.TypeOf(s), "Add", func(_ *fake.Slice, _ int) error {
return nil
})
defer patches.Reset()
err = slice.Add(1)
So(err, ShouldEqual, nil)
err = slice.Remove(1)
So(err, ShouldEqual, nil)
So(len(slice), ShouldEqual, 0)
})
Convey("for already exist", func() {
err := slice.Add(2)
So(err, ShouldEqual, nil)
patches := ApplyMethod(reflect.TypeOf(s), "Add", func(_ *fake.Slice, _ int) error {
return fake.ERR_ELEM_EXIST
})
defer patches.Reset()
err = slice.Add(1)
So(err, ShouldEqual, fake.ERR_ELEM_EXIST)
err = slice.Remove(2)
So(err, ShouldEqual, nil)
So(len(slice), ShouldEqual, 0)
})
Convey("two methods", func() {
err := slice.Add(3)
So(err, ShouldEqual, nil)
defer slice.Remove(3)
patches := ApplyMethod(reflect.TypeOf(s), "Add", func(_ *fake.Slice, _ int) error {
return fake.ERR_ELEM_EXIST
})
defer patches.Reset()
patches.ApplyMethod(reflect.TypeOf(s), "Remove", func(_ *fake.Slice, _ int) error {
return fake.ERR_ELEM_NT_EXIST
})
err = slice.Add(2)
So(err, ShouldEqual, fake.ERR_ELEM_EXIST)
err = slice.Remove(1)
So(err, ShouldEqual, fake.ERR_ELEM_NT_EXIST)
So(len(slice), ShouldEqual, 1)
So(slice[0], ShouldEqual, 3)
})
Convey("one func and one method", func() {
err := slice.Add(4)
So(err, ShouldEqual, nil)
defer slice.Remove(4)
patches := ApplyFunc(fake.Exec, func(_ string, _ ...string) (string, error) {
return outputExpect, nil
})
defer patches.Reset()
patches.ApplyMethod(reflect.TypeOf(s), "Remove", func(_ *fake.Slice, _ int) error {
return fake.ERR_ELEM_NT_EXIST
})
output, err := fake.Exec("", "")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, outputExpect)
err = slice.Remove(1)
So(err, ShouldEqual, fake.ERR_ELEM_NT_EXIST)
So(len(slice), ShouldEqual, 1)
So(slice[0], ShouldEqual, 4)
})
})
}
ApplyGlobalVar
ApplyGlobalVar 接口定义如下:
func ApplyGlobalVar(target, double interface{}) *Patches
func (this *Patches) ApplyGlobalVar(target, double interface{}) *Patches
ApplyGlobalVar 第一个参数是全局变量的地址,第二个参数是全局变量的桩。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
全局变量打桩的习惯用法:
import (
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
var num = 10
func TestApplyGlobalVar(t *testing.T) {
Convey("TestApplyGlobalVar", t, func() {
Convey("change", func() {
patches := ApplyGlobalVar(&num, 150)
defer patches.Reset()
So(num, ShouldEqual, 150)
})
Convey("recover", func() {
So(num, ShouldEqual, 10)
})
})
}
ApplyFuncVar
ApplyFuncVar 接口定义如下:
func ApplyFuncVar(target, double interface{}) *Patches
func (this *Patches) ApplyFuncVar(target, double interface{}) *Patches
ApplyFuncVar 第一个参数是函数变量的地址,第二个参数是桩函数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
函数变量打桩的习惯用法:
import (
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"testing"
"github.com/agiledragon/gomonkey/test/fake"
)
func TestApplyFuncVar(t *testing.T) {
Convey("TestApplyFuncVar", t, func() {
Convey("for succ", func() {
str := "hello"
patches := ApplyFuncVar(&fake.Marshal, func (_ interface{}) ([]byte, error) {
return []byte(str), nil
})
defer patches.Reset()
bytes, err := fake.Marshal(nil)
So(err, ShouldEqual, nil)
So(string(bytes), ShouldEqual, str)
})
Convey("for fail", func() {
patches := ApplyFuncVar(&fake.Marshal, func (_ interface{}) ([]byte, error) {
return nil, fake.ErrActual
})
defer patches.Reset()
_, err := fake.Marshal(nil)
So(err, ShouldEqual, fake.ErrActual)
})
})
}
ApplyFuncSeq
ApplyFuncSeq 接口定义如下:
func ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches
func (this *Patches) ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches
其中,OutputCell 的定义为:
type Params []interface{}
type OutputCell struct {
Values Params
Times int
}
ApplyFuncSeq 第一个参数是函数名,第二个参数是特定的桩序列参数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
函数打桩序列的习惯用法:
import (
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"testing"
"github.com/agiledragon/gomonkey/test/fake"
)
func TestApplyFuncSeq(t *testing.T) {
Convey("TestApplyFuncSeq", t, func() {
Convey("default times is 1", func() {
info1 := "hello cpp"
info2 := "hello golang"
info3 := "hello gomonkey"
outputs := []OutputCell{
{Values: Params{info1, nil}},
{Values: Params{info2, nil}},
{Values: Params{info3, nil}},
}
patches := ApplyFuncSeq(fake.ReadLeaf, outputs)
defer patches.Reset()
output, err := fake.ReadLeaf("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info1)
output, err = fake.ReadLeaf("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info2)
output, err = fake.ReadLeaf("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info3)
})
Convey("retry succ util the third times", func() {
info1 := "hello cpp"
outputs := []OutputCell{
{Values: Params{"", fake.ErrActual}, Times: 2},
{Values: Params{info1, nil}},
}
patches := ApplyFuncSeq(fake.ReadLeaf, outputs)
defer patches.Reset()
output, err := fake.ReadLeaf("")
So(err, ShouldEqual, fake.ErrActual)
output, err = fake.ReadLeaf("")
So(err, ShouldEqual, fake.ErrActual)
output, err = fake.ReadLeaf("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info1)
})
Convey("batch operations failed on the third time", func() {
info1 := "hello gomonkey"
outputs := []OutputCell{
{Values: Params{info1, nil}, Times: 2},
{Values: Params{"", fake.ErrActual}},
}
patches := ApplyFuncSeq(fake.ReadLeaf, outputs)
defer patches.Reset()
output, err := fake.ReadLeaf("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info1)
output, err = fake.ReadLeaf("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info1)
output, err = fake.ReadLeaf("")
So(err, ShouldEqual, fake.ErrActual)
})
})
}
ApplyMethodSeq
ApplyMethodSeq 接口定义如下:
func ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell) *Patches
func (this *Patches) ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell) *Patches
ApplyMethodSeq 第一个参数是目标类的指针变量的反射类型,第二个参数是字符串形式的方法名,第三参数是特定的桩序列参数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
成员方法打桩序列的习惯用法:
import (
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"testing"
"github.com/agiledragon/gomonkey/test/fake"
"reflect"
)
func TestApplyMethodSeq(t *testing.T) {
e := &fake.Etcd{}
Convey("TestApplyMethodSeq", t, func() {
Convey("default times is 1", func() {
info1 := "hello cpp"
info2 := "hello golang"
info3 := "hello gomonkey"
outputs := []OutputCell{
{Values: Params{info1, nil}},
{Values: Params{info2, nil}},
{Values: Params{info3, nil}},
}
patches := ApplyMethodSeq(reflect.TypeOf(e), "Retrieve", outputs)
defer patches.Reset()
output, err := e.Retrieve("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info1)
output, err = e.Retrieve("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info2)
output, err = e.Retrieve("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info3)
})
Convey("retry succ util the third times", func() {
info1 := "hello cpp"
outputs := []OutputCell{
{Values: Params{"", fake.ErrActual}, Times: 2},
{Values: Params{info1, nil}},
}
patches := ApplyMethodSeq(reflect.TypeOf(e), "Retrieve", outputs)
defer patches.Reset()
output, err := e.Retrieve("")
So(err, ShouldEqual, fake.ErrActual)
output, err = e.Retrieve("")
So(err, ShouldEqual, fake.ErrActual)
output, err = e.Retrieve("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info1)
})
Convey("batch operations failed on the third time", func() {
info1 := "hello gomonkey"
outputs := []OutputCell{
{Values: Params{info1, nil}, Times: 2},
{Values: Params{"", fake.ErrActual}},
}
patches := ApplyMethodSeq(reflect.TypeOf(e), "Retrieve", outputs)
defer patches.Reset()
output, err := e.Retrieve("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info1)
output, err = e.Retrieve("")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, info1)
output, err = e.Retrieve("")
So(err, ShouldEqual, fake.ErrActual)
})
})
}
ApplyFuncVarSeq
ApplyFuncVarSeq 接口定义如下:
func ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches
func (this *Patches) ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches
ApplyFuncVarSeq 第一个参数是函数变量地址,第二个参数是特定的桩序列参数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
函数变量打桩序列的习惯用法:
import (
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"testing"
"github.com/agiledragon/gomonkey/test/fake"
)
func TestApplyFuncVarSeq(t *testing.T) {
Convey("TestApplyFuncVarSeq", t, func() {
Convey("default times is 1", func() {
info1 := "hello cpp"
info2 := "hello golang"
info3 := "hello gomonkey"
outputs := []OutputCell{
{Values: Params{[]byte(info1), nil}},
{Values: Params{[]byte(info2), nil}},
{Values: Params{[]byte(info3), nil}},
}
patches := ApplyFuncVarSeq(&fake.Marshal, outputs)
defer patches.Reset()
bytes, err := fake.Marshal("")
So(err, ShouldEqual, nil)
So(string(bytes), ShouldEqual, info1)
bytes, err = fake.Marshal("")
So(err, ShouldEqual, nil)
So(string(bytes), ShouldEqual, info2)
bytes, err = fake.Marshal("")
So(err, ShouldEqual, nil)
So(string(bytes), ShouldEqual, info3)
})
Convey("retry succ util the third times", func() {
info1 := "hello cpp"
outputs := []OutputCell{
{Values: Params{[]byte(""), fake.ErrActual}, Times: 2},
{Values: Params{[]byte(info1), nil}},
}
patches := ApplyFuncVarSeq(&fake.Marshal, outputs)
defer patches.Reset()
bytes, err := fake.Marshal("")
So(err, ShouldEqual, fake.ErrActual)
bytes, err = fake.Marshal("")
So(err, ShouldEqual, fake.ErrActual)
bytes, err = fake.Marshal("")
So(err, ShouldEqual, nil)
So(string(bytes), ShouldEqual, info1)
})
Convey("batch operations failed on the third time", func() {
info1 := "hello gomonkey"
outputs := []OutputCell{
{Values: Params{[]byte(info1), nil}, Times: 2},
{Values: Params{[]byte(""), fake.ErrActual}},
}
patches := ApplyFuncVarSeq(&fake.Marshal, outputs)
defer patches.Reset()
bytes, err := fake.Marshal("")
So(err, ShouldEqual, nil)
So(string(bytes), ShouldEqual, info1)
bytes, err = fake.Marshal("")
So(err, ShouldEqual, nil)
So(string(bytes), ShouldEqual, info1)
bytes, err = fake.Marshal("")
So(err, ShouldEqual, fake.ErrActual)
})
})
}
NewPatches
NewPatches 接口定义如下:
func NewPatches() *Patches
NewPatches 是 patches 对象的显式构造函数,一般用于目标和桩的表驱动场景。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
NewPatches 的习惯用法:
import (
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"testing"
"github.com/agiledragon/gomonkey/test/fake"
"encoding/json"
)
func TestPatchPair(t *testing.T) {
Convey("TestPatchPair", t, func() {
Convey("TestPatchPair", func() {
patchPairs := [][2]interface{} {
{
fake.Exec,
func(_ string, _ ...string) (string, error) {
return outputExpect, nil
},
},
{
json.Unmarshal,
func(_ []byte, v interface{}) error {
p := v.(*map[int]int)
*p = make(map[int]int)
(*p)[1] = 2
(*p)[2] = 4
return nil
},
},
}
patches := NewPatches()
defer patches.Reset()
for _, pair := range patchPairs {
patches.ApplyFunc(pair[0], pair[1])
}
output, err := fake.Exec("", "")
So(err, ShouldEqual, nil)
So(output, ShouldEqual, outputExpect)
var m map[int]int
err = json.Unmarshal(nil, &m)
So(err, ShouldEqual, nil)
So(m[1], ShouldEqual, 2)
So(m[2], ShouldEqual, 4)
})
})
}
注意事项
如果 gomonkey 打桩失败,请确认:
- 打桩目标是否为内联的函数或成员方法?如果是,请在测试时通过命令行参数
-gcflags=-l
(go1.10 版本之前)或-gcflags=all=-l
(go1.10 版本及之后)关闭内联优化; - gomonkey 是否在非 amd64 架构的处理器上运行?如果是,则需要开发相关的功能代码;
- gomonkey 是否被多协程并发用于同一个目标的打桩?如果是,则需要将之前的协程先优雅退出;
- go1.6 版本的反射机制支持私有成员方法的查询,而 go1.7 及之后的版本却不支持。然而,所有版本的反射机制都支持私有函数的查询,所以当用户使用 go1.7 及之后的版本时,gomonkey 对于私有成员方法的打桩将触发一个异常。
后续计划
- 支持为一个 interface 打一个桩
- 支持为一个 interface 打一个特定的桩序列
- 在必要的情况下,支持非 amd64 架构的其它处理器
作者:_张晓龙_
链接:https://www.jianshu.com/p/633b55d73ddd
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2017-03-10 【转】 一张图看懂开源许可协议,开源许可证GPL、BSD、MIT、Mozilla、Apache和LGPL的区别
2017-03-10 redis
2017-03-10 How To Configure a Redis Cluster on Ubuntu 14.04
2017-03-10 redis connetced refused remote
2017-03-10 redis 中如何切换db
2017-03-10 分布式数据库