Aaron的测试生活小说

半两五钱,笃志向前
  首页  :: 新随笔  :: 联系 :: 管理

译释Dozen ways to find bugs(4)——Think of It as Design

Posted on 2009-02-24 13:12  Aaron Wu  阅读(171)  评论(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测试开始:

public void testGetItem() {

    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());
}

 这个测试方法实际上列出了我们对于Shopping Cart类能够实现的功能的期望。这个测试方法不能运行通过,甚至不能编译成功。我们需要写多少代码来帮助这个测试方法运行通过呢?我们注意到,我们需要将一个指定的物品“ISBN123”添加到购物车中,而这个“ISBN123”实际上又是一个Product类的示例,这就有牵扯到Product类上来了。看起来我们需要一个数据库,我们可以从数据库中得到这个数据,然后上面的测试方法就可以运行通过了。赶紧打住!我们需要的不是一个数据库这种复杂的东西,我们只需要使用一个简单的数据结构将Product类与“ISBN123”关联起来就行了。我们可以通过硬编码的方式将这个数据写死到我们的代码中然后返回一个期望的Product。接下来我们使用一个简单的接口来做这件事情:

 

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相关联”这一功能相关规则。

    我们不需要一个很高超的面向对象的设计技术,只是简单得让测试告诉我们到底需要什么,然后我们写出尽可能简单的可以使测试通过的源代码就够了。再次提醒大家,记住在两轮测试的过程间重构我们的代码以保证我们的代码的简洁。