以前经常看到现东家标榜自己在实践TDD(测试驱动开发,Test Driven Development)。没想到,前几天,我们团队竟然会讨论TDD的优势在哪里。联想到面试时,TDD被面试官diss了一顿。看来我司对TDD的接纳度并没有想象中的高。

恰好,在入职我司之前,我曾有幸在一个团队中真正实践TDD。尽管受益良多,但从未真正做过总结。在讨论会上,刚刚听到这个问题。竟一时梳理不出:TDD的好处里,哪些是测试带来的好处,哪些是“测试先行”带来的好处。

现总结一二,留在这里。

拆分任务,测试用例就是开发计划

TDD,测试驱动开发。顾名思义,开发总是从编写测试开始。

实际上,在开发人员接到开发任务的时候。任务功能往往是复杂的。这时,开发人员可以将一个大的任务拆分为若干小的开发任务。

当拆分任务的时候,开发人员可以直接使用测试用例,清晰的描述开发任务目标。而这个过程,往往也是一个将任务需求从文字描述确认为代码的一个很有效的手段。

很多时候,我们有可能会将类似的任务子项,写在某个记事本里,或着git commit message里面,而这个message将来大概率会在rebase的时候修改掉。

写成一组测试用例,把任务需求持久化在git history里面可以更方便的回顾。

明确的测试意图,保证了单一职能原则

在软件开发的要求中,一个函数或类的作用必须是尽可能单一的。然而,在日常开发中,很容易会因为投机取巧,扩充一个已有实现的功能,增加了后期重构的工作量。

而在TDD中,我们会首先编写测试用例。在这时候,开发人员就能够主动思考是否需要将新功能写成一个新的函数,甚至是需要重构类的组织。

自外至内的思考,让代码符合依赖倒置原则

按照TDD的要求,我们总是从外向内的思考代码的实现。也就是说,只有在写当前层面的代码时,我们才会去思考我们需要什么样的内层结构来支撑当前层面的代码。

在这种场景下:

  1. 代码的语义更符合当前层面的场景,可读性更好;
  2. 设计出来的接口和实现,更符合依赖倒置原则,也很容易满足《代码整洁之道》里的“整洁的边界”。

全力保证测试通过,更小的开发context

在TDD中,代码的组织方式在编写测试用例的时候就已经思考过了。所以在开发功能的时候,开发人员没有太多的顾虑,只需要全力保证测试用例通过。

如果遇到有某件事情打扰到自己,那么也可以立即停下来。因为下次要恢复开发是,只需要编译一次代码就可以了。因为实际意义上讲,我们的唯一需要做的便是,让代码编译通过。

想想以前,每次下班的时候,都要花几分钟,找个地方专门写一下自己当前的开发进度。在实践TDD的时候,完全没有这份顾虑,因为:

  1. 当前我在做的任务肯定测试不通过。
  2. 我做好的任务,测试已经通过了。
  3. 将来我要做的任务,有测试用例等着我通过。

所有的进度都在测试用例里面,合起电脑盖子就可以下班了。

由红至绿,更可靠的单元测试用例

在TDD中,每一个功能背后都有一个测试用例来支撑。所以代码的测试覆盖率更高。

同时,每一个测试用例都经历从“不通过”到“通过”,这条测试的有效性是毋庸置疑的。

在后期补充的测试用例中,往往会发现某些用例通过了,但是代码依然在线上出现bug。

亡羊补牢,低效的补充测试用例

在开发后补充测试用例,往往会落入一个陷阱:盲信开发代码的行为,让测试用例符合开发代码的行为。

这种错误的思维方式很难在code review、code diff等敏捷实践中发现。因为在开发工作完成后,测试用例只成了一个锦上添花的东西。没有人会真正的在意。

相反在TDD中,测试用例处于很重要的位置。测试用例的意图,决定了后期开发代码的行为。因此,对测试用例的code review就很重要。

如果要在后期补充测试用例时,避免测试用例去迎合开发代码,那么需要独立的思考函数的行为应该是什么样的。而这个思考过程实际上在开发代码的时候已经进行过一次了。

在TDD中,这个思考过程只需要在编写测试用例的时候进行一次。在开发过程中,只需要集中精力让测试用例通过就行了。显然,心智负担更小。

TDD应该是一个团队的选择

尽管良好的敏捷实践和开发思维也可以做到上述几点,但是TDD能够从更客观的角度主动的避免上述问题,能够帮助一个开发水平较低的人快速提高开发水平。这对一个成员水平层次不齐的团队有很大的诱惑力。

毕竟,写代码,从来都不是单打独斗。

以上,便是一个代码弱鸡实践三个月TDD之后的感想。

延伸阅读