第 9 章 单元测试
9.1 TDD 三定律
- 定律一:在编写不能通过的单元测试前,不可编写生产代码。
- 定律二:只可编写刚好无法通过的单元测试,不能编译也算不通过。
- 定律三:只可编写刚好足以通过当前失败测试的生产代码。
9.2 保持测试整洁
测试代码和生产代码一样重要。它可不是二等公民。他需要被思考、被设计和被照料。它该像生产代码一样保持整洁。
9.3 整洁的测试
整洁的测试有什么要素?有三个要素:可读性、可读性和可读性。在单元测试中,可读性甚至比在生产代码中还重要。测试如何才能做到可读?和其他代码一样:明确,简洁,还有足够的表达力。在测试中,你要以尽可能少的文字表达大量内容。
……
现在看看代码清单 9-2 中改进了的测试。这些测试还是做一样的事,不过已经被重构为更整洁和有表达力的形式。
// 代码清单9-2 SerializedPageResponderTestjava (重构后)
public void testGetPageHierarchyAsXml() throws Exception {
makePages("Page0ne", "PageOne.Child0ne", "PageTwo") ;
submitRequest ("root", "type:pages") ;
assertResponseIsXML();
assertResponseContains (
"<name> PageOne</name>", "<name> PageTwo</name>", "<name>Child0ne</name>"
);
}
public void testSymbolicLinksAreNotInXmlPageHierarchy() throws Exception {
WikiPage page = makePage(" PageOne") ;
makePages("Rage0ne.Child0ne", "PageTwo");
addLinkTo(page, "PageTwo", "SymPage") ;
submitRequest("root", "type:pages");
assertResponseIsXML() ;
assertResponseContains(
"<name> PageOne</name>", "<name> PageTwo</name>","<name>Child0ne</name>"
);
assertResponseDoesNotContain ("SymPage") ;
}
public void testGetDataAsXml() throws Exception {
makePageWithContent("TestPageOne", "test page");
submitRequest("TestPageOne", "type:data");
assertResponseIsXML() ;
assertResponseContains("test page", "<Test") ;
}
这些测试显然呈现了构造-操作-检验(BUILD-OPERATE-CHECK)模式。每个测试都清晰地拆分为三个环节。第一个环节构造测试数据,第二个环节操作测试数据,第三个部分检验操作是否得到期望的结果。
9.4 每个测试一个断言
单个断言是个好准则。……最好的说法是单个测试中的断言数量应该最小化。
……
每个测试一个概念
更好一些的规则或许是每个测试函数中只测试一个概念。我们不想要超长的测试函数,测试完这个又测试那个。代码清单 9-8 就是那样一种测试的例子。这个测试应当拆解为 3 个单独测试,因为它测试了 3 件不同的事。把三者混到一起,读者就不得不猜想每段代码出现的理由,以及那段代码到底要测试什么。
9.5 F.I.R.S.T.
整洁的测试还遵循以下 5 条规则,这 5 条规则的首字母构成了本节标题:
- 快速(Fast):测试应该够快。 测试应该能快速运行。测试运行缓慢,你就不会想要频繁地运行它。如果你不频繁运行测试,就不能尽早发现问题,也无法轻易修正,从而也不能轻而易举地清理代码。最终,代码就会腐坏。
- 独立(Independent):测试应该相互独立。 某个测试不应为下一个测试设定条件。你应该可以单独运行每个测试,及以任何顺序运行测试。当测试互相依赖时,头一个没通过就会导致一连串的测试失败,使问题诊断变得困难,隐藏了下级错误。
- 可重复(Repeatable):测试应当可在任何环境中重复通过。你应该能够在生产环境、质检环境中运行测试,也能够在无网络的列车上用笔记本电脑运行测试。如果测试不能在任意环境中重复,你就总会有个解释其失败的接口。当环境条件不具备时,你也会无法运行测试。
- 自足验证(Self-Validating):测试应该有布尔值输出。无论是通过或失败,你不应该查看日志文件来确认测试是否通过。你不应该手工对比两个不同文本文件来确认测试是否通过。如果测试不能自足验证,对失败的判断就会变得依赖主观,而运行测试也需要更长的手工操作时间。
- 及时(Timely):测试应及时编写。单元测试应该恰好在使其通过的生产代码之前编写。如果在编写生产代码之后编写测试,你会发现生产代码难以测试。你可能会认为某些生产代码本身难以测试。你可能不会去设计可测试的代码。