[转]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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM