你好,我是徐昊。今天我们继续来讨论测试驱动开发中的驱动。

上节课我们讲了四种常用的重构手法,分别是提取方法、内联方法、引入参数和引入字段。并区分了用于高效修改代码的重构手法,和用于消除坏味道以改进架构设计的重构。还介绍了一种架构软件的方法:重构到模式。也就是通过重构将坏味道替换为模式,从而改进软件架构的方式。

重构到模式,或者说TDD 红/绿/重构循环中的重构,是在完成功能的前提下以演进的方式进行设计。这是延迟性决策策略,也叫最晚尽责时刻(Last Responsible Moment,LRM)。也就是说,与其在信息不足的情况下做决定,不如延迟到信息更多,或是不得不做出决策的时机再决策。这种策略的重点在于,在保持决策有效性的前提下,尽可能地推迟决策时间。

如果架构愿景不清晰,那么“最晚尽责时刻”让我们不必花费时间进行空对空的讨论,可以尽早开始实现功能,再通过重构从可工作的软件(Working Software)中提取架构。这种方式也被称作TDD的经典学派(Classic School)或芝加哥学派(Chicago School)。

除了经典学派之外,还有一种TDD风格,被称作TDD的伦敦学派(London School)。如果架构愿景已经比较清晰了,那么我们就可以使用伦敦学派进行TDD。

命令行解析

伦敦学派的做法是这样的:

接下来我仍然以命令行参数解析为例,演示一下如何通过伦敦学派的方式来开发它。

首先是明确我们的架构愿景,也就是对象的角色与职责划分。如下图所示:

如上图所示,在系统中一共存在四个类:作为对外API的Args,从参数列表中提取参数的ValueRetriever,将Java类对象封装为选项的OptionClass,以及根据类型和数据解析参数的OptionParser。它们的交互,也如上图所示。

在划定角色和职责之后,我们来依次实现它们。视频演示如下:

与经典模式的差异

通过上面的视频展示,你能明显体会到“伦敦学派”与“经典学派”的差异:

然而二者其实并没有什么冲突。回顾一下第八讲的演示,以及本节课介绍伦敦学派时的演示,你会发现二者有相似之处:都不是将功能整体作为单元的粒度,而是选择了更小的范围。所不同的仅仅在于,在本节课中,我们更为细致地划分了功能上下文(也就是做了更多的设计)。

所以我们可以将伦敦学派看作一种利用架构愿景分割功能上下文,然后再进入经典模式的TDD方法。这么做的好处是,对于复杂的场景,可以极大简化构造测试的时间。下面让我们看一个例子:

在这个例子里,我们可以通过重构去获得想要的架构,比如提取出Repository等。但是,以经典学派构造测试过于麻烦,而如果以伦敦学派开始就可以简单很多:

需要提醒一下,经典学派和伦敦学派是TDD中都需要掌握的基本功。在功能上下文内,以经典学派为主;而跨功能上下文时,可以使用伦敦学派对不同的功能上下文进行隔离。

顺便说一句,曾经有一段时间,TDD社区内将这两种风格对立起来:

然而这种分歧是无意义的。在不同的场合下,我们只需要使用该用的方式就好。

小结

通过第8-10这三节课,我们讨论了测试驱动中的驱动。也回答了测试驱动到底能够驱动什么:单元级别功能测试能够驱动其对应单元(功能上下文或变化点)的功能需求。而对于单元之内某个功能的实现,则无能为力。

从“驱动”的角度来说,TDD实际上并不是一种编码技术,更像是一种架构技术,它可以帮助你更好地将功能放置到不同的单元。于是我们继续介绍了TDD中两种处理架构的思路:延迟性决策的经典学派和通过架构愿景划分功能上下文的伦敦学派。

至此,我们已经对测试驱动开发有了较深入的了解,知道了什么样的测试,提供什么样的驱动。那么下节课,让我们看看测试驱动开发的全貌。

思考题

在伦敦学派中,是如何保证测试的有效性的?

编辑来信

TDD是一项技能,唯有动手实操、反复练习,才能有所小成。为了帮助你更快地进步,徐昊老师特发起了“代码评点”活动。
 
在第一个实战项目结束后,我们会根据你提交的学习反馈,手动选出其中几位进行代码评点与解疑答惑。而评点的详细内容我们也将制成加餐,展示在专栏里,供其他同学学习与参考。
 
划重点!如果学完第1-10讲再写反馈,将会大大提高你入选的机会!另,此次收集时间截至4月3日零点。所以非常希望你能跟上我们的更新进度,多动手实操,并记录学习体会。
 
最后,希望我们都能好好学习,更上层楼!