• [转]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
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    41:和为S的两个数
    40:数组中只出现一次的数字
    39-2:平衡二叉树
    39:二叉树的深度
    38:数字在排序数组中出现的次数
    37:两个链表的第一个公共结点
    36:数组中的逆序对
    35:第一个只出现一次的字符
    34:丑数
    33:把数组排成最小的数
  • 原文地址:https://www.cnblogs.com/oxspirt/p/14509881.html
Copyright © 2020-2023  润新知