1. TDD 三定律
TDD要求我们在编写生产代码前先编写单元测试,下面的三条定律只是冰山之巅。
- 在编写不能通过的单元测试前,不可编写生产代码。
- 只可编写刚好无法通过的单元测试,不能编译也算不通过。
- 只可编写刚好足以通过当前失败测试的生产代码。
这三条定律将你限制在大概30秒一个的循环中,测试与生产代码一起编写,测试代码只比生产代码早写几秒钟。
2. 保持测试简洁
有些开发团队,在单元测试中不做良好设计和仔细划分,变量名不用好,测试函数不必短小和具有描述性。只要测试代码还能工作,还覆盖着生产代码,就足够好。
其实他们没有意识到,脏测试等同于没测试。因为测试代码必须跟生产代码的演进而修改。测试越脏,就越难修改,你有可能花更多的时间添加新测试,而不是编写新代码。最终,编写新测试变成了开发者最大的抱怨,当经理问为何开发缓慢,开发者们就归咎于测试。最后,为了加快开发,他们只能扔掉了整个测试。没有测试,生产代码就开始变坏,客户开始抱怨。
如果保持测试简洁,测试就不会让他们失望。
测试代码和生产代码一样重要。它们可不是二等公民,它们需要被思考,被设计和被照料。
3. 简洁的测试
简洁的测试有三要素:可读性,可读性和可读性。在单元测试中,可读性甚至比生产代码还重要。
下面的测试来自FitNess,我经常写成这样,作者觉得它很难懂。
public void testGetPageHierachyAsXml() 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) resonder.makeResponse(new FitNessContext(root), request); String xml = response.getContent(); assertEquals("text/xml", response.getContentType()); assertSubString("PageOne", xml); assertSubString("PageTwo", xml); assertSubString("ChildOne", xml); }
下面是重构后的测试,更简洁、更可读。
public void testGetPageHierachyAsXml() throws Exception { makePages("PageOne", "PageOne.ChildOne", "PageTwo"); submitRequest("root", "type:pages"); assertResponseIsXML(); assertContentContains("PageOne", "PageTwo", "ChildOne"); }
4. 每个测试一个概念
有个流派认为,每个测试函数应该有且只有一个断言语句。这个有点苛刻,通常每个测试只测一个概念。
我们不想要超长的测试函数,测完这个又测那个。下面的例子测试了三个不同的是,应当拆分为三个不同的测试。
public void testAddMonths() { SerialDate d1 = SerialData.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, d2); assertEquals(31, d3.getDayOfMonth()); assertEquals(7, d3.getMonth()); assertEquals(2004, d3.getYYYY()); SerialDate d4 = SerialDate.addMonths(1, SerialData.addMonths(1, d1)); assertEquals(30, d4.getDayOfMonth()); assertEquals(7, d4.getMonth()); assertEquals(2004, d4.getYYYY()); }
当多个概念被测试,就可能导致测试遗漏。
5. F.I.R.S.T原则
快速(Fast)测试应当够快。这样才能频繁运行,帮助发现代码问题。
独立(Independent)每个测试用例相互独立,以任何顺序运行。
可重复(Repeatable)测试应该能在任何环境中重复通过。
自足验证(Self-Validating)不需查看日志来确认是否通过,不需要手动比较两个文件来确认是否通过。
及时(Timely)测试应该及时编写。如果在编写生产代码之后编写测试,你就会发现生产代码难以测试。你觉得难以测试,就可能不会去写测试。(P.S. 这条感通深受)。