让我们再聊聊TDD 续——正起思规其行

ThoughtWorks洞见 2021-02-25 20:58

北京


在实际工作中,实践TDD第一步就是转变思维-测试前移(及测试左移),将测试用例分析,设计和实现前移到编写代码之前。这里的测试并不只是单元测试,也不是说一定要使用mock和stub来做测试。这里的测试就是指软件测试本身。


在前两篇中我们聊了很多TDD理论实践相关的疑惑,其中包括TDD的分类,选择以及其实施步骤。最近TDD相关的培训和讨论也越来越多,比如:
  • 学好TDD靠多多练习就可以了,不用学习其理论知识

  • 开发应该自己理解业务,并提炼测试(需求)点来实施TDD

  • 开发只要做好TDD,就不需要其他人测试了

  • 软件没有做好就是因为TDD没有做好

  • 等等

而我对于这些片面的说法都是不赞同的,比如我在第一篇讨论中就提到,TDD不是银弹,所以软件没有做好的原因是很多的,也不是靠TDD做好了就一定能做好。其次学习TDD的理论是非常重要的,而仅仅靠不断练习并自悟的方法,对于大部分普通人来说是难以成功的。
最后一般开发能做的较好的TDD,一般是UTDD,而对于ATDD,则需要相应的业务分析,测试分析与设计的相关方法和技术。如果要求开发做好ATDD,则需要开发学习并掌握相应的业务分析,测试分析与设计的相关方法和技术。而这对于开发也是非常大的挑战。所以在真正的TDD实践中,如果想大规模实施TDD,仍然需要相应的分工才更可行,更容易实施。而提前充分学习并了解TDD实践的相关理论,也是可以帮助实施人员更好的去实践,少踩坑,从而正其思规其行。

TDD需要的能力
在真实工作中要较好的实施TDD需要具备以下能力:
  • 测试前移(左移)的思维能力

  • 软件设计能力

  • 业务和技术需求分析和任务拆分能力

  • 测试用例分析和设计能力

  • 自动化测试开发能力

  • 代码重构和持续改进能力

首先是测试前移(左移)的思维能力是TDD实施的前提条件。如果没有这个思维能力,或者不认可这个测试前移(左移)的价值,这样的开发人员则很难认可TDD的开发方式,从而可能会想尽各种办法抵制TDD,或者阳奉阴违,从而导致TDD实施艰难,并显得困难重重。所以一定要拥有这个能力作为前提才容易真正实施好TDD。
要实施TDD,第一步就是业务需要分析能力,其次根据需求点设计测试用例的能力。这里面包含了一个业务需求人员和测试人员的基本能力。其中业务需求分析能力需要能很好的分析业务需求,并能总结出业务需求点或者业务验收点。其次测试用例设计能力需要能根据业务的需求点或者业务验收点设计出有效的正确的测试用例,从而才能驱动出功能正确的业务代码。
实施TDD最为核心的两个能力则是自动化测试开发能力和代码重构能力。其中自动化测试开发能力是指熟练使用各种自动化测试框架,将前面设计出来的测试用例自动化起来。对于UTDD常用的自动化测试框架由JUnit,Jasmine等,而对于ATDD常用的自动化测试框架则由Cucumber,RobotFramework等。只有将测试用例自动化之后,才能快速的进行回归测试,从而帮助代码重构。而良好的代码重构能力,则是代码质量内建,防止代码腐化以及保障代码易于维护的主要手段之一。如果没有能力或者不愿对代码进行重构,那么就不能算一个完整的TDD。
最后要实施一套完整的好的TDD,还需要一个持续改进的能力。不仅需要对代码进行持续改进,即代码重构;还需要对自动化测试的代码,测试用例设计和业务分析进行持续改进。只有这样对TDD的各个步骤和环节都进行持续改进,才能越来越好的实施TDD。

UTDD和ATDD的步骤

在实际工作中,实践TDD第一步就是转变思维-测试前移(及测试左移),将测试用例分析,设计和实现前移到编写代码之前。这里的测试并不只是单元测试,也不是说一定要使用mock和stub来做测试。这里的测试就是指软件测试本身,可以是基于代码单元的单元测试,也可以是基于业务需求的功能测试,也可以是基于特定验收条件的验收测试。
其次是帮助开发人员,主要是帮助开发人员理解软件的功能需求和验收条件,帮助其思考和设计代码,从而达到驱动开发的目的。所以TDD的可以被分为UTDD(Unit TDD)和ATDD(Acceptance TDD),其中UTDD是指代码单元级别的驱动开发,而ATDD是指功能验收级别的驱动开发。所以TDD可以帮助开发人员梳理和理解需求 ,帮助开发人员获得更好的代码设计 ,并且有效的减少过度设计,获得大量有效的测试用例(手动/自动), 以及可以获得快速反馈,从而有效的减少返工,提高代码的内在质量。
对于UTDD(单元驱动测试开发),首先由开发人员自己或者开发人员结对业务或者测试人员一起分析并梳理需求点,然后开发人员针对每个需求点编写自动化单元测试用例,并实现代码直到单元测试通过。UTDD中的测试主要针对函数(方法)或者业务单元代码的测试,而且一定要自动化,这样才能在开发的过程中快速的反复的执行它们,从而达到驱动开发的目的。最后也可以在持续集成流水线快速反复的执行他们,从而帮助持续集成获得单元测试层面上的快速反馈。
UTDD的特点如下:
  • 关注单元级别的代码设计

  • 测试用例需要明确的实例

  • 清晰的单元完成标志

  • 最快的feedback周期

  • 有效的减少开发过程中side effect引起的返工

  • 可以帮助开发减少调式的成本

  • 可以作为单元接口的使用文档

而对于ATDD(验收驱动测试开发),首先BA或者QA编写验收测试用例,然后Dev通过验收测试来理解需求和验收条件,并编写实现代码直到验收测试用例通过。由于验收方法和类型也是多种多样的,所以根据验收方法和类型的不同ATDD又可以分为BDD(Behavior Driven Development),EDD(Example Driven Development),FDD(Feature Driven Development),CDCD(Consumer Driven Contact Development)等具体的实践方法。比如以用户使用软件时软件的行为为验收标准,这个是BDD;如果以特定的实例数据为验收标准,这个是EDD;如果以Web Service API消费者提出API契约来驱动API提供者开发API,这个是CDCD等。所以ATDD的具体实现需要结合项目的实际情况来选用适合的验收测试d方法与类型。
ATDD的特点如下:
  • 关注业务价值,测试与需求一体化

  • 明确的测试示例(SBE)而不是复杂的描述

  • 清晰的功能完成标志

  • 更快的feedback周期,提早/频繁沟通

  • 消除误解,减少返工

  • 可视化的验收回归测试

  • 可以作为描述功能的活文档

图中TDD测试驱动开发经典三步曲,不论是UTDD还是ATDD都可以按照这个三步来实施: 
  1. 变红:写一个不通过的测试 (红) 

  2. 变绿:写实现代码,使其刚好 通过测试 (绿) 

  3. 重构

但是要实施好TDD,不能只靠这三个核心步骤,还需要相应的其他辅助步骤以及多方协作,下面两个图分别展示了TDD的步骤和协作的基本全貌。

TDD实施-步骤

TDD实施-协作


最后

业界元老Robert C. Martin 也提出过他总结的TDD三原则:
  • 不允许编写任何产品代码,除非目的是为了让失败的测试通过

  • 不允许编写多于一个的失败测试,编译错误也是失败

  • 不允许编写多于恰好能让测试通过的产品代码,有效的减少返工

这三个原则很好的总结了TDD实践的关键步骤。此三原则虽然是正确的,但是严格按照此三个原则去做却是不易的,并且在现实的开发工作中并不是很多人能严格按照此三原则去编写代码,因为转变思维是一件很困难的事情,而且如果在时间短交付压力大的情况下就更为困难了。

但是在我经历的项目中,我们一般以ATDD结合UTDD的模式进行工作,并根据资源的多少决定其比例,从而全方位保证代码的内在质量和业务正确性。

- 相关阅读 -

让我们再聊聊TDD|洞见

让我们再聊聊TDD 续——人人都在做TDD|洞见

点击【阅读原文】可至洞见网站查看原文&绿色字体部分的相关链接。

本文版权属ThoughtWorks公司所有,如需转载请在后台留言联系。