在工作中我经常会发现很多工程师的 Golang 单测是写的有问题的,只是单纯的调用代码做输出,并且会包含各种 IO 操作,导致单测无法到处运行。
本文将介绍 Golang 中如何正确的做单测。
什么是单元测试?单元测试的特点
单元测试是质量保证十分重要的一环,好的单元测试不仅能及时地发现问题,更能够方便地调试,提高生产效率,所以很多人认为写单元测试是需要额外的时间,会降低生产效率,是对单元测试最大的偏见和误解。
单测会将对应测试的模块隔离出来进行测试,所以我们要尽可能把所有相关的外部依赖都移除,只对相关的模块进行单测。
所以大家看到的在业务代码仓库中的一些在 client 模块中调用 HTTP 的单测其实是不规范的,因为 HTTP 是外部依赖,你的目标服务器如果有故障,那么你的单测就会失败。
func Test_xxx(t *testing.T) {
DemoClient := &demo.DemoClient{url: "http://localhost:8080"}
DemoClient.Init()
resp := DemoCliten.DoHTTPReq()
fmt.Println(resp)
}
上面这个例子中 DemoClient
在做 DoHTTPReq
方法的时候,会调用 http.Get
方法,这个方法是中包含了外部依赖也就是说他会去请求本地的服务器,如果有一个新的同事刚拿到你的代码,并且本地是没有这个服务器的话,那么你的单测就会失败。
并且上面这个例子中,对于 DoHTTPReq
这个函数只是简单的做了一个输出,没有对返回值做任何检查,如果内部逻辑修改并且返回值修改了,虽然你的测试还是能够 pass
但是其实你的单测是不起作用的。
从上面的例子中我们可以总结出两个单测特点:
- 没有外部依赖,尽量无副作用,能够到处运行
- 对输出进行检查
此外我还想提及的一点就是对于单测的编写难度其实是有排序的:
UI > Service > Utils
所以对于单测的编写,我们会优先考虑对 Utils 的单测, 因为 Utils 不会有太多的依赖。其次是对于 Service 的单测,因为 Service 的单测主要是对上游服务和数据库的依赖,只需要分离出依赖就可以进行逻辑的测试。
那该如何分离出依赖呢?我们走到下一节,我们将会介绍如何分离出依赖。
什么是 Mock?
对于 IO 的依赖,我们可以使用 Mock 来模拟数据,这样我们就可以不用担心数据源不稳定的问题了。
那么什么是 Mock 呢?我们又该如何 Mock 呢? 可以想想一个场景,就是你和你的同事正在进行合作的项目开发,你这边的进展比较快,已经快完成你的开发了,但是你的同事进展稍慢,并且你还依赖他的服务。你要怎么不 bl