这周 Yahoo“测试驱动开发(TDD)”小组里面有个热门贴子,是讨论“classic”和“mockist”TDD 方式之间的对立统一关系。Steve Freeman、Nat Pryce、Michael Feathers、Dale Emery 等很多人对这些术语进行了讨论,并描述了各自的工作方式。他们也探讨了是否确实存在这种关系。如果存在这种关系,又是什么从本质上区分开这两种方式?
在声明自己“倾向于classic TDD 开发方法”之后, Olaf Bjarnason 在小组里面用问题——“如果你以前采用 classic(方式),是什么让你转向 [‘mockist’]?觉得新方法怎么样?”——发起了一场超过 70 条贴子的讨论。然而,前期的回复大多是围绕着 Olaf 的贴子标题“classic/mockist 讨论”进行的,即是否确有必要进行如此绝对对立的划分。
JMock 的合作创始人 Nat Pryce 表达了他的看法:
我认为把 TDD 分成“mockist”和“基于状态”是没有意义的,不仅分散了关注点,而且妨碍人们去尝试学习和实践 TDD。
Mock 对象只是一种工具,只是实践 TDD 时会使用到的工具之一。跟其他工具一样,它们被设计出来以帮助解决特定上下文的一系列问题。脱离了上下文,它们不会有任何帮助,甚至成为障碍。
如果确实如此,那怎么知道该何时使用这种“工具”呢?怎么判断该测试“状态”(不使用 mock),还是该验证行为(使用 mock)?
Dale Emery 也加入讨论,讲了他一般是怎么区分的:
很多人使用“基于状态”和“基于行为”的说法来区分。我换了种说法,我认为是“结果”和“协议”。如果是测试 UUT(unit under test,被测试单元)是否返回了正确的结果,我通常不使用 mock。如果是测试 UUT 是否基于协议扮演了恰当的角色——比如,给定正确的起始条件和影响因素,(UUT)是否向正确的协作者发送了正确消息——我会用 mock 来代替 UUT 的直接协作者。
Lior Friedman 认为mock 的用途是展示一种契约:
对我来说,mock 指出了被测试覆盖的类和被(该类)调用的其他类之间存在的“契约”。(mock)测试的目的是确保契约被满足了。
Charlie Poole 就“什么时候使用基于状态(的测试)”提出了他的论断:
如果没有其他办法来观察对象的行为,也就是说如果方法调用只会引起对象外界可观察状态的改变,我会选择基于状态的测试。而假如事实上对象做了些什么,我会 [使用 mock] 验证它的确是做了。
Adam Sroka 解释了他何时使用 mock:
就我个人而言,我通常是在系统定义的边界处(比如文件系统、网络、数据库等)使用 mock。在自顶而下的开发中,为了得到接口和客户端的松耦合,我通常会使用 mock。相反,当交互的对象很小、易于用假对象 /stub 对象替代时,我一般不使用 mock。
随着讨论的继续,讨论中反复出现这样的现象(可能上文的摘录也体现了这点):虽然不少贴子的观点类似,但是它们并没有使用通用的分类和命名,只是强调“我们有时这么做,其他时候不是”。而且,每个贴子(可能除了 Sroka“自顶而下”的论断)关注更多的是:到底是设计驱动 mock,抑或相反,mock 驱动设计?
关于 mock 驱动设计,Michael Feathers 给出了如下例子:
对我来说,问题的根本在于人们愿意在多大程度上遵循“tell, don’t ask.”mocks 支持了这种设计方式。 …
假设你拥有某一类对象,想得到它的错误: class Errors {
int errorCount();
Error getError(int index);
}Errors errors = object.getErrors();
这就是 _ask_。我们可以基于状态对 Errors 对象进行测试。为了改成 _tell_,你需要进行如下处理: interface ErrorReceiver {
void accept(Error error);
}ErrorReceiver receiver = …;
object.reportErrors(receiver);
我们可以对 mock 的 receiver 对象指定预期行为,使之通过测试,而在生产代码中使用“真实”的类。
Steve Freeman(JMock 的另一位合作开发者)发表了一篇贴子,描述了他的搭档 Nat Pryce 对一些“值得 mock 的相关对象组(mock-worthy peer objects)”提出的分类(“Dependencies”,“Notifications”和“Policies”)。在回应的贴子中, Feathers (以及 Colin Jack )认为他们两人(Freeman 和 Pryce)提出的设计理念实际上就是“mock 驱动设计”哲学的核心的一部分。在接下来的讨论中,扼要的讲,当人们提到“mockist TDD”,主要是指这些理念。而且,随着这些更具体的名字,之前反复出现的关于“classic 与 Mockist TDD”的混淆也减少了。
Pryce 提醒小组成员,他和 Freeman 计划写一本书,提供一些这方面需要的解释。同时他也建议小组成员去参阅他的一篇文章“基于状态与交互的测试”。
坦率的说,这种讨论并不是什么新货色(Google 一下),过一段时间就会出现一次。是否存在着“classic TDD”,而它是否又意味着“设计引出mock”或者其他特性?是否存在“mockist TDD”,体现着“mock 引出设计”的哲学?“Tell, Don’t Ask”,这是否又是完全不相干的东西?不管怎样,这是一种“非此即彼”的分类么,又或者“这种适合这些,那种适合那些”的分类更合适?
当然,一如既往,本文只是摘录了 Yahoo 小组讨论中的突出部分 [希望本文是客观的],仅仅是完整讨论的一部分。你可以自行阅读该贴以及其他相关资料,在这里或讨论贴里与其他人分享你的经验。
评论