单元测试(unit test) | 青训营

91 阅读3分钟

单元测试(unit test)除用来测试逻辑算法是否符合预期外,还承担着监控代码质量的责任。任何时候都可用简单的命令来验证全部功能,找出未完成任务(验收)和任何因修改而造成的错误。它与性能测试、代码覆盖率等一起保障了代码总是在可控范围内,这远比形式化的人工检查要有用得多。

单元测试并非要取代人工代码审查(code review),实际上它也无法切入到代码实现层面。但可通过测试结果为审查提供筛选依据,避免因烦琐导致代码审查沦为形式主义。单元测试可自动化进行,能持之以恒。但测试毕竟只是手段,而非目的,所以如何合理安排测试就需要开发人员因地制宜。

可将测试、版本管理工具,以及自动发布(nightly build)整合。编写脚本将测试失败结果与代码提交日志相匹配,最终生成报告发往指定邮箱。

很多人认为单元测试代码不好写,不知道怎么测试。如果非技术原因,那么需要考虑结构设计是否合理,因为可测试性也是代码质量的一个体现。

在我看来,写单元测试本身就是对即将要实现的算法做复核预演。因为无论什么算法都需要输入条件,返回预期结果。这些,加上平时写在main里面的临时代码,本就是一个完整的单元测试用例,无非换个地方存放而已。

testing

工具链和标准库自带单元测试框架,这让测试工作变得相对容易。

  • 测试代码须放在当前包以“_test.go”结尾的文件中。
  • 测试函数以Test为名称前缀。
  • 测试命令(go test)忽略以“_”或“.”开头的测试文件。
  • 正常编译操作(go build/install)会忽略测试文件。

main_test.go

package main
  
import( 
    "testing" 
) 
  
func add(x,y int)int{ 
   return x+y
} 
  
func TestAdd(t*testing.T) { 
   if add(1,2) !=3{ 
       t.FailNow() 
    } 
}

输出:

$go test-v              # 要测试当前包及所有子包,可用go test./... 
  
===RUN TestAdd
---PASS:TestAdd(0.00s) 
PASS
ok    test  0.006s

标准库testing提供了专用类型T来控制测试结果和行为。

 方法          说明             相关 
------------------+-----------------------------+----------------- 
Fail        失败:继续执行当前测试函数 
FailNow      失败:立即终止执行当前测试函数    Failed
SkipNow      跳过:停止执行当前测试函数      Skip,Skipf,Skipped
Log         输出错误信息。仅失败或 -v时输出  Logf
Parallel     与有同样设置的测试函数并行执行 
Error  Fail+Log         Errorf
Fatal  FailNow+Log       Fatalf

使用Parallel可有效利用多核并行优势,缩短测试时间。

func TestA(t*testing.T) { 
   t.Parallel() 
   time.Sleep(time.Second*2) 
} 
  
func TestB(t*testing.T) { 
   if os.Args[len(os.Args)-1] == "b" { 
       t.Parallel() 
    } 
  
   time.Sleep(time.Second*2) 
}

输出:

$go test-v
  
---PASS:TestB(2.00s) 
---PASS:TestA(2.00s) 
PASS
ok    test  4.014s
  
  
$go test-v-args"b" 
  
---PASS:TestA(2.00s) 
---PASS:TestB(2.00s) 
PASS
ok    test  2.009s

从测试总耗时可以看到并行执行的结果只有2秒。

只有一个测试函数调用Parallel方法并没有效果,且go test执行参数parallel必须大于1。

常用测试参数:

 参数        说明                示例 
------------+-----------------------------------+------------------------ 
 -args  命令行参数 
 -v        输出详细信息 
 -parallel  并发执行,默认值为GOMAXPROCS     -parallel 2
 -run       指定测试函数,正则表达式           -run"Add" 
 -timeout   全部测试累计时间超时将引发panic,默认值为10ms  -timeout 1m30s       
 -count     重复测试次数,默认值为1

对于测试是否应该和目标放在同一目录,一直有不同看法。某些人认为应该另建一个专门的包用来存放单元测试,且只测试目标公开接口。好处是,当目标内部发生变化时,无须同步维护测试代码。每个人对于测试都有不同理解,就像覆盖率是否要做到90%以上,也是见仁见智。