「然而,没有测试套件,他们就丧失确保『程序修改後是否仍能照预期般工作』的能力,他们没办法保证『对系统某部分的修改不会搞烂系统其他部分的程序』。所以他们的程序缺陷率开始上升」
「他们开始害怕修改程序,他们的产品程序开始腐坏,最後变成没有任何测试、混乱和 Bug 丛生的产品程序」
取自: Clean Code (p.140)
关於整洁的测试这个议题,本身就能写成一本书,本章节仅做简单概念介绍
测试程序保存和加强了产品的:
原因很简单,有了测试程序,你就不会害怕修改程序。无论你的程序架构分割的多好,每一次的修改都可能潜藏错误。没有了测试,你将害怕改变会导致其他尚未察觉的错误!
先写测试程序反而能加快产品开发的速度
作者的朋友 Jason Gorman 以一个将罗马数字与整数互相转换的小程序为例,共切分 6 个小阶段开发程序,并故意间隔采用 TDD 开发策略 (即,开发功能前先写好测试程序):
取自: Clean Architecture (p.9)
我们可以发现:
我们或许可以得到两个启发:
「测试程序对一个软件专案的影响程度就跟产品程序一样重要」
「想要走的快,唯一的方式就是要走的好」
取自: Clean Code (p.150) & Clean Architecture (p.10)
测试会覆盖所有的产品程序,数量足以和产品程序匹敌,将产生管理问题
测试程序会随着产品程序的演进而修改,而当测试程序越陷入一团混乱时,所花的时间可能比开发新产品还要多。当开发者将错误归咎於测试套件时,他们会将整个测试套件都舍弃掉
「然而,没有测试套件,他们就丧失确保『程序修改後是否仍能照预期般工作』的能力,他们没办法保证『对系统某部分的修改不会搞烂系统其他部分的程序』。所以他们的程序缺陷率开始上升」
「他们开始害怕修改程序,他们的产品程序开始腐坏,最後变成没有任何测试、混乱和 Bug 丛生的产品程序」
测试程序跟产品程序一样重要
「容许测试程序可以是混乱的」,是失败的源头。测试程序也需要花时间思考、设计、和维护
良好的测试例子: Build-Operate-Check
// Test 1
public void testGetPageHierarchyAsXml() throws Exception {
makePages("PageOne", "PageOne.ChildOne", "PageTwo");
submitRequest("root", "type:pages");
assertResponseIsXML();
assertResponseContains(
"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"
);
}
// Test 2
public void testSymbolicLinksAreNotInXmlPageHierarchy() throws Exception {
WikiPage page = makePage("PageOne");
makePages("PageOne.ChildOne", "PageTwo");
addLinkTo(page, "PageTwo", "SymPage");
submitRequest("root", "type:pages");
assertResponseIsXML();
assertResponseContains(
"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"
);
assertResponseDoesNotContain("SymPage");
}
// Test 3
public void testGetDataAsXml() throws Exception {
makePageWithContent("TestPageOne", "test page");
submitRequest("TestPageOne", "type:data");
assertResponseIsXML();
assertResponseContains("test page", "<Test>");
}
上述的每个测试都被拆解成三个部分
1. 建立测资
2. 操作测资
3. 结果是否如预期
任何人都可以在不被细节误导和干扰的情况下,马上了解测试程序
上述例子虽然采用了 Build-Operate-Cehck 的模式来设计测试程序,但仍有另一派人认为,每个测试函式都只能有唯一的一个 Assert。好处是每个测试都只会产生一个结论,人们可以更快速容易地了解它们。让我们以此概念来改写上述例子:
// Test 1 (Refactored)
public void testGetPageHierarchyAsXml() throws Exception {
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "type:pages");
thenResponseShouldBeXML();
}
public void testGetPageHierarchyAsXml() throws Exception {
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "type:pages");
thenResponseShouldContain(
"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"
);
}
注意: 这边依据[1] Given-When-Then 的概念来替换了函式的名称,这让程序可读性上升。不幸的是,这样的拆解会导致重复程序码产生
要解决 Give-When-Then 模式产生的重复程序码,可以利用 Template Method [2]设计模式来提取共用程序码。也就是将 Given/When 提取至基底类别,Then 则放在不同衍生类别
断言仅能唯一或许是一种较为极端的作法,但不论如何,谨记:
「测试里的断言应该尽可能地减少」
public void testAddMonths() {
SerialDate d1 = SerialDate.createInstance(31, 5, 2004);
SerialDate d2 = SerialDate.addMonths(1, d1);
assertEquals(30, d2.getDayOfMonth());
assertEquals(6, d2.getMonth());
assertEquals(2004, d2.getYYYY());
SerialDate d3 = SerialDate.addMonths(2, d1);
assertEquals(31, d3.getDayOfMonth());
assertEquals(7, d3.getMonth());
assertEquals(2004, d3.getYYYY());
SerialDate d4 = SerialDate.addMonths(1, SerialDate.addMonths(1, d1));
assertEquals(30, d4.getDayOfMonth());
assertEquals(7, d4.getMonth());
assertEquals(2004, d4.getYYYY());
}
「测试程序对於一个专案的健康程度,就跟产品程序一样重要。如果你让测试程序腐败,那麽你的产品程序也会跟着腐败。保持你的测试整洁」
P.S. 关於 TDD 的探讨在国外也有另一群人批评其为 "Cargo Cult" (邪教),有兴趣的读者们可以自行 Google 查找相关资讯。另外,究竟台湾的职场环境适不适合导入测试驱动开发(或说,该如何说服主管?),笔者挺好奇各位大大们的看法,欢迎交流~
>>: [Python 爬虫这样学,一定是大拇指拉!] DAY06 - URL / URN / URI (2)
前言 写程序,设定好 IDE,可以增加自己的效率,今天来纪录一下安装 Visual Studio C...
前言 今天来讲解特殊型别中的 never,never 是一种函式回传值的状况,跟 void 很像,稍...
In this series, I want to introduce some concepts ...
After introducing about the 2 methods for timing e...
Let's Start From Scratch 本系列文章的头几篇我决定还是带点基础的东西,但是我...