Go单元测试工具gomonkey的使用

目录
  • Go 单元测试工具
    • 单测
  • Go 单元测试工具
    • gomonkey
      • gomonkey 打桩失败的可能原因
    • goconvey
      • 为全局变量打一个桩
    • 为一个函数打桩
      • 什么是内联?

Go 单元测试工具

测试分为4个层次

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

常犯的错误:

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

单测的特征:

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

单测

代码 bug 总是在所难免, 越早发现问题解决成本越低, 单测可以尽早的暴露错误。提高代码之路,使得项目更高质量的交付。 起码有三个优点:

  • 提高代码质量

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

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

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

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

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

  • 测试异常场景

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

Go 单元测试工具

gomonkey

引入 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 的值修改为 double
  • ApplyFuncVar(target, double interface{}):检查 target 是否为指针类型,与 double 函数声明是否相同,最后调用 ApplyGlobalVar
  • ApplyFunc(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失效的情况,这个问题一般是内联导致的。

什么是内联?

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

我们禁用下内联,然后执行, go test -v -gcflags=-l mock_func_test.go

执行结果:

=== 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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-06-23

使用 gomonkey Mock 函数及方法示例详解

目录 前言 函数 方法 参考 前言 在 Golang 语言中,写单元测试的时候,不可避免的会涉及到对其他函数及方法的 Mock,即在假设其他函数及方法响应预期结果的同时,校验被测函数的响应是否符合预期. 其中,在 Mock 其他函数及方法的时候,我们常用到的一个测试类库是「gomonkey」.特别地,对于方法和函数的 Mock,略有差异,在这里我们就分别给出函数和方法 Mock 示例,方便大家参考. 函数 在 Golang 语言中,函数是没有接受者的方法,其形式为 func function_n

Go语言中的字符串处理方法示例详解

1 概述 字符串,string,一串固定长度的字符连接起来的字符集合.Go语言的字符串是使用UTF-8编码的.UTF-8是Unicode的实现方式之一. Go语言原生支持字符串.使用双引号("")或反引号(``)定义. 双引号:"", 用于单行字符串. 反引号:``,用于定义多行字符串,内部会原样解析. 示例: // 单行 "心有猛虎,细嗅蔷薇" // 多行 ` 大风歌 大风起兮云飞扬. 威加海内兮归故乡. 安得猛士兮守四方! ` 字符串支持转义

编译安装redisd的方法示例详解

安装方法: yum安装 查看yum仓库redis版本 [root@centos ~]# yum list redis Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile Available Packages redis.x86_64 3.2.12-2.el7 myepel yum安装 [root@centos ~]# yum install redis -y 启动服务并设为开机启动

VSCode各语言运行环境配置方法示例详解

系统环境变量的配置 如:将F:\mingw64\bin添加到系统环境变量Path中 VSCode软件语言json配置C语言 创建个.vscode文件夹,文件夹内创建以下两个文件 launch.json 文件配置 { "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg&

浅谈使用Java Web获取客户端真实IP的方法示例详解

Java-Web获取客户端真实IP: 发生的场景:服务器端接收客户端请求的时候,一般需要进行签名验证,客户端IP限定等情况,在进行客户端IP限定的时候,需要首先获取该真实的IP. 一般分为两种情况: 方式一.客户端未经过代理,直接访问服务器端(nginx,squid,haproxy): 方式二.客户端通过多级代理,最终到达服务器端(nginx,squid,haproxy): 客户端请求信息都包含在HttpServletRequest中,可以通过方法getRemoteAddr()获得该客户端IP.

PHP日期和时间函数的使用示例详解

(1)查看日期函数拓展 phpinfo() <?php phpinfo(); 打开上述页面之后,可以看到以下,证明已经安装日期拓展 浏览器输入php.net访问php的文档 (2)设置时区 方式一:修改配置文件php.ini D:\ITSoft\wamp64\bin\php\php7.0.10\php.ini [Date] ; Defines the default timezone used by the date functions ; http://php.net/date.timezon

PHP基于phpqrcode类生成二维码的方法示例详解

HP QR Code是一个PHP二维码生成类库,利用它可以轻松生成二维码,官网提供了下载和多个演示demo,查看地址: http://phpqrcode.sourceforge.net/ 下载官网提供的类库后,只需要使用phpqrcode.php就可以生成二维码了,当然您的PHP环境必须开启支持GD2. phpqrcode.php提供了一个关键的png()方法,其中 参数$text表示生成二位的的信息文本: 参数$outfile表示是否输出二维码图片 文件,默认否: 参数$level表示容错率,

golang 函数以及函数和方法的详解及区别

golang 函数以及函数和方法的区别 在接触到go之前,我认为函数和方法只是同一个东西的两个名字而已(在我熟悉的c/c++,python,java中没有明显的区别),但是在golang中者完全是两个不同的东西.官方的解释是,方法是包含了接收者的函数.到底什么意思呢. 首先函数的格式是固定的,func+函数名+ 参数 + 返回值(可选) + 函数体.例 func main() { fmt.Println("Hello go") } 在golang中有两个特殊的函数,main函数和ini

小程序微信支付功能配置方法示例详解【基于thinkPHP】

本文实例讲述了小程序微信支付功能配置方法.分享给大家供大家参考,具体如下: ★ 背景 近期进行小程序的开发,毕竟是商城项目的开发,最后牵扯到的微信支付是必要的 个人开发过程中也是遇到各种问题,在此,我根据自己的实际操作,进行了代码的详细配置,以方便小程序新手的快速操作 -  使用语言:PHP             # PHP世界上最好的语言 HaHahahaaha -  使用框架:ThinkPHP 3.2    # 版本有点低而已,没啥大碍 -  测试工具:微信开发者工具    # 其实还挺好

JavaScript函数定义方法实例详解

本文实例讲述了JavaScript函数定义方法.分享给大家供大家参考,具体如下: JavaScript 函数定义方法 函数声明 在之前的教程中,你已经了解了函数声明的语法 : function functionName(parameters) { 执行的代码 } 函数声明后不会立即执行,会在我们需要的时候调用到. function myFunction(a, b) { return a * b; } 分号是用来分隔可执行JavaScript语句. 由于函数声明不是一个可执行语句,所以不以分号结束

Python正确重载运算符的方法示例详解

前言 说到运算符重载相信大家都不陌生,运算符重载的作用是让用户定义的对象使用中缀运算符(如 + 和 |)或一元运算符(如 - 和 ~).说得宽泛一些,在 Python 中,函数调用(()).属性访问(.)和元素访问 / 切片([])也是运算符. 我们为 Vector 类简略实现了几个运算符.__add__ 和 __mul__ 方法是为了展示如何使用特殊方法重载运算符,不过有些小问题被我们忽视了.此外,我们定义的Vector2d.__eq__ 方法认为 Vector(3, 4) == [3, 4]