美文网首页
gomonkey支持为private method打桩了

gomonkey支持为private method打桩了

作者: _张晓龙_ | 来源:发表于2021-10-19 07:18 被阅读0次

    引言

    gomonkey 是笔者开源的一款 Go 语言 的打桩框架,目标是让用户在单元测试中低成本的完成打桩,从而将精力聚焦于业务功能的开发。gomonkey 接口友好,功能强大,目前已被很多项目使用,用户遍及世界多个国家。

    这几年陆续有多个 gopher 线上或线下咨询笔者 gomonkey 是否可以给私有方法(private method)打桩的问题,他们往往显得比较焦急,笔者也感同身受,能够体会到他们对该需求的渴望。但那时那刻,该需求实现难度很大,作者通常会答复暂时无法实现该需求,主要原因是 Go 反射的 API 不支持,具体就是 reflect 包有限制,即提供的 MethodByName 方法实现中调用了私有的 exportedMethods (可导出)方法,就是说私有方法(不可导出)在 MethodByName 这个 API 中查不到。

    直到 gomonkey 全面支持 arm64 后,笔者感觉 gomonkey 已经开始引领 Go 语言的猴子补丁了。这时,恰好又有一些 gopher 站出来,希望 gomonkey 能够使用黑科技实现对私有方法打桩的需求。虽然这次与前几次是同样的需求,但此时此刻,作者突然有了一个想挑战一下的念头冒出来,于是就有了后续支持该需求的破局行动。

    有的读者可能会有疑问:public method 是类暴露出来的 API,相对稳定,而 private method 类内部的具体实现细节,可能不稳定,同时给 public method 打桩已经足够,为什么还有给 private method 打桩的需求?或者说,给 private method 打桩到底会有什么价值?

    笔者也同样思考过这个问题,结论是至少有一种场景,价值还是很大的:private method 封装了多 个下层操作,这些操作虽然都是 public method,但是对 private method 打桩只需打一次桩,而对多个下层操作的 public method 打桩需要打多次桩,并且这几个桩有关联。显然,这时对 private method 直接打桩会更高效,让开发者自测更美好

    private method 接口设计

    private method 需求实现的关键是要穿越 reflect 包的限制,需要在 gomonkey 内实现一个特定的反射包来与 Go 语言对接。但为 gomonkey 定制的反射包 creflect 与 Go 语言标准库的反射包 reflect 在内部数据结构上有一定的耦合,因此需要设计独立的 API 让用户使用,而不复用已有的 ApplyMethod,以便 creflect 包的变化仅仅影响 private method 需求。

    按照 gomonkey 的惯例,private method 接口应该有两个:一个是函数接口,一个是方法接口

    func ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches
    func (this *Patches) ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches
    

    说明:除过方法名字,参数和 ApplyMethod 一模一样,之所以用两个方法名,完全是为了隔离变化。

    private method 接口使用方法

    gomonkey 在测试目录增加了测试文件 apply_private_method_test.go 来引导读者写 private method 的测试。

    测试代码:

    func TestApplyPrivateMethod(t *testing.T) {
        Convey("TestApplyPrivateMethod", t, func() {
            Convey("patch private pointer method in the different package", func() {
                f := new(fake.PrivateMethodStruct)
                var s *fake.PrivateMethodStruct
                patches := ApplyPrivateMethod(s, "ok", func(_ *fake.PrivateMethodStruct) bool {
                    return false
                })
                defer patches.Reset()
                result := f.Happy()
                So(result, ShouldEqual, "unhappy")
            })
    
            Convey("patch private value method in the different package", func() {
                s := fake.PrivateMethodStruct{}
                patches := ApplyPrivateMethod(s, "haveEaten", func(_ fake.PrivateMethodStruct) bool {
                    return false
                })
                defer patches.Reset()
                result := s.AreYouHungry()
                So(result, ShouldEqual, "I am hungry")
            })
        })
    
    }
    

    模拟的产品代码:

    type PrivateMethodStruct struct {
    }
    
    func (this *PrivateMethodStruct) ok() bool {
        return this != nil
    }
    
    func (this *PrivateMethodStruct) Happy() string {
        if this.ok() {
            return "happy"
        }
        return "unhappy"
    }
    
    func (this PrivateMethodStruct) haveEaten() bool {
        return this != PrivateMethodStruct{}
    }
    
    func (this PrivateMethodStruct) AreYouHungry() string {
        if this.haveEaten() {
            return "I am full"
        }
    
        return "I am hungry"
    }
    

    运行测试:

    zhangxiaolongdeMacBook-Pro:test zhangxiaolong$ go test -gcflags=all=-l apply_private_method_test.go -v
    === RUN   TestApplyPrivateMethod
    
      TestApplyPrivateMethod 
        patch private pointer method in the different package ✔
        patch private value method in the different package ✔
    
    
    2 total assertions
    
    --- PASS: TestApplyPrivateMethod (0.00s)
    PASS
    ok      command-line-arguments  
    

    新版本说明

    作者在 github 上发布了 gomonkey 的新版本 v2.7.0 来完整支持该特性:


    gomonkey v2.7.0.png

    如何获取 gomonkey 新版本?
    假设你使用 go get 命令来获取 gomonkey v2.7.0:

    $ go get github.com/agiledragon/gomonkey/v2@v2.7.0
    

    如何导入 gomonkey 新版本?

    import (
       "encoding/json"
       "testing"
    
       . "github.com/agiledragon/gomonkey/v2"
       . "github.com/smartystreets/goconvey/convey"
    )
    

    小结

    gomonkey 穿越 reflect 包的限制,终于支持为 private method 打桩的特性了!该特性高效解决了诸多 gopher 这些年写测试在某些场景下打桩繁杂的困扰,使得 gomonkey 打桩更贴心,让开发者自测更美好!

    gomonkey 专门为用户提供了新接口来使用 private method 打桩特性,从而将为 gomonkey 私人定制的反射包 creflect 的影响尽可能的局部化。

    关于支持 private method sequence 的特性,暂不考虑开发计划。

    相关文章

      网友评论

          本文标题:gomonkey支持为private method打桩了

          本文链接:https://www.haomeiwen.com/subject/xfxooltx.html