这篇关于测试驱动开发(TDD)的文章将帮助您熟悉这个开发周期并使其适应您的编码方法。
测试驱动开发(TDD)的概念由Kent Beck在2003年引入。没有正式的定义,但Beck给出了TDD的方法和示例。TDD的目标是“编写干净的代码”。
在TDD中,只跟随一个经验法则:只有改变生产代码,如果测试失败。否则,只有重构才能优化代码。对于更新的需求,将它们转换为测试用例,添加这些测试,然后才编写新代码。
TDD是一个非常短的开发周期,并且是重复的。客户需求转变为高度特定的测试用例,并编写和改进软件以通过新测试。
测试驱动开发与极限编程中的测试优先编程概念相关,在短开发周期中提倡频繁的软件更新/发布,并促进广泛的代码审查,单元测试和增量功能的添加。
与TDD密切相关的概念是验收测试驱动开发(ATDD),其中客户,开发人员和测试人员都参与需求分析过程。TDD既适用于移动应用程序开发人员,也适用于Web应用程序开发人员,而ATDD则是确保需求定义明确的通信工具。
测试驱动开发(TDD)循环
让我们从基础知识开始,逐步了解TDD循环,也称为Red-Green-Refactor过程。
测试驱动的开发周期:
1.添加一个测试,肯定会失败。(红色)
在TDD中,首先根据测试用例添加软件中的每个功能。为新功能或更新功能创建测试。要编写测试,开发人员必须了解功能规范和要求。
这种做法将TDD与传统的软件开发方法区分开来,在编写源代码后编写单元测试。通过这种方式,TDD使开发人员在编写代码之前关注需求。
2.运行所有测试。看看是否有任何测试失败。
运行测试验证测试工具是否正常工作并同时证明当添加的新测试失败并且现有代码失败时,需要新代码。
3.只写足够的代码来通过所有测试。(绿色)
在这个阶段编写的新代码可能并不完美,可能会以不相关的方式通过测试。这个阶段唯一的要求是所有的测试都应该通过。添加语句的一种可能方式是返回常量,并逐步添加逻辑块以构建函数。
4.运行所有测试。如果任何测试失败,请返回步骤3.否则,继续。
如果所有测试都通过,则可以说代码符合测试要求,并且不会降低任何现有功能。如果任何测试失败,则必须编辑代码以确保所有测试都通过。
5.重构代码。(重构)
随着代码库的增长,必须定期清理和维护。怎么样?有几种方法:
为方便传递测试而添加的新代码可以移动到代码中的逻辑位置。
必须消除重复。
必须设置对象定义和名称以表示其用途和用法。
随着更多功能的添加,功能变得冗长。它可以证明有利于拆分和仔细命名,以提高可读性和可维护性。
由于所有测试都在重构阶段重新运行,因此开发人员可以确信该过程不会改变任何现有功能。
6.如果添加了新测试,请从步骤1开始重复。
采取小步骤,在每次测试运行之间进行少至1到10次编辑。
如果新代码没有快速满足新测试,或者其他不相关的测试意外失败,则撤消/恢复为工作代码,而不是进行大量调试。
使用外部库时,重要的是不要使增量小到仅仅测试库本身,除非是测试库是否过时/不兼容,错误或功能不完整。
TDD周期中的常见做法
在这部分中,我将为您提供一个快速的TDD常用实践演练,它将帮助您更好地编写代码。
小单位
单元是一个类/模块,它是一组密切相关的函数,通常称为模块。保持较小的单元增加了诸如更容易测试和调试的好处。
测试结构
由于您将始终运行单元测试,因此应用以下测试结构非常重要:
设置:使系统或被测单元(UUT)进入运行测试的必要状态,确保系统准备好进行测试。
执行:在目标上运行测试并监控所有返回值和输出,确保执行路径是您要定位的路径。
验证:断言/确保结果正确。这是声明测试是否通过/失败的重点。
清理:将测试系统恢复到原始状态。这允许立即执行另一个测试。
要避免的做法
决定论 - 确保测试是确定性的。对API调用或系统日期/时间等事件的依赖性可能导致测试失败,即使代码没有更改也是如此。
如果可能,请避免执行测试执行顺序,并允许随机执行测试。同样,避免让测试依赖于先前或其他测试结果。
测试精确的执行行为时间或性能。
不要开发那些评估超出预期的测试用例(“无所不知的神谕”)。
不要设计执行时间要长得多的测试。
个人实践
保持每项测试仅关注验证它所需的结果。
在非实时系统中,开发与时间相关的测试以实现执行容差。允许延迟执行的5%到10%的余量以减少测试期间漏报的可能性是常见的做法。
将测试代码视为与生产代码相同。这提高了代码质量和稳健性。
在可行的情况下将测试分成更小的测试。
作为一个团队,请检查您的测试和测试实践,以分享有效的技术并捕捉坏习惯。
高级实践
验收测试驱动开发(ATDD)具有先进的TDD实践,开发团队的目标是满足客户定义的验收测试。客户可以使用自动机制来确定软件是否满足其要求。
测试驱动开发(TDD) - 优点和缺点
优点
在TDD中编写测试会强制您考虑用例,并提高工作效率。
即使考虑到基于编写单元测试的代码量,总体实现也将更短且更少的错误。
调试变得更容易。
如果遵循常见的TDD实践,则开发的代码是模块化的,灵活的和可扩展的。
每次增量更新都会自动回归检测。
自动化测试非常彻底。由于编写的代码不会超过通过失败测试所需的代码,因此这些自动化测试往往涵盖每个代码路径。
更简单的文档,单元测试是自我记录,更易于阅读和理解。您应始终以描述性方式记录源/生产代码。
限制
当需要功能测试时,TDD不能很好地完成,例如GUI设计。
当开发人员自己编写单元测试时,测试可能会与代码共享相同的盲点。
有时,大量的通过测试会产生错误的安全感,导致集成测试期间的测试活动减少,从而可能导致问题。
测试成为维护开销的一部分。写得不好的测试可能会进一步导致维护或更新的成本增加。
TDD期间实现的详细程度无法在 以后轻松重新创建。
实践中的测试驱动开发
适用于大型系统
对于大型系统,测试具有挑战性,并且需要具有明确定义的组件的模块化架构。必须满足的一些关键要求是:
高内聚确保每个模块提供一组相关功能,使相应的测试更易于维护。
低耦合允许对模块进行隔离测试。
场景建模
在场景建模中,构建了一组序列图,每个图表都集中在单个系统级执行场景中。它为创建响应输入的交互策略提供了极好的工具。
每个场景模型都作为组件将提供的功能的一组要求。场景建模有助于在复杂系统中构建TDD测试。
代码可见性和安全性
区分测试和生产之间的代码非常重要。单元测试套件必须能够访问要测试的代码。但是,信息隐藏和封装以及模块分离等标准的设计不得受到损害。
在面向对象的设计中,测试仍然无法访问私有数据成员和方法,并且需要额外的编码。或者,可以在源代码中使用内部类来包含单元测试。此类测试黑客不应保留 在生产代码中。TDD从业者经常争论是否应该测试私人数据。
结论
在本文中,我们概述了测试驱动开发(TDD)。我们看到了TDD的优势和局限,以及与TDD相关的实践,涵盖了开始采用TDD周期所需的知识。