1.TDD三定律
定律一 在编写能通过的单元测试前,不可编写生产代码
定律二 只可编写刚好无法通过的单元测试,不能编译也算不通过
定律三 只可编写刚好足以通过当前失败测试的生产代码
这样写程序,我们每天就会编写数十个测试,测试将覆盖所有生产代码。测试代码量将足以匹敌生产代码量,导致令人生畏的管理问题。
2.保持测试整洁
脏测试等同于没测试
测试代码和生产代码一样重要,它该像生产代码一般保持整洁
测试带来的好处
单元测试让你的代码可扩展、可维护、可复用
没有测试,每次修改都可能带来缺陷
测试覆盖率越高,就越不担心修改会造成问题
3.整洁的测试
整洁的测试最重要的要素———可读性
public void testGetPageHieratchyAsXml() throws Exception {
crawler.addPage(root, PathParser.parse("PageOne"));
crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
crawler.addPage(root, PathParser.parse("PageTwo"));
request.setResource("root");
request.addInput("type", "pages");
Responder responder = new SerializedPageResponder();
SimpleResponse response =
(SimpleResponse) responder.makeResponse(new FitNesseContext(root), request);
String xml = response.getContent();
assertEquals("text/xml", response.getContentType());
assertSubString("<name>PageOne</name>", xml);
assertSubString("<name>PageTwo</name>", xml);
assertSubString("<name>ChildOne</name>", xml);
}
public void testGetPageHieratchyAsXmlDoesntContainSymbolicLinks() throws Exception {
WikiPage pageOne = crawler.addPage(root, PathParser.parse("PageOne"));
crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
crawler.addPage(root, PathParser.parse("PageTwo"));
PageData data = pageOne.getData();
WikiPageProperties properties = data.getProperties();
WikiPageProperty symLinks = properties.set(SymbolicPage.PROPERTY_NAME);
symLinks.set("SymPage", "PageTwo");
pageOne.commit(data);
request.setResource("root");
request.addInput("type", "pages");
Responder responder = new SerializedPageResponder();
SimpleResponse response =
(SimpleResponse) responder.makeResponse(new FitNesseContext(root), request);
String xml = response.getContent();
assertEquals("text/xml", response.getContentType());
assertSubString("<name>PageOne</name>", xml);
assertSubString("<name>PageTwo</name>", xml);
assertSubString("<name>ChildOne</name>", xml);
assertNotSubString("SymPage", xml);
}
public void testGetDataAsHtml() throws Exception {
crawler.addPage(root, PathParser.parse("TestPageOne"), "test page");
request.setResource("TestPageOne"); request.addInput("type", "data");
Responder responder = new SerializedPageResponder();
SimpleResponse response =
(SimpleResponse) responder.makeResponse(new FitNesseContext(root), request);
String xml = response.getContent();
assertEquals("text/xml", response.getContentType());
assertSubString("test page", xml);
assertSubString("<Test", xml);
}
这三个测试很难读懂,代码中充满了干扰测试表达力的细节。
对PathParser的那些调用,它们将字符串转换为供爬虫使用的PagePath实体。转换与测试毫无关系。
创建responder相关的细节,还有reponse的收集与转换也充满了和测试无关的细节
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>");
}
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");
}
public void testGetDataAsXml() throws Exception {
makePageWithContent("TestPageOne", "test page");
submitRequest("TestPageOne", "type:data");
assertResponseIsXML();
assertResponseContains("test page", "<Test");
}
这些测试显然呈现构造-操作-检验模式。
第一个环节构造测试数据,第二个环节操作测试数据,第三个部分检验操作是否得到期望的结果
3.1面向特定领域的测试语言
我们不应直接使用程序员用来对系统进行操作的api,而是打造了一套包装这些api的函数和工具代码,这样就能更方便的编写测试,写出来的测试也更便于阅读。
3.2双重标准
测试api中的代码与生产代码相比,的确有一套不同的工程标准。测试代码应当简单、精悍、足具表达力,但它该和生产代码一般有效
public String getState() {
String state = "";
state += heater ? "H" : "h";
state += blower ? "B" : "b";
state += cooler ? "C" : "c";
state += hiTempAlarm ? "H" : "h";
state += loTempAlarm ? "L" : "l";
return state;
}
代码效率不是非常高。要提升效率,应该使用StringBuffer
这套应用显然是嵌入式实时系统,计算机和内存资源都很有限,不过测试环境完全不必做限制
有些事大概不会永远在生产环境中做,而在测试环境中做却完全没有问题,通常这关乎内存或cpu效率的问题。
4.每个测试一个断言
单个断言是个好准则,但也不用担心在单个测试中放入一个以上断言,最好的方法是单个测试中的断言数量应该最小化
更好一些鹅规则或许是每个测试函数中只测试一个概念。
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());
}
这个测试应该拆解为3个单独测试
1.对于某个有31天的月份的最后一天
(1)5月31日加一个月是6月30(而非6月31日)
(2)5月31日加两个月是7月31日
2.对于某个有30天的月份的最后一天
(3)6月30加一个月是7月30日(而非7月31日)
5. F.I.R.S.T
整洁的测试还遵循以下5条规则
快速(FAST)
测试应该够快,测试缓慢你就不会想要频繁地运行它。
独立(Independent)
测试应该相互独立。某个测试不应为下一个测试设定条件。你应该可以单独的运行每个测试,及以任何顺序运行测试。
可重复(Repeatable)
测试应当可在任何环境中重复通过。
自足验证(Self-Validating)
测试应该有布尔值输出。你不应该查看日志文件来确认测试是否通过。也不应该手工对比两个不同文本文件来确认测试是否通过
及时(Timely)
测试应及时编写。单元测试应该恰好在使其通过的生产代码之前编写。