测试驱动开发初体验

1 TDD 的价值

我们对新增代码的单元测试覆盖率有明确要求,不达标的代码是不能进主分支的。因此我们需要专门花时间写单元测试。

正常情况,我们是先写功能代码,最后补单元测试。很容易遇到如下问题:

  • 已经写好的功能代码,耦合度较高,写 UT 很艰难。
  • 写 UT 耗时长,易出错,基本离不开 Powermock;
  • 到达一定水平后(例如 60%),UT 覆盖率很难再往上提。

如果采用 TDD 的方式,有以下优点:

  • 第一次写出的功能代码,就是“易于测试”的代码,耦合度较低,UT 写起来方便快捷,较少用到 Powermock;
  • 强迫自己先想清楚需求(接口应该实现什么功能),再编码实现,减少返工几率;
  • UT 覆盖率很高,一次性达到要求,不需要后期花时间补单元测试;
  • 为以后做重构提供可靠的保障。

2 什么是 TDD

TDD,即测试驱动开发。是敏捷开发在编码层面的一种具体实践。

2.1 澄清一些常见误解

误解 1:TDD 的重点在于写单元测试

TDD 是一种开发技术,而不是一种测试技术。

误解 2:TDD 很费时间,是一种高质量但低效的实践

如果 TDD 很费时间,实战价值不大,它就不应该是一种行之有效的敏捷方法。在 UT 覆盖率必须达标的前提下,TDD 比常规的先开发后写 UT 的方式更快。

至于开发效率,有很多影响因素,从之后的例子中可以看出,凭借对工具和方法的熟练使用,TDD 也可以很快。

误解 3:TDD 需要先把测试类写完整,才能写功能代码

这是典型的瀑布思维:先定义完整的需求,然后一次性实现。

实际的 TDD 不是这样的。先写一小部分测试代码,然后写功能代码。可以 work 之后,接着写测试代码。小步快跑。具体用法后面详细介绍。

误解 4:可以先写一个功能类,再写一个测试类;或者先写一半功能类,再写一半测试类

虽然也是小步快跑,但不是测试驱动。

2.2 TDD 的节奏

  1. 写一个失败的测试;
  2. 写最少的功能代码,让测试通过;
  3. 重构,消除重复代码,改进设计;
  4. 再写一个失败的测试,循环往复。

理论上,每一次小型增量迭代结束后,都得到一个可以 work 的,覆盖率 100% 的系统。

2.3 TDD 的原则

  1. 没有失败的测试就不能写代码;
  2. 只写恰好让测试通过的代码(在不超过1分钟的时间内,让一个失败的测试通过)。

3 TDD 实战体验:FizzBuzz

下面,通过一个实际的例子,来感受一下 TDD 的运用方式。

3.1 FizzBuzz 开发需求

题目描述

写一个程序,打印出从1到100的数字,将其中3的倍数替换成“Fizz”,5的倍数替换成“Buzz”。既能被3整除、又能被5整除的数则替换成“FizzBuzz”。

样例输出:

验收要求

  • 10min 内完成功能(有一些同学在长期练习后可以做到 2 分钟内完成)
  • UT 覆盖率 100%
  • 代码尽量符合规范,没有重复代码

乍一看,这个需求很简单。但是考虑到 UT 和代码质量,就变得很难。

3.2 实操

我们可以打开计时器,正儿八经地做一下这道题目。看能做到什么程度,过程中有没有磕磕碰碰。

(此处省略一万字)

3.3 课代表示范