原文 A pattern for Go tests

我曾经花费了无数时间来思考到底该如何编写单元测试。

我去搜索了Go语言的单元测试模式

很多人通过外部依赖来使用断言。事实上,我认为类似 isNil(v interface{}) bool 这样的范型函数最开始确实能够提升开发速度,但是长远来看,我想如果 接受Go的强类型天性 而不是只想着回避它,会更加有意义。编写地道的代码对于提升质量和理解代码都有好处。

然后,我去向Go的核心库寻求答案

当我明白我需要从哪寻找答案的时候,我注意到了一个地方。

Go的核心库里面有一个专门为了测试目的的包(package): [net/http/httptest][2] 。这个包一定会有好的测试方式。

我找到了什么

Brad Fitzpatrick的代码,还能有什么。

这里是一个稍微修改过的 recorder_test.go 的版本:

这是怎么一回事?

这个测试函数包含三个部分。

第一部分(2–28行):匹配器(matcher)

代码第一行定义了一个函数类型: checkFunc 。这个函数签名有一个参数接收我们需要测试的数据。 checkFunc 的参数应该包括目标函数的所有返回值。在这里,我们测试一个ResponseRecorder包含的方法,需要测试的状态都在ResponseRecorder里面,它作为 checkFunc 的唯一一个参数。

匹配器函数是闭包: 包含了期待值。如果期待值与实际结果不匹配则 checkFunc 会返回错误。

第二部分(30–60行):测试用例

使用一个匿名的 结构体(struct) 存放测试数据。所有的测试都被定义为:

  • 对用例的描述
  • 输入
  • 一个 checkFunc 的切片用来承载期望值

第三部分(62–74行):测试逻辑

这是我们要亲自去做的部分,使用测试用例的数据来 checkFunc 切片:如果有错误返回,就直接跳到 t.Error()

TDD还是BDD,你自己决定

如果更喜欢 表格驱动测试(table-driven tests) ,那每一个测试里面可能要使用不同值重复定义匹配器,用用例的名称来描述目标函数的测试场景。

对于偏向 BDD 的开发人员,测试用例的名称会描述作为输出的期望值,而且并不是每个匹配器都会被每一个用例使用。

或者如果你像我这样 不希望命令行界面被测试淹没 的话,可以自行按需搭配使用!