一、TDD是什么
测试驱动开发(Test-Driven Development,TDD)是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,但不只适用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。TDD的基本思路就是通过测试来推动整个开发的进行,但测试驱动开发并不只是单纯的测试工作,而是把需求分析,设计,质量控制量化的过程。TDD的重要目的不仅仅是测试软件,测试工作保证代码质量仅仅是其中一部分,而且是在开发过程中帮助客户和程序员去除模棱两可的需求。TDD首先考虑使用需求(对象、功能、过程、接口等),主要是编写测试用例框架对功能的过程和接口进行设计,而测试框架可以持续进行验证。“测试驱动开发不是一种测试技术。它是一种分析技术、设计技术,更是一种组织所有开发活动的技术”。 ——Kent Beck(TDD之父)
二、TDD需要考虑什么
TDD需要考虑以下几点:2.1 开发人员的开发意识开发人员对于软件质量,常常偏重于软件的外部质量,体现在他们的工作KPI设置,就是被测试人员发现的缺陷数。开发人员不适合做测试思想作怪,从而可能忽略真正需要测试的用例。开发人员编写测试带来的收益,最重要的一点不在于测试本身,而在于它能促进开发、测试以及需求分析人员的交流与沟通。而测试先行的方式也能让开发者从业务角度去看待问题,从客户角度去思考接口。此外,由于开发者总将测试职责委派给了专门的测试人员,于是渐渐会产生一种依赖心理。这种测试带来的反馈周期太长,一旦发现缺陷,修复的成本很高。2.2 分析需求,任务分解需求分析能力常常是开发人员的短板。开发人员养成了一个习惯,看什么事情都会从技术实现的角度去思考,很少从客户角度考虑,也很少从考虑需求因何而来,这个需求的价值是什么,这个需求的关键点在哪里。对于需求描述,我们没有深入了解,大多数掌握了大体后,就匆匆进行开发与实现。TDD要求我们在编写测试之前要做好合理的任务分解。若没有很好地理解需求,任务分解就无法顺利的进行。从用户价值出发,先梳理出最外层的需求任务,自上而下分解。基本上,任务拆分步骤:业务价值->业务功能->业务实现。2.3 测试先行的开发习惯测试先行开发习惯的问题,不是一朝一夕可以完成的。主要两个方面:测试先行为何物以我个人多年工作经验,询问过了很多开发人员,很多人都不清楚什么是TDD开发方法,更何况测试先行是什么。TDD方法的掌握有一些误解导致在改变为测试先行的时候,错以为应该一上来就写测试。因为思路没有理清,脑子里是一片乱麻,加上对TDD不够熟悉又不愿意写测试代码(都是测试干的活、凭什么这工作归到我手上的观念作怪),于是编写测试就变得举步维艰。在开始测试驱动开发之前,做适度的事先设计,还有利于我们仔细思考技术实现的解决方案。它与测试驱动接口的设计并不相悖。解决方案或许属于实现层面,若过早思考实现,会干扰我们对接口的判断;但完全不理会实现,又可能导致设计方向的走偏。2.4 开发人员的重构

不可运行——写一个功能最小完备的单元测试,并使得该单元测试编译失败。
可运行——快速编写刚刚好使测试通过的代码,不需要考虑太多,甚至可以使用一些不合理的方法。
重构——消除刚刚编码过程引入的重复设计,优化设计结构。
重构的关键首先在于如何识别代码的坏味道, 需要阅读一定规模优秀的开源代码,当这些坏味道变成你的一种直觉,你就会降低对糟糕代码的容忍度。
重构手法与代码坏味道一一对应。若有测试保障,重构就变得安全。但尽可能地,我们还是希望运用工具提供的自动重构功能,这既提高了重构效率,也在一定程度下确保了重构的安全。
2.5 单元测试
确定开发人员编写,测试函数/方法粒度,代码正确性检验方式(尤其测试数据),编码前后和修改代码时通过单元测试。
三、TDD实践
3.1 思考要做什么,如何测试它,然后编写一个测试(所需的接口类型、输入和数据)。
例如:

3.2 编写code使测试失败(明确失败比模糊好)。

运行失败code

3.3 编写刚刚测试通过code(确保之前编写的测试)。

运行成功的code

3.4 运行获取测试结果。如果没有通过,则立刻解决它。
示例:

3.5 如果有重复的逻辑或无法解释的代码,立刻重构(减少耦合,增加内聚力)。再次运行测试验证重构是否引入新的错误。如果没有通过,很可能是在重构时犯了一些错误,需要立即修复并重新运行,直到所有测试通过。
重构前:

重构后

运行结果:

3.6 重复01-05上述步骤,直到找不到更多驱动编写新代码的测试。

四、TDD难点
4.1 小步迭代,具体迭代是粒度多少?DD 建议是采用尽量小的迭代(测试无法再拆分,微小的重构),但是也没有强制一定按照这种步伐,不同人定义粒度不同,可在实践中获取适合自己的粒度,但前提必须尽量小。4.2 TDD 是银弹吗?TDD 不是银弹,不可能适合所有的场景,遇到问题还需要具体问题具体分析,寻找最佳实践。4.3 不遵循不可运行->可运行->重构是否可以?Kent Beck(TDD之父) 没有说明,也没有专门的人真正去做过这个统计。4.4 为什么每次在可运行阶段只编写“可用”代码?因为要尽快使测试运行起来,尽快缩短反馈时间,持续保持小步迭代的节奏。效率高,可产出优雅的code。
四、总结
TDD提供一种更快、更优雅、更安全的代码开发方式。“代码简洁可用这句言简意赅的话,正是 TDD 所追求的目标”——《Test-Driven Development》Kent Beck参考文章《重构》《测试驱动开发》Test-driven development:https://en.wikipedia.org/wiki/Test-driven_developmentLyning技术博客:https://juejin.im/post/5c3e73876fb9a049d37f5db1

- 作者:陈思宇 - 程序猿/ACP/PMP/ACP/P2/DOP/SA5.0/DevOps社区志愿者。