测试驱动开发(TDD)2020总结——原理篇

 

 

我是一名喜欢追求高质量代码和高效率工做的软件开发工程师,所以我学习 SOLID 和 Simple Design 等原则、阅读优秀的开源代码、阅读相关的书籍、学习软件过程方法和真实项目实践,可是在追求高质量代码的道路上,总感受目前的知识还不能帮我塑形成一种思惟框架。在 2018 年年初机缘巧合阅读了TDD(测试驱动开发)培训录这篇文章,瞬间欣喜若狂!程序员

到如今接触 TDD 将近一年,期间由于沉不下心只阅读了不多的资料就在项目中实践了一段时间,获得的效果还不错,自觉得已经很理解 TDD 实践和背后的思想,结果在不断阅读相关书籍和关于一些 TDD 的讨论中不断暴露本身的无知,发现本身就像站在“达克效应”曲线的愚昧之巅,原来大部分自认为正确的知识都是不许确甚至是错误的。不过我很享受这种过程,在学习的过程当中不断验证本身的知识是很是有趣的,这使我变得更有自知之明的同时也在不断突破自身的认知上限。编程

接下来我会经过图文的方式总结这段时间来对 TDD 的实践和思考,以便于沉淀自身对 TDD 的理解,但愿对读者有所帮助,也但愿读者能够指点一二,集思广益才能离真相更进一步。框架

 

范围.


 

TDD (Test Driven Development) 在不一样的圈子、不一样的角色的认知中可能会有不一样的理解,有人可能会理解成 ATDD(Acceptance Test Driven Development),也有人可能会理解成 UTDD(Unit Test Driven Development),为了不产生歧义, 文章涉及到 TDD 专指 UTDD(Unit Test Driven Development),即 「单元测试驱动开发」
 

什么是 TDD


 


之前很片面的认为 TDD = XP 的测试优先原则 + 重构,认为 TDD 只是经过单元测试来推进代码的编写,而后经过重构来优化程序的内部结构。这很容易被理解成只须要先写单元测试就能够驱动出高质量的代码,直到我精读 Kent Beck 的著做《测试驱动开发》和不断实践思考以后才总算窥探到 TDD 藏在冰山下的面貌:
Kent Beck:“测试驱动开发不是一种测试技术。它是一种分析技术、设计技术,更是一种组织全部开发活动的技术”。

分析技术: 体如今对问题域的分析,当问题尚未被分解成一个个可操做的任务时,分析技术就派上用场,例如需求分析、任务拆分和任务规划等,《实例化需求》这本书能够给予必定的帮助做用。函数

设计技术: 测试驱动代码的设计和功能的实现,而后驱动代码的再设计和重构,在持续细微的反馈中改善代码。单元测试

组织全部开发活动的技术: TDD 很好地组织了测试、开发和重构活动,但又不只限于此,好比实施 TDD 的前置活动包括需求分析、任务拆分和规划活动,这使得 TDD 具备很是好的扩展性。学习


TDD 的目标


Kent Beck 在他的著做《Test-Driven Development》一书中提到:“代码简洁可用这句言简意赅的话,正是 TDD 所追求的目标”。测试

对于如何保证“代码简洁可用”可使用分而治之的方法,先达到“可用”目标,再追求“简洁”目标。优化

可用: 保证代码经过自动化测试。编码

代码简洁: 在不一样阶段人们对简洁的理解程度也不同,不过遵循的原则差很少,例如 OOD 的 SOLID 原则,Kent Beck 的 Simple Design 原则等。设计

虽然有不少因素妨碍咱们获得整洁的代码,甚至可用的代码,无需征求太多意见,只须要采用 TDD 的开发方式来驱动出简洁可用的代码。



TDD 的规则

在 TDD 的过程当中,须要遵循两条简单的规则:

  1. 仅在自动测试失败时才编写新代码
  2. 消除重复设计(去除没必要要的依赖关系),优化设计结构(逐渐使代码通常化)

第一条规则的言下之意是每次只编写刚恰好使测试经过的代码,而且只在测试运行失败的时候才编写新的代码,由于每次增长的代码少,即便有问题定位起来也很是快,确保咱们能够遵循小步快跑的节奏;第二条规则就是让小步快跑更加踏实,在自动化测试的支撑下,经过重构环节消除代码的坏味道来避免代码日渐腐烂,为接下来编码打造一个温馨的环境

关注点分离是这两条规则隐含的另外一个很是重要的原则。其表达的含义指在编码阶段先达到代码“可用”的目标,在重构阶段再追求“简洁”目标,每次只关注一件事!!!

TDD 的口号

 

 

简单来讲,不可运行/可运行/重构——这正是测试驱动开发的口号,也是 TDD 的核心。在这个闭环中,每个阶段的输出都会成为下一阶段的输入。

  1. 不可运行——写一个功能最小完备的单元测试,并使得该单元测试编译失败。
  2. 可运行——快速编写刚恰好使测试经过的代码,不须要考虑太多,甚至可使用一些不合理的方法。
  3. 重构——消除刚刚编码过程引入的重复设计,优化设计结构。

假设这样的开发方式是可能的,那我采用 TDD 真正的动机是什么?

采用 TDD 的动机

  • 控制编程过程当中的忧虑感。

有一个有趣的想象,当我感受压力越大,自身就越不想去作足够多的测试。当知道本身作的测试不够时,就会增长自身的压力,由于我担忧本身写的代码有 BUG,对本身编写的代码不够自信,这是一种心态上的变化。此时测试是开发人员的试金石,能够将对压力的恐惧变为平日的杂事,采用自动化测试,就有机会选择恐惧的程度。

  • 把控编程过程当中的反馈与决策之间的差距。

若是我作了一周的规划,而且量化成一个个可操做的任务写到 to-do list,而后使用测试驱动编码,把完成的任务像这样划掉,那么个人工做目标将变得很是清晰,由于我明确工期,明确待办事项,明确难点,能够在持续细微的反馈中有意识地作一些适当的调整,好比添加新的任务,删除冗余的测试;还有一点更加让人振奋,我能够知道我大概何时能够完工。项目经理对软件开发进度能够更精确的把握。


TDD 的总体流程

 

 

  • 想一下我要作什么,想一想如何测试它,而后写一个小测试。思考所需的类、接口、输入和输出。
  • 编写足够的代码使测试失败(明确失败总比模模糊糊的感受要好)。
  • 编写刚恰好使测试经过的代码(保证以前编写的测试也须要经过)。
  • 运行并观察全部测试。若是没有经过,则如今解决它,错误只会落在新加入的代码中。
  • 若是有任何重复的逻辑或没法解释的代码,重构能够消除重复并提升表达能力(减小耦合,增长内聚力)。
  • 再次运行测试验证重构是否引入新的错误。若是没有经过,极可能是在重构时犯了一些错误,须要当即修复并从新运行,直到全部测试经过。
  • 重复上述步骤,直到找不到更多驱动编写新代码的测试。

使测试程序可运行的三条策略:


 

  1. 伪实现——能够返回一个常量或变量,而后调整伪实现,直至伪实现变成可接受的实现代码。
  2. 明显实现——直接将实现代码键入,由于已经明确如何编写实现代码。
  3. 三角法——当我明确输入和输出但殊不知道它背后的设计和实现是什么时,可使用三角法,原理是先用简单的可运行的例子做为参考的信息源,而后推出测试的明显实现。详细信息在参考资源中给出。

这三条规则的目的是达到代码的“可用”目标,只须要键入咱们认为正确的代码使测试程序尽快经过便可。


TDD 的难点

  • 缺少软件质量意识
  • 缺少必定程度的程序设计能力,很难设计出高内聚低耦合、意图清晰的结构和代码。
  • 缺少分析需求并进行任务分解和规划的能力,很容易在还没开始 TDD 的时候就被打乱了节奏。
  • 缺少合适的测试环境和测试规范。
  • 测试优先的习惯难以养成。
  • 重构手法不熟练。

TDD 疑问

  • 都说小步快跑,具体步伐是多小?

不管是测试程序覆盖的范围仍是重构时的中间步骤,TDD 建议是采用尽可能小的步伐(测试没法再拆分,微小的重构),可是也没有强制必定按照这种步伐,不一样人的步伐能够不一样,能够在实践中不断寻找适合本身的步伐,可是前提必须尽可能小。

  • 什么须要测试?什么不须要测试?

除了那些不写测试还能对本身的代码感到很是自信的人以外,这取决于本身的经验和对代码的信心程度。若是某些代码本身认为即便不须要测试,运行和重构时也很是有信心,就能够不须要测试,好比大部分 set get ;相反,若是去掉会让本身感到不安,就须要考虑加入测试。

  • 为何须要遵循不可运行/可运行/重构这个顺序,不能够采用其它顺序吗?

这个问题《测试驱动开发》做者 Kent Beck 也很难去证实,由于没有专门的人真正去作过这个统计,因此他表示不否定可能存在一些更好的顺序设计。

  • 为何每次在可运行阶段只编写“可用”代码?

由于要尽快使测试运行起来,这样能够下降来自系统的反馈周期,若是可以快速持续获得来自系统的反馈,那么就能够持续保持小步快跑的节奏。若是能够短期实现一个好的设计,写出优雅简洁的代码,那么在一开始 TDD 的时候,就应该采用最好的设计,由于这样的效率会比较高。

  • TDD 是银弹吗?

TDD 不是银弹,遇到问题须要寻找核心痛点是什么,而后再对症下药。

单元测试

与 ATDD 不一样, UTDD 主要面向的是开发人员,因此 UTDD 在这里主要关注的是软件内部的质量属性,若是说软件的外部质量体如今“缺陷数”和“缺陷率”等指标,那软件的内部质量属性体如今代码的“可测试性”、“可读性”和“可扩展性”等,这些几乎是每一位软件开发工程师的追求。“单元测试”做为 TDD 的产物之一,为了把控软件内部的质量属性,一般会使用到自动化“单元测试”做为软件质量保证的“根基”。

在计算机编程中,单元测试(英语:Unit Testing),一般由软件开发人员编写,用于确保他们所写的代码匹配软件需求和遵循开发目标,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工做。
每一个理想的测试案例独立于其它案例;为测试时隔离模块,常用 stubs、mock 或 fake 等测试马甲程序。
一般来讲,程序员每修改一次程序就会进行最少一次单元测试,在编写程序的先后极可能要进行屡次单元测试,以证明程序达到软件规格书要求的工做目标。

从维基百科的描述中能够看出单元测试拥有以下特色

  1. 开发人员编写。
  2. 测试函数/方法(TDD 的粒度更小)。
  3. 用于对代码进行正确性检验。
  4. 编码先后和修改代码时都会运行单元测试。
  5. 常用 stubs、mock 或 fake 等测试马甲程序确保每个单元测试之间互相独立(正交)

这里从我我的的角度对单元测试进行简单的分析,但对“单元测试”的理解,或者是它所处的位置仍是不够清晰,因此接下来我使用了“测试金字塔”模型来帮助我站在一个更高的视角理解“单元测试”。

测试金字塔

 

 

上图的“测试金字塔”模型按照运行速度和投入成本两个维度对不一样阶段的测试工做进行很是直观的可视化,能够看到单元测试是位于“测试金字塔”的最底部,很明显“单元测试”相对于其它不一样阶段的测试工做,拥有速度快(运行效率),成本低(维护成本)的优点,同时也是做为上层测试工做的支撑,体现了“单元测试”的重要程度。

总结


文章纯理论总结了 TDD 的全貌和一些 TDD 实践过程当中的策略,包括 TDD 的难点和疑问,文章屡次提到“反馈”一词是由于 TDD 是一种引入大量底层反馈的技术(得益于自动化测试),这些反馈使得很快就能看到行动的结果,使用 TDD,它将会在实践的过程当中学会如何雕琢咱们的代码,从而获得稳定的面向对象设计、可维护和高质量的系统。

后续

纸上得来终觉浅,惟有知行合一,经过理论指导实践,在实践中不断总结经验,不断验证本身的知识,才能不断对 TDD 有更深刻更正确的理解。接下来将计划出几篇文章演示使用 TDD 如何解决一些真实的案例的总结,以便于提升本身的 TDD 技艺。

相关文章
相关标签/搜索