Go单元测试工具gomonkey的使用

2022-06-23 11:57:13
目录
Go 单元测试工具单测Go 单元测试工具gomonkeygomonkey 打桩失败的可能原因goconvey为全局变量打一个桩为一个函数打桩什么是内联?

Go>

测试分为4个层次

    单元测试:对代码进行测试集成测试:对一个服务的接口测试端到端测试(链路测试):从一个链路的入口输入测试用例,验证输出的系统的结果UI测试

    常犯的错误:

      没有断言。没有断言的单测是没有灵魂的。

      单测的特征:

        A:(Automatic,自动化):单元测试应该是全自动执行的,并且非交互式的I:(Independent,独立性):为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。R:(Repeatable,可重复):单元测试通常会被放到持续集成中,每次有代码 check in 时单元测试都会被执行。

        单测

        代码>

          提高代码质量

          编写单测是自测的一部分,编写新代码时增加相应的单测,可以帮助我们发现大部分的bug,有助于减少联调时的调整,提高联调效率。

            花更少的时间进行功能测试

            功能测试成本相对较高,因为经常需要执行一系列操作以验证结果是否符合预期。如果问题如果发现了问题,沟通和复测往往要花费很多的时间。

              花更少的时间进行回归测试

              回归测试是为了避免在对应用程序进行更改时引入bug。测试人员不仅要测试他们的新特性,还要测试以前存在的特性,以验证之前实现的特性是否仍然像预期的那样运行。 通过单元测试,可以在每次构建之后,重新运行整个测试流程,以确保新代码不会破坏已有功能

                测试异常场景

                一些异常的场景QA不好构造,比如并发出款是否资金安全,事务异常相关测试等等。而问题经常出现在这些异常的场景,可能引发线上问题甚至是事故。 而单元测试可通过mock的方式方便的模拟各种异常场景。

                Go>

                gomonkey

                引入>

                  隔离被测代码加速执行测试使执行变得确定模拟特殊情况

                  功能列表

                    支持为一个函数打一个桩支持为一个函数打一个特定的桩序列支持为一个成员方法打一个桩支持为一个成员方法打一个特定的桩序列支持为一个函数变量打一个桩支持为一个函数变量打一个特定的桩序列支持为一个接口打桩支持为一个接口打一个特定的桩序列支持为一个全局变量打一个桩

                    函数打桩, 对变量的 mock 实现原理跟 gostub 一样都是通过 reflect 包实现的。除了 mock 变量,gomonkey 还可以直接 mock 导出函数/方法、mock 代码所在包的非导出函数

                    Go monkey Permission Denied 解决方案:https://github.com/eisenxp/macos-golink-wrapper

                    mv $GOROOT/pkg/tool/darwin_amd64/link $GOROOT/pkg/tool/darwin_amd64/original_link
                    cp https://github.com/eisenxp/macos-golink-wrapper/link $GOROOT/pkg/tool/darwin_amd64/link
                    

                    下载文件,然后再 cp

                    wget https://raw.githubusercontent.com/eisenxp/macos-golink-wrapper/main/link  
                    

                    gomonkey 提供了如下 mock 方法:

                      ApplyGlobalVar(target, double interface{}):使用 reflect 包,将 target 的值修改为 doubleApplyFuncVar(target, double interface{}):检查 target 是否为指针类型,与 double 函数声明是否相同,最后调用 ApplyGlobalVarApplyFunc(target, double interface{}):修改 target 的机器指令,跳转到 double 执行ApplyMethod(target reflect.Type, methodName string, double interface{}):修改 method 的机器指令,跳转到 double 执行ApplyFuncSeq(target interface{}, outputs []OutputCell):修改 target 的机器指令,跳转到 gomonkey 生成的一个函数执行,每次调用会顺序从 outputs 取出一个值返回ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell):修改 target 的机器指令,跳转到 gomonkey 生成的一个方法执行,每次调用会顺序从 outputs 取出一个值返回ApplyFuncVarSeq(target interface{}, outputs []OutputCell):gomonkey 生成一个函数顺序返回 outputs 中的值,调用 ApplyGlobalVar

                      gomonkey>
                        gomonkey 不是并发安全的。如果有多协程并发对同一个目标的打桩的情况,则需要将之前的协程先优雅退出。打桩目标为内联的函数或成员方法。可通过命令行参数 -gcflags=-l (go1.10 版本之前)或-gcflags=all=-l(go1.10 版本及之后)关闭内联优化。gomonkey 对于私有成员方法的打桩失败。go1.6 版本的反射机制支持私有成员方法的查询,而 go1.7 及之后的版本却不支持,所以当用户使用 go1.7 及之后的版本时,gomonkey 对于私有成员方法的打桩会触发异常。

                        goconvey

                        为全局变量打一个桩

                        package unittest
                        
                        import (
                        	"testing"
                        
                        	"github.com/agiledragon/gomonkey"
                        	"github.com/smartystreets/goconvey/convey"
                        )
                        
                        var num = 10 //全局变量
                        
                        func TestApplyGlobalVar(t *testing.T) {
                        	convey.Convey("TestApplyGlobalVar", t, func() {
                        		convey.Convey("change", func() {
                        			patches := gomonkey.ApplyGlobalVar(&num, 150)
                        			defer patches.Reset()
                        			convey.So(num, convey.ShouldEqual, 150)
                        		})
                        
                        		convey.Convey("recover", func() {
                        			convey.So(num, convey.ShouldEqual, 10)
                        		})
                        	})
                        }
                        

                        执行结果:

                        === RUN   TestApplyGlobalVar
                        ..
                        2 total assertions

                        --- PASS: TestApplyGlobalVar (0.00s)
                        PASS

                        为一个函数打桩

                        func networkCompute(a, b int) (int, error) {
                        	// do something in remote computer
                        	c := a + b
                        
                        	return c, nil
                        }
                        
                        func Compute(a, b int) (int, error) {
                        	sum, err := networkCompute(a, b)
                        	return sum, err
                        }
                        
                        func TestFunc(t *testing.T) {
                        	// mock 了 networkCompute(),返回了计算结果2
                        	patches := gomonkey.ApplyFunc(networkCompute, func(a, b int) (int, error) {
                        		return 2, nil
                        	})
                        
                        	defer patches.Reset()
                        
                        	sum, err := Compute(1, 2)
                        	println("expected %v, got %v", 2, sum)
                        	if sum != 2 || err != nil {
                        		t.Errorf("expected %v, got %v", 2, sum)
                        	}
                        }
                        

                        结果:

                        === RUN   TestFunc
                        expected %v, got %v 2 3
                            mock_func_test.go:91: expected 2, got 3
                        --- FAIL: TestFunc (0.00s)

                        FAIL

                        可以看到上面的结果,执行时失败的,mock 没有成功。

                        有时会遇到mock失效的情况,这个问题一般是内联导致的。

                        什么是内联?

                        为了减少函数调用时的堆栈等开销,对于简短的函数,会在编译时,直接内嵌调用的代码。

                        我们禁用下内联,然后执行,>

                        执行结果:

                        === RUN   TestFunc
                        expected %v, got %v 2 2
                        --- PASS: TestFunc (0.00s)
                        PASS

                        对于 go 1.10以下版本,可使用-gcflags=-l禁用内联,对于go 1.10及以上版本,可以使用-gcflags=all=-l。但目前使用下来,都可以。 关于gcflags的用法,可以使用 go tool compile --help 查看 gcflags 各参数含义

                        到此这篇关于Go单元测试工具gomonkey的使用的文章就介绍到这了,更多相关Go gomonkey内容请搜索易采站长站以前的文章或继续浏览下面的相关文章希望大家以后多多支持易采站长站!