译释Dozen ways to find bugs(4)——Think of It as Design
Posted on 2009-02-24 13:12 Aaron Wu 阅读(172) 评论(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却认为时至今日该文依然值得一阅。
测试先行实质上是一种设计活动,它要求你站在源代码之外考虑问题,我们要在执行代码之前(实际此时还没有可供执行的源代码)考虑我们的代码将会实现什么样的功能。如果享受过测试驱动开发的好处,你应该会注意到测试驱动开发实际上更像是一种设计技术而不是测试技术。当然,如果你试图从之前写过的一些测试方法中(包括从这篇文章中前面介绍的一个简单示例测试方法)找到设计的影子,你可能会失望。当前,我们还是先关注测试设计中的难点,然后我们会慢慢发现这之中的设计的影子。测试要求我们站在用户的角度考虑问题,而测试先行的方法其实是帮助你实现这个目标。如果我们写出来的代码/软件难以测试,那么对客户来讲它也一定不是一个易用的——优秀的软件。
下面是有关设计的一个场景示例:依然假设你正在写一个Shopping Cart的应用程序~客户需要能够将一个特定的物品放置到购物车中并可以得到当前购物车中已经有的物品的详细信息。不要先考虑购物车相关联的基层代码结构,我们还是先从一个JUnit测试开始:
ShoppingCart cart = new ShoppingCart();
cart.addItems("ISBN123", 1);
Iterator items = cart.items();
Product item = (Product)items.next();
assertEquals("Confessions of an OO Hired Gun", item.getDescription());
assertEquals(9.95, item.getUnitCost(), 0.0);
assertEquals(1, item.getQuantity());
}
public interface Catalog {
public void addProduct(String key, Product p);
public Product getProduct(String key);
}
这样我们就可以避免去新建一个数据库而仅仅是在代码中实现Catalog
接口,我们将这些数据放到内存中处理(比如使用Hashmap)而不是使用“持久性”方法。这样做不是因为懒惰,实际上简单易行的方法还可以保持我们Catalog接口的干净整洁。实际上,测试可以帮助我们将接口的设计与实现方法的设计区分开来(关于接口的作用的话题就不在这里延伸讨论了,译者。)我们简单实现了Catalog接口并构造了我们测试方法中需要的数据,但是我们需要重构一下我们的测试方法
:
public void testGetItem() {
Catalog catalog = new InMemoryCatalog();
catalog.addProduct("ISBN123", new Product("Confessions of an OO Hired Gun", 9.95));
ShoppingCart cart = new ShoppingCart(catalog);
cart.addItems("ISBN123", 1);
Iterator items = cart.items();
Product item = (Product)items.next();
assertEquals("Confessions of an OO Hired Gun", item.getDescription());
assertEquals(9.95, item.getUnitCost(), 0.0);
assertEquals(1, item.getQuantity());
}
显然,现在我们要使这个测试方法通过就不再是一件难事了,同时也可以看到我们压根就没有使用数据库。当然,你也许会说,我们迟早是要用到数据库的。你是对的,但是——我们现在可以不需要数据库,我们只在我们不得不用的时候才去考虑新建一个数据库这么复杂的事情,但肯定不是现在。我们现在可以测试——在没有数据库的情况下运行我们的测试方法并使其通过。目前为止,一个简单的数据构造方式有助于我们集中精力去设计我们的Shopping Cart而不用考虑相关联的其他的事情。测试先行体现出了一种高内聚低耦合的设计:Shopping Cart类与所有Catalog的实现方法关系变弱了,而我们的Catelog接口则规定了“将指定物品与Product相关联”这一功能相关规则。
我们不需要一个很高超的面向对象的设计技术,只是简单得让测试告诉我们到底需要什么,然后我们写出尽可能简单的可以使测试通过的源代码就够了。再次提醒大家,记住在两轮测试的过程间重构我们的代码以保证我们的代码的简洁。