简介
Go 语言在设计之初就考虑到了代码的可测试性。一方面 Go 本身提供了 testing 库,使用方法很简单;
另一方面 go 的 package 提供了很多编译选项,代码和业务逻辑代码很容易解耦,可读性比较强(不妨对比一下C++测试框架)。 本文中,我们讨论的重点是 Go 语言中
的单元测试,而且只讨论一些基本的测试方法,包括下面几个方面:
写一个简单的测试用例
Table driven test
使用辅助测试函数(test helper)
临时文件
这里我们只涉及到一些通用的测试方法。关于 HTTP server/client 测试,这里不做深入讨论。
阅读建议
Testing shows the presence, not the absence of bugs – Edsger W. Dijkstra
在阅读本文之前,建议您对 Go 语言的 package 有一定的了解,并在实际项目中使用过,下面是一些基本的要求:
了解如何在项目中 import 一个外部的package
了解如何将自己的项目按照功能模块划分 package
了解 struct、struct字段、函数、变量名字首字母大小写的含义(非必需)
了解一些 Go语言的编译选项,比如 +build !windows(非必需)
如果你对 1、2都不太了解,建议阅读一下这篇文章How to Write Go Code,动手实践一下。
写一个简单的测试用例
为了便于理解,我们首先给出一个代码片段(如果你已经使用过go 的单元测试,可以跳过这个环节):
|
|
上面这个例子中,如果你从来没有使用过单元测试,建议在本地开发环境中运行一次。这里有几点需要注意一下:
这两个文件的父目录必须与包名一致(这里是 demo),且包名必须是在 \$GOPATH 下
测试用例的函数命名必须符合 TestXXX 格式,并且参数是 t *testing.T
了解一下 t.Errorf 与 t.Fatalf 的行为差异
Table Driven Test
上面的测试用例中,我们一次只能测试一种情况,如果我们希望在一个 TestXXX 函数中进行很多项测试,Table Driven Test 就派上了用场。
举个例子,假设我们实现了自己的 Sqrt 函数 mymath.Sqrt,我们需要对其进行测试:
首先,我们需要考虑一些特殊情况:
Sqrt(+Inf) = +Inf
Sqrt(±0) = ±0
Sqrt(x < 0) = NaN
Sqrt(NaN) = NaN
然后,我们需要考虑一般情况:
Sqrt(1.0) = 1.0
Sqrt(4.0) = 2.0
…
注意:在一般情况中,我们对结果进行验证时,需要考虑小数点精确位数的问题。由于文章篇幅限制,这里不做额外的处理。
有了思路以后,我们可以基于 Table Driven Test 实现测试用例:
|
|
辅助函数 (test helper)
在写测试的过程中,我们可能遇到下面几个场景:
待测试的功能需要一些前提条件,比如初始化数据库连接、打开文件、创建资源
核心功能测试结束后,需要一些清理工作,比如关闭文件、销毁资源
待测试的功能错误分类比较多,考虑到table driven test,写到一个测试函数里可读性比较差
这时候,我们需要定义一些辅助函数,以协助核心功能的测试。下面我们以用户登录校验为例,来看如何使用辅助函数。
我们要测试的函数是 login,为了保证本次单元测试不会污染数据库,我们采取的流程是:
初始化数据库连接(类似于 Junit 中的 @Before)
创建一个用户 (类似于 Junit 中的 @Before)
测试 login
删除该用户(类似于 Junit 中的 @After)
确定了测试的逻辑以后,我们看下代码:
|
|
在测试代码中,我们推荐使用 t.Fatalf , 而不是 t.Errorf,一方面测试代码不需要做太多容错,另一方面增加了测试代码的可读性。
临时文件
如果待测试的功能模块涉及到文件操作,临时文件是一个不错的解决方案。go语言的 ioutil 包提供了 TempDir 和
TempFile 方法,供我们使用。
我们以 etcd 创建 wal 文件为例,来看一下 TempDir 的用法:
|
|
上面这段代码是从 etcd 中摘取出来的,源码查看 coreos/etcd - Github。
需要注意的是,使用 TempDir 和 TempFile 创建文件以后,需要自己去删除。
关于 package
在写单元测试时,一般情况下,我们将功能代码和测试代码放到同一个目录下,仅以后缀 _test 进行区分。
对于复杂的大型项目,功能依赖比较多时,通常在跟目录下再增加一个 test 文件夹,不同的测试
放到不同的子目录下面,如下图所示:
针对自己的项目进行测试时,可以结合这两种方式实现测试用例,提高代码的可读性和可维护性。
相关链接:
扫码关注微信公众号“深入Go语言”