译释Dozen ways to find bugs(3)——Assert your Expections
Posted on 2009-02-21 19:31 Aaron Wu 阅读(196) 评论(0) 编辑 收藏 举报英文原文名叫《A Dozen Ways to Get the Testing Bug in the New Year》,是Aaron在java.net上发现的文章。原文地址http://today.java.net/pub/a/today/2004/01/22/DozenWays.html,原文是在2004年1月22日发表的,按常理说一片早已经过了时效期的文章是没有多大用处的,但是Aaron却认为时至今日该文依然值得一阅。
现在网络上有很多关于测试驱动开发(Test Driven Development,TDD)的信息,我们可以把它简单概括成下面的两条:
1. 只在测试运行失败之后才开始写新的代码
2. 重构以保持代码的干净简洁
这种先写一个通不过的测试然后再来写实现代码的方法看起来很傻很天真,是吧?那我们先来看看我们自己是怎样写代码的——
在大多数情况下,我们先要由一个设计或者说计划的过程,如果没有这个过程而是直接写代码,一定会有人来责备你还没想清楚就开始写代码了。而测试先行(Test First)是一种很简单很快捷的方式。我们之前的写代码的方式要求我们事先考虑各种各样的问题(原文是rabbit holes,这让我想到了中国有句成语叫做“狡兔三窟”,和一个抓土拨鼠的小游戏,在很短时间内要快速且准确击中各个洞里面的土拨鼠的确是很麻烦的一件事,Aaron注),这些可以采用更简单的方式来处理——测试先行,测试可以让你明确什么时候代码才算完成(当所有的测试都通过了的时候)。这样做的最大好处就在于当你准备写下一个方法的时候,不需要考虑脑子里各种各样的期望或者需要考虑的问题。我们假设我们已经写好了这个方法,并且这个方法已然是一个完美的方法(能满足我们对它的各种期望),我们用一个自动化测试来检查它,在测试之中为我们的期望设置断言——断言我们的方法实现了某项期望。好吧,我们先看一个例子来看看测试先行是怎么办的:
假设你现在刚坐下来对着电脑,你准备写一个ShopPingCart类。你需要做的第一件事是建立起一个管理所购物品的集合。当同一种物品被分作两次放入购物车的时候,每次都会有一个数量,我们的ShoppingCart类应该具备计算物品总数量的能力。这样,我们把这个期望在测试中设置为断言——你期望你的代码能够达到的标准或者实现的功能。下面的是一个JUnit的测试方法:
import junit.framework.TestCase;
public class ShoppingCartTest extends TestCase {
public void testAddItems() {
ShoppingCart cart = new ShoppingCart();
cart.addItems("Snowboard", 1);
cart.addItems("Lift Ticket", 2);
cart.addItems("Snowboard", 1);
assertEquals(4, cart.itemCount());
}
}
有一个问题值得大家好好去思考,当你已经把代码写出来了——这个时候你已经不知不觉陷入到自己的一个固定思维之中了——我们还能想到多少测试用例呢?因此,测试驱动开发(测试先行,即在一开始就将自己的期望设置成测试中的断言,用这些断言来检查自己的代码是否符合需求或自己的期望,Aaron著)可以看做是用来构建和提炼出优质代码的有效方式,我们应该将它贯穿到整个开发过程中去。
好,现在你有了这个测试方法了,接着你应该做的就是写一段代码好让找个测试方法运行通过,不要多不要少,只要可以让这个测试方法通过就行,够用就好。用写实现代码前写好的测试来做代码完成的裁判,而不要担心我们将来需要适应怎么样的架构,我们下一个测试会不会导致这段代码崩溃,不要想这些。当测试方法运行通过之后,我们接着还需要重构我们的代码,以保持代码的简洁。重构完代码之后我们需要再运行我们的测试,以确保重构没有破坏代码。
如此这般重复这个过程,为下一个期望设置断言—写代码—运行测试—测试通过—重构—运行测试—测试通过。相信用不了多久你就会喜欢上这种测试—写代码——重构的循环作业方式,如果能坚持下去你会发现这种方法真的很棒。