我不知道Test Double翻译成中文是什么,测试替身?Test Double就像是陈龙大哥电影里的替身,起到以假乱真的作用。在单元测试时,使用Test Double减少对被测对象的依赖,使得测试更加单一,同时,让测试案例执行的时间更短,运行更加稳定,同时能对SUT内部的输入输出进行验证,让测试更加彻底深入。但是,Test Double也不是万能的,Test Double不能被过度使用,因为实际交付的产品是使用实际对象的,过度使用Test Double会让测试变得越来越脱离实际。

我感觉,Test Double这玩意比较适合在Java,C#等完全面向对象的语言中使用。并且需要很好的使用依赖注入(Dependency injection)设计。如果被测系统是使用C或C++开发,使用Test Double将是一个非常困难和痛苦的事情。

要理解Test Double,必须非常清楚以下几个东西的关系,本文的重点也是说明一下他们之间的关系。他们分别是:

  1. Dummy Object
  2. Test Stub
  3. Test Spy
  4. Mock Object
  5. Fake Object

Dummy Object

Dummy Objects泛指在测试中必须传入的对象,而传入的这些对象实际上并不会产出任何作用,仅仅是为了能够调用被测对象而必须传入的一个东西。

使用Dummy Object的例子:


public void testInvoice_addLineItem_DO() {
      final int QUANTITY = 1;
      Product product = new Product("Dummy Product Name",
                                    getUniqueNumber());
      Invoice inv = new Invoice( new DummyCustomer() );
      LineItem expItem = new LineItem(inv, product, QUANTITY);
      // Exercise
      inv.addItemQuantity(product, QUANTITY);
      // Verify
      List lineItems = inv.getLineItems();
      assertEquals("number of items", lineItems.size(), 1);
      LineItem actual = (LineItem)lineItems.get(0);
      assertLineItemsEqual("", expItem, actual);
}

Test Stub

测试桩是用来接受SUT内部的间接输入(indirect inputs),并返回特定的值给SUT。可以理解Test Stub是在SUT内部打的一个桩,可以按照我们的要求返回特定的内容给SUT,Test Stub的交互完全在SUT内部,因此,它不会返回内容给测试案例,也不会对SUT内部的输入进行验证。

使用Test Stub的例子:


public void testDisplayCurrentTime_exception()
         throws Exception {
      // Fixture setup
  Testing with Doubles 136 Chapter 11    Using Test Doubles
      //   Define and instantiate Test Stub
      TimeProvider testStub = new TimeProvider()
         { // Anonymous inner Test Stub
            public Calendar getTime() throws TimeProviderEx {
               throw new TimeProviderEx("Sample");
         }
      };
      //   Instantiate SUT
      TimeDisplay sut = new TimeDisplay();
      sut.setTimeProvider(testStub);
      // Exercise SUT
      String result = sut.getCurrentTimeAsHtmlFragment();
      // Verify direct output
      String expectedTimeString =
            "<span class=\"error\">Invalid Time</span>";
      assertEquals("Exception", expectedTimeString, result);
}

Test Spy

Test Spy像一个间谍,安插在了SUT内部,专门负责将SUT内部的间接输出(indirect outputs)传到外部。它的特点是将内部的间接输出返回给测试案例,由测试案例进行验证,Test Spy只负责获取内部情报,并把情报发出去,不负责验证情报的正确性。

使用Test Spy的例子:


public void testRemoveFlightLogging_recordingTestStub()
            throws Exception {
      // Fixture setup
      FlightDto expectedFlightDto = createAnUnregFlight();
      FlightManagementFacade facade =
            new FlightManagementFacadeImpl();
      //    Test Double setup
      AuditLogSpy logSpy = new AuditLogSpy();
      facade.setAuditLog(logSpy);
      // Exercise
      facade.removeFlight(expectedFlightDto.getFlightNumber());
      // Verify state
      assertFalse("flight still exists after being removed",
                  facade.flightExists( expectedFlightDto.
                                            getFlightNumber()));
      // Verify indirect outputs using retrieval interface of spy
      assertEquals("number of calls", 1,
                   logSpy.getNumberOfCalls());
      assertEquals("action code",
                   Helper.REMOVE_FLIGHT_ACTION_CODE,
                   logSpy.getActionCode());
      assertEquals("date", helper.getTodaysDateWithoutTime(),
                   logSpy.getDate());
      assertEquals("user", Helper.TEST_USER_NAME,
                   logSpy.getUser());
      assertEquals("detail",
                   expectedFlightDto.getFlightNumber(),
                   logSpy.getDetail());
}

Mock Object

Mock Object和Test Spy有类似的地方,它也是安插在SUT内部,获取到SUT内部的间接输出(indirect outputs),不同的是,Mock Object还负责对情报(indirect outputs)进行验证,总部(外部的测试案例)信任Mock Object的验证结果。

Mock的测试框架有很多,比如:NMock,JMock等等。如果使用Mock Object,建议使用现成的Mock框架,因为框架为我们做了很多琐碎的事情,我们只需要对Mock对象进行一些描述。比如,通常Mock框架都会使用基于行为(Behavior)的描述性调用方法,即,在调用SUT前,只需要描述Mock对象预期会接收什么参数,会执行什么操作,返回什么内容等,这样的案例更加具有可读性。比如下面使用Mock的测试案例:


public void testRemoveFlight_Mock() throws Exception {
      // Fixture setup
      FlightDto expectedFlightDto = createAnonRegFlight();
      // Mock configuration
      ConfigurableMockAuditLog mockLog =
         new ConfigurableMockAuditLog();
      mockLog.setExpectedLogMessage(
                           helper.getTodaysDateWithoutTime(),
                           Helper.TEST_USER_NAME,
                           Helper.REMOVE_FLIGHT_ACTION_CODE,
                           expectedFlightDto.getFlightNumber());
      mockLog.setExpectedNumberCalls(1);
      // Mock installation
      FlightManagementFacade facade =
            new FlightManagementFacadeImpl();
      facade.setAuditLog(mockLog);
      // Exercise
      facade.removeFlight(expectedFlightDto.getFlightNumber());
      // Verify
      assertFalse("flight still exists after being removed",
                  facade.flightExists( expectedFlightDto.
                                             getFlightNumber()));
      mockLog.verify();
}

Fake Object

经常,我们会把Fake Object和Test Stub搞混,因为它们都和外部没有交互,对内部的输入输出也不进行验证。不同的是,Fake Object并不关注SUT内部的间接输入(indirect inputs)或间接输出(indirect outputs),它仅仅是用来替代一个实际的对象,并且拥有几乎和实际对象一样的功能,保证SUT能够正常工作。实际对象过分依赖外部环境,Fake Object可以减少这样的依赖。需要使用Fake Object通常符合以下情形:

  1. 实际对象还未实现出来,先用一个简单的Fake Object代替它。
  2. 实际对象执行需要太长的时间
  3. 实际对象在实际环境下可能会有不稳定的情况。比如,网络发送数据包,不能保证每次都能成功发送。
  4. 实际对象在实际系统环境下不可用,或者很难让它变得可用。比如,使用一个依赖实际数据库的数据库访问层对象,必须安装数据库,并且进行大量的配置,才能生效。

一个使用Fake Object的例子是,将一个依赖实际数据库的数据库访问层对象替换成一个基于内存,使用Hash Table对数据进行管理的数据访问层对象,它具有和实际数据库访问层一样的接口实现。


public class InMemoryDatabase implements FlightDao{
   private List airports = new Vector();
   public Airport createAirport(String airportCode,
                        String name, String nearbyCity)
            throws DataException, InvalidArgumentException {
      assertParamtersAreValid(  airportCode, name, nearbyCity);
      assertAirportDoesntExist( airportCode);
      Airport result = new Airport(getNextAirportId(),
            airportCode, name, createCity(nearbyCity));
      airports.add(result);
      return result;
   }
   public Airport getAirportByPrimaryKey(BigDecimal airportId)
            throws DataException, InvalidArgumentException {
      assertAirportNotNull(airportId);
      Airport result = null;
      Iterator i = airports.iterator();
      while (i.hasNext()) {
         Airport airport = (Airport) i.next();
         if (airport.getId().equals(airportId)) {
            return airport;
         }
      }
      throw new DataException("Airport not found:"+airportId);
}

说了这么多,可能更加糊涂了。在实际使用时,并不需要过分在意使用的是哪种Test Double。当然,作为思考,可以想一想,以前测试过程中做的一些所谓的“假的”东西,到底是Dummy Object, Test Stub, Test Spy, Mock Object, 还是Fake Object呢?

Test Double的更多相关文章

  1. Java:Double Brace Initialization

    在我刚刚接触现在这个产品的时候,我就在我们的代码中接触到了对Double Brace Initialization的使用.那段代码用来初始化一个集合: final Set<String> ...

  2. Double Dispatch讲解与实例-面试题

    引言 说实话,我看过GoF<Design Patterns>,也曾深深的被李建忠<设计模式>系列Webcast吸引.但是还没有见过“Double Dispatch模式”.的确G ...

  3. Sql的decimal、float、double类型的区别

    三者的区别介绍 float:浮点型,含字节数为4,32bit,数值范围为-3.4E38~3.4E38(7个有效位) double:双精度实型,含字节数为8,64bit数值范围-1.7E308~1.7E ...

  4. decimal与double,float的选择与区别

    decimal 类型可以精确地表示非常大或非常精确的小数.大至 1028(正或负)以及有效位数多达 28 位的数字可以作为 decimal类型存储而不失其精确性.该类型对于必须避免舍入错误的应用程序( ...

  5. java使double保留两位小数的多方法 java保留两位小数

    这篇文章主要介绍了java使double类型保留两位小数的方法,大家参考使用吧 复制代码 代码如下: mport java.text.DecimalFormat; DecimalFormat    d ...

  6. Max double slice sum 的解法

    1. 上题目: Task description A non-empty zero-indexed array A consisting of N integers is given. A tripl ...

  7. OpenMesh 将默认的 float 类型改为 double 类型

    OpenMesh 中默认的数据类型都是 float 类型的,如果要将其默认的 float 类型改为 double 类型,可以这么做: #include <OpenMesh/Core/Mesh/P ...

  8. Double的精度问题

    /** * 自定义Math工具类 * */ public class MyMathTools { /** * 提供精确的小数位四舍五入处理. * * @param v * 需要四舍五入的数字 * @p ...

  9. 关于printf错用格式化字符串导致double和long double输出错误的小随笔

    [题外话] 以前用HUSTOJ给学校搭建Online Judge,所有的评测都是在Linux下进行的.后来为了好往学校服务器上部署,所以大家重新做了一套Online Judge,Web和Judge都是 ...

  10. C/C++ 双精度double 数据相加出错缺陷解释

    不知道有没有人和我一样遇到过这样一个问题,请看下面代码. #include<iostream> using namespace std; int main(){ double a=2.3, ...

随机推荐

  1. CentOS 7挂载磁盘提示: mount: unknown filesystem type 'LVM2_member'

    说明:CentOS 7采用新的分区格式xfs以及LVM的模式,造成挂载硬盘的方式有变化. 解决: 使用lvdisplay命令查看LVM最终的卷路径,然后再进行挂载. 比如: mount /dev/Vo ...

  2. phpcms功能列表

    上1 站点首页 就是前台首页 会员中心 跳到会员中心页面 搜索 新闻,图片等文档搜索 锁屏 锁住账号 Phpcms 官网 授权 官网查询 支持论坛 官网论坛 帮助 官网帮助 上2 我的面板 个人信息 ...

  3. Syncovery 是目前功能最为强大的实时自动备份工具

    Syncovery Pro(原名叫做Super Flexible Synchronizer) 是目前功能最为强大的实时自动备份工具,连FTP.WebDAV等全部支持! 最近从V6开始改用比较好记.易懂 ...

  4. Digital Adjustment of DC-DC Converter Output Voltage in Portable Applications

    http://pdfserv.maximintegrated.com/en/an/AN818.pdf http://www.maximintegrated.com/app-notes/index.mv ...

  5. maven打包出错: Failed to clean project: Failed to delete

    maven打包出错: Failed to clean project: Failed to delete 出现这种错误,通常是由于您已启动了另一个tomcat 进程,导致报错,关闭tomcat进程即可 ...

  6. C#编程(六)------------枚举

    原文链接:http://blog.csdn.net/shanyongxu/article/details/46423255 枚举 定义枚举用到的关键字:enum public enum TimeOfD ...

  7. Android - 警告Nested weights are bad for performance

    Android - 警告Nested weights are bad for performance 本文地址: http://blog.csdn.net/caroline_wendy 原因: Lay ...

  8. DAHON 美国大行

    DAHON美国大行1982年创立于美国加州,多年来一直率先推动环保的代步工具——折叠自行车.创新是大行的生命,从韩氏兄弟发明了全世界第一辆小到能够放进火车座椅底下的折叠自行车起,大行就开启了不断向前的 ...

  9. [Ubuntu] ubuntu的tty下挂载移动硬盘拷贝数据

    转载:http://blog.csdn.net/langb2014/article/details/51567460 更换CUDA好多人都更换成功了,我却失败了,然后电脑最后进不了界面了,只有tty端 ...

  10. 在Xcode中显示代码行号

    打开一个程序,点击屏幕菜单栏的Xcode,然后选Xcode -> Preferences -> Text Editing -> Show line numbers前面打勾就行了. 如 ...