跳转至

11 作为工程化方法的TDD:更低的成本与更高的效能

你好,我是徐昊。在了解了测试驱动开发中的“测试”,和测试驱动开发中的“驱动”之后。让我们重新复盘一下作为工程化方法的TDD。

TDD的流程

第一讲中,我曾给出了一张任务分解法的流程图。用以在TDD演示过程中,帮助我们把握整体流程的走向。由于是在课程刚开始给出的,很多内容我们尚未澄清,无法给出更细致的描绘,只能看做对于TDD流程大略的描述。

那么在学习了什么是TDD中的测试,和什么是TDD中的驱动之后,我们将会怎么描述TDD的流程呢?

如上图所示,使用TDD的核心流程为:

  • 首先将需求分解为功能点,也就是将需求转化为一系列可验证的里程碑点
  • 如果已经存在架构或架构愿景,则依据架构中定义的组件与交互,将功能点分解为不同的功能上下文
  • 如果尚不存在架构愿景,则可以将功能点作为功能上下文;
  • 将功能点按照功能上下文,分解为任务项。也就是进一步将可验证的里程碑点,分解为功能上下文中可验证的任务项;
  • 将任务项转化为自动化测试,进入红/绿/重构循环,驱动功能上下文内的功能实现;
  • 如果重构涉及功能上下文的重新划分,即提取/合并组件,即视作对于架构的重构与梳理。需调整后续功能点中对于功能上下文以及任务项的划分。
  • 如此往复,直到所有功能完成。

通过上述过程的描述,可以发现任务列表中的任务项源自两层分解:源自对于业务理解的功能点分解,以及源自架构愿景的功能上下文分解。

功能点分解帮助我们形成可验证的里程碑点。这些里程碑点可看作由可工作的软件(Working Software)构成的进度度量。功能上下文分解帮助我们找到正确的单元,指导我们保持良好的软件架构。

如果功能点分解错误,那么就得不到功能正确的软件;如果功能上下文分解错误,那么就得不到架构良好的软件。

TDD的工程优势

所以,使用TDD开发软件对人的要求,与其他所有软件工程方法对人的要求是一样的:理解需求,明白架构。但是TDD提供了这样几点在工程管理上的优势。

第一,理解需求等于可以针对功能点写出测试。换句话说,写不出测试就是不理解需求。不理解需求就不要开发。在不理解需求的前提下开发功能点,只能带来负的进度。从工程管理角度上看,“判断一个人是否理解了需求”的成本极高。我想大家都有在各种拉通会议中,反复宣讲确认,但仍如鸡同鸭讲,不断消磨精力与斗志的体验。

TDD的“先写测试,后写生产的代码”的实践,实际上也就是要求“先理解需求,再开发”。但是TDD对于什么叫理解了需求,有了清晰的要求:能写出这个功能点的测试,就是理解了需求,别的都白扯。因而TDD从工程管理的角度,降低了“判断一个人是否理解了需求”的成本:看测试就行

第二,不写测试,除了不会写测试之外,就是没理解需求。没理解需求就去写测试,那就是瞎干,瞎干不如不干。如果整个团队都写不出测试,那么说明这个需求无法通过可管控的工程化方式交付。

可管控的工程化方式交付,意味着这个需求在实现层面上可以被执行,也就是高确定性的。在高确定性的环境下,要追求效率。

而无法通过可管控的工程化方式交付,意味着不确定这个需求在实现层面上是否可被执行,需要进入探索模式。在不确定的情况下,要追求低成本及时止损

所谓探索模式,也就是原型验证、定向的深度搜索、聘请外脑之类的活动。探索模式的关键在于,要在给定的时间范围内尝试,比如一到两天。超出这个时间范围仍然无法解决的,将变成项目风险,需要额外的管理方法,比如上升。然而,从工程管理的角度,“判断哪个需求应该从工程化方式交付模式进入探索模式”的成本极高。通常已经形成进度阻塞、延期或大量返工的时候,才能发现根本原因是不能确定实现方式

TDD可以通过是否可以在规定时间内写出测试加以判断。因而,从工程管理的角度,降低了“判断哪个需求应该从工程化方式交付模式进入探索模式”的成本:看测试就行

第三,所有软件从业人士都认为架构是重要的,但却很少有人理解架构究竟是如何发挥作用的。架构并不是停留在纸面上的框图,而是约定了构成软件系统的组件,以及组件之间的交互方式。也就是说,架构是组件职责划分的依据以及组件的交互模式

比如MVC(Model/View/Controller)架构,表明系统组件按职责应该分成三种:管理数据的模型(Model),负责展示的视图(View),以及处理事件交互逻辑的控制器(Controller)。它们的交互模式为:视图渲染模型中的数据,视图中触发的事件则交由控制器处理,控制器可以通过修改模型,或是更新视图。如下图所示:

想要破坏MVC的架构愿景很容易,比如把控制逻辑写到模型中,或者在视图中直接修改模型,都会破坏MVC的架构愿景。

架构是软件构造过程中产生的一类非常特殊的知识,它必须成为全体人员的共识,才能真正发挥作用。否则,架构就是一句空话,或者一堆空话。

从工程管理的角度,“判断团队是否对架构达成了共识”的成本极高。通常需要大量的代码审查才能发现对于架构理解的偏差,也需要大量的返工才能纠正这些偏差。

TDD则可以通过功能上下文以及任务项拆分的情况,判断成员是否认同并理解了架构。如果团队已经形成了架构共识,那么对于相同的功能点,团队中所有成员拆分出的功能上下文应该也相同。因而,从工程管理的角度,TDD降低了“判断团队是否对架构达成了共识”的成本:看功能上下文拆分就行

第四,架构愿景很难在一开始就想得尽善尽美,随着需求发展,总会出现以当前架构愿景不容易实现的需求。如果硬拗进当前架构,就会出现不当的职责划分别扭的组件交互,这只会加速架构的腐化。而“发现当前架构愿景不容易实现的需求”成本极高

TDD则可以通过功能上下文以及任务项拆分的情况,判断架构是否能够实现当前需求。如果无法拆分出合理的功能上下文和任务项,那么这个需求,就是当前架构愿景不易实现的需求。因而,从工程管理的角度,TDD降低了“发现当前架构愿景不容易实现的需求”的成本:看功能上下文拆分就行

顺便说一句,就算发现了“当前架构愿景不容易实现的需求”,也可以直接进入经典模式,通过重构抽取架构要素,演进式调整当前架构,不会影响项目进度。如下图所示:

不难发现,TDD要求先写测试,实际是以可工作的软件的形态,验证对于需求的理解;而任务分解,则是在确定单元粒度的过程中,验证对于架构的共识。只要我们承认,“理解需求,明白架构”是一切工程化软件开发的前提,那么TDD就是目前已知的效能最高的方法

小结

到此为止,我们展示了一个完整的例子:如何通过TDD实现命令行参数解析。以此为引子,我们讲解了TDD中的测试,分别介绍了状态验证与行为验证两种验证模式。并讨论了,为什么TDD中的测试不是单元测试。

在明白了TDD中的测试是什么之后,我们讨论了这样的测试是怎样驱动软件开发的:比起单元内的功能实现,测试更容易驱动架构设计,因而TDD更应该被看做是一种架构技术。然后,我们分别介绍了通过重构演进式驱动架构的经典TDD模式,以及在一定架构愿景之下,逐步实现不同功能单元的伦敦学派TDD模式。

最后,我们解释了作为工程方法,TDD与其他工程方法并没有什么本质区别,都需要在“理解需求,明白架构”的前提下,进行软件开发。只不过,TDD在工程管理上具有更低的成本和更高的效能。

虽然TDD看起来非常地违反直觉,然而在抽丝剥茧之后,你会发现,它反而是非常传统的开发模式。围绕测试与任务列表,它严谨地将所有必须的开发活动,转化为可管理的行为。杜绝了嘴上说一套实际做一套的可能。

也正是因为TDD从骨子里是非常传统的开发模式,非常强调对于需求的理解(可测试性),以及对于架构愿景的维护(演进式)。那么想要用好TDD,仅仅关注测试是不够的,还需要在需求分解与功能上下文分解上花力气下功夫。

接下来,我们将使用一系列更为实际的例子,展示如何通过TDD来完成它们。

思考题

在团队中从哪里开始推行TDD最容易入手?

欢迎把你的思考和想法分享在留言区,也欢迎你加入我们的读者交流群。我们下节课再见!

精选留言(10)
  • aoe 👍(1) 💬(1)

    1. 从认可 TDD 的员工开始推行最容易入手 2. 从想不加班但又想通过技术手段提升效率的员工入手 3. 从不想花费大量时间在线上找 Bug 的员工入手

    2022-04-07

  • 夏天 👍(0) 💬(1)

    TDD带来的是工程效率的提升,是降本增效的方法。所以在团队中推行TDD,要先说服sponsor。sponsor愿意买单,才能推行TDD。

    2022-04-11

  • 姑射仙人 👍(5) 💬(0)

    老师,后面会去讲如何在项目型的工程实践中,Restful接口,数据库模型。项目为典型的三层架构,或DDD分层架构。这样的场景下,如何进行TDD,以及在需求大量变动的情况下,保证项目的质量?

    2022-05-21

  • 汗香 👍(3) 💬(0)

    与客户交流的过程中以测试代替文档,先得到客户的对需求列表方式的认可

    2022-04-17

  • humor 👍(2) 💬(0)

    从自己开始入手,自己在做项目的过程中实践TDD,并取得一定的成果,进而在组内及团队内推广

    2022-04-17

  • Geek_b11f27 👍(0) 💬(0)

    从我自己入手,一个人如果不知道一件事情的好处就不会去做,不做就不知道这个好处,这样形成了一个死循环。我自己先试着打破循环,别人看到了了效果也相当于打破了他们的认知,从而有可能打破他们的认知上的死循环。

    2024-07-01

  • 少晴 👍(0) 💬(2)

    我目前是项目TDD的推动负责人,我现在很困惑,对于团队成员能力的参差不齐,推动TDD的过程非常困难,很难让团队成员接受TDD思想。很主要的一点是不会写测试代码是他们一直吐槽的点,甚至很多人认为写测试代码所投入的精力远大于功能实现。所以这个如何解决呢

    2023-05-28

  • 姑射仙人 👍(0) 💬(0)

    在团队中从哪里开始推行 TDD 最容易入手? 从正在需要重构的项目入手容易些吧,但是往往这样的项目都有工期,和领导的KPI。敢于推行TDD需要莫大的勇气,感觉又是个一把手工程。

    2022-05-21

  • 姑射仙人 👍(0) 💬(0)

    1. 从工程管理的角度,“判断一个人是否理解了需求”的成本极高。 2. 从工程管理的角度,“判断哪个需求应该从工程化方式交付模式进入探索模式”的成本极高。 3. 从工程管理的角度,“判断团队是否对架构达成了共识”的成本极高。 4. 从工程管理的角度,“发现当前架构愿景不容易实现的需求”成本极高。 感觉自己不懂工程管理,如何补足这方面的知识呢?

    2022-05-21

  • 👍(0) 💬(0)

    通过提交代码时检查代码测试覆盖率来推动TDD:前期可以覆盖率较低,但是一旦增加了检查,代码提交失败(红),增加测试代码使其通过检查并提交代码(绿),逐步提高测试覆盖率(重构),整个推动过程也可以保持TDD的节奏。——有种俄罗斯套娃的感觉,手动狗头

    2022-04-11