Test Double
我不知道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,必须非常清楚以下几个东西的关系,本文的重点也是说明一下他们之间的关系。他们分别是:
- Dummy Object
 - Test Stub
 - Test Spy
 - Mock Object
 - 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通常符合以下情形:
- 实际对象还未实现出来,先用一个简单的Fake Object代替它。
 - 实际对象执行需要太长的时间
 - 实际对象在实际环境下可能会有不稳定的情况。比如,网络发送数据包,不能保证每次都能成功发送。
 - 实际对象在实际系统环境下不可用,或者很难让它变得可用。比如,使用一个依赖实际数据库的数据库访问层对象,必须安装数据库,并且进行大量的配置,才能生效。
 
一个使用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的更多相关文章
- Java:Double Brace Initialization
		
在我刚刚接触现在这个产品的时候,我就在我们的代码中接触到了对Double Brace Initialization的使用.那段代码用来初始化一个集合: final Set<String> ...
 - Double Dispatch讲解与实例-面试题
		
引言 说实话,我看过GoF<Design Patterns>,也曾深深的被李建忠<设计模式>系列Webcast吸引.但是还没有见过“Double Dispatch模式”.的确G ...
 - Sql的decimal、float、double类型的区别
		
三者的区别介绍 float:浮点型,含字节数为4,32bit,数值范围为-3.4E38~3.4E38(7个有效位) double:双精度实型,含字节数为8,64bit数值范围-1.7E308~1.7E ...
 - decimal与double,float的选择与区别
		
decimal 类型可以精确地表示非常大或非常精确的小数.大至 1028(正或负)以及有效位数多达 28 位的数字可以作为 decimal类型存储而不失其精确性.该类型对于必须避免舍入错误的应用程序( ...
 - java使double保留两位小数的多方法 java保留两位小数
		
这篇文章主要介绍了java使double类型保留两位小数的方法,大家参考使用吧 复制代码 代码如下: mport java.text.DecimalFormat; DecimalFormat d ...
 - Max double slice sum 的解法
		
1. 上题目: Task description A non-empty zero-indexed array A consisting of N integers is given. A tripl ...
 - OpenMesh 将默认的 float 类型改为 double 类型
		
OpenMesh 中默认的数据类型都是 float 类型的,如果要将其默认的 float 类型改为 double 类型,可以这么做: #include <OpenMesh/Core/Mesh/P ...
 - Double的精度问题
		
/** * 自定义Math工具类 * */ public class MyMathTools { /** * 提供精确的小数位四舍五入处理. * * @param v * 需要四舍五入的数字 * @p ...
 - 关于printf错用格式化字符串导致double和long double输出错误的小随笔
		
[题外话] 以前用HUSTOJ给学校搭建Online Judge,所有的评测都是在Linux下进行的.后来为了好往学校服务器上部署,所以大家重新做了一套Online Judge,Web和Judge都是 ...
 - C/C++ 双精度double  数据相加出错缺陷解释
		
不知道有没有人和我一样遇到过这样一个问题,请看下面代码. #include<iostream> using namespace std; int main(){ double a=2.3, ...
 
随机推荐
- mac下git+maven+jenkins自动打包发布
			
随着springboot+springcloud(dubbo)越来越多人使用,流行的微服务的概念越来越深入人心.分布式部署越来越复杂,给手动发布带来很大工作量.为了方便前期测试和后期线上部署更新,可使 ...
 - python 编程语言基础技术框架
			
python标识符身份 id方法查看唯一标示符,内存地址 >>> a = "str" >>> b = 2 >>> id(a) ...
 - SEEprog Serial EEPROM programmer
			
Features SEEprog is universal programmer of all types of serial EEPROMs in 8-pin package. SEEprog en ...
 - Delphi 调用SQL Server 2008存储过程
			
1.表结构如下(预算数据明细表): CREATE TABLE [dbo].[BA_FeeDetail]( [ID] [int] IDENTITY(1,1) NOT NULL, [FeeDeptID] ...
 - Geeks 一般二叉树的LCA
			
不是BST,那么搜索两节点的LCA就复杂点了,由于节点是无序的. 以下是两种方法,都写进一个类里面了. 当然须要反复搜索的时候.能够使用多种方法加速搜索. #include <iostream& ...
 - SEO从理论到实践
			
GITHUB:http://www.liu12fei08fei.top/blog/12seo.html 明白seo是什么 知道怎么做 SEO从理论到实践 什么是SEO? SEO和SEM的区别 SEO和 ...
 - MVC扩展Filter, 通过继承AuthorizationAttribute限制IP
			
为什么需要AuthorizationAttribute 在没有Authorization系统属性之前,我们可能这样判断:Request.IsAuthenticated && User. ...
 - 把NDK的工具链提取出来单独使用
			
独立toolchain 把NDK压缩包解压到系统,如/mnt目录下,后在/mnt目录下建立文件夹my_ndk_toolchain,然后再/mnt目录下执行以下命令:/mnt/android-ndk-r ...
 - 最小二乘法least square
			
上研究生的时候接触的第一个Loss function就是least square.最近又研究了一下,做个总结吧. 定义看wiki就够了.公式如下 E(w)=12∑n=1N{y−xWT}2E(w)=12 ...
 - 混沌数学之Rössler(若斯叻)吸引子
			
若斯叻吸引子(Rössler attractor)是一组三元非线性微分方程: frac{dx(t)}{dt} = -y(t)-z(t) frac{dy(t)}{dt} = x(t)+a*y(t) fr ...