首页 > 《xUnit Test Patterns》学习笔记6 - Test Double

《xUnit Test Patterns》学习笔记6 - 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,必须非常清楚以下几个东西的关系,本文的重点也是说明一下他们之间的关系。他们分别是:

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

image

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内部的输入进行验证。

image

使用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 =

            
""error">Invalid Time";

      assertEquals(
"Exception", expectedTimeString, result);

}

 

Test Spy

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

image

使用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的验证结果。

image

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呢?

更多相关:

  • 打开 build文件夹下面的webpack.base.conf.js; 找到下面这段代码,并将它注释掉: const createLintingRule = () => ({// test: /.(js|vue)$/,// loader: 'eslint-loader',// enforce: 'pre',// includ...

  • 写一个.cc文件,其中抱哈std::lock_guard以及std::thread等c++11特性,开始使用gcc编译,过程中出现如下问题 gcc test_lock.cc -o test_lock This file requires compiler and library support for the ISO C++ 201...

  • 在阅读ceph源码过程中发现部分C++语法还是不够熟悉,特此做一下笔记。 关于STL中的reserve函数的使用 reserve()是为容器预留空间,即为当前容器设定一个空间分配的阈值,但是并不会为容器直接allocate具体的空间,具体空间的分配是在创建对象时候进行分配得 以vector的reserve函数过程为例,直接看如下代码...

  • 第一种写法: 第二种写法:   转载于:https://www.cnblogs.com/w...

  • Rank() over()的用法 创建一个test表,并插入6条数据。 CREATE TABLE test (a INT,b INT,c CHAR ) INSERT INTO test VALUES(1,3,'E') INSERT INTO test VALUES(2,4,'A') INSERT INTO test VAL...

  • nan 是not a number ,inf是无穷大 numpy.nan_to_num(x): 使用0代替数组x中的nan元素,使用有限的数字代替inf元素...

  • 简介 Simple Reference  基础CUDA示例,适用于初学者, 反映了运用CUDA和CUDA runtime APIs的一些基本概念.Utilities Reference  演示如何查询设备能力和衡量GPU/CPU 带宽的实例程序。Graphics Reference  图形化示例展现的是 CUDA, OpenGL,...

  • 在做开发的过程中难免需要给内核及下载的一些源码打补丁,所以我们先学习下Linux下使用如如何使用diff制作补丁以及如何使用patch打补丁。...

  • 我在调研ATS 4.2.3挂载SSD的过程中,遇到很多坑,特此详细记录我摸索的主要过程,以便大家以后避免之。 基本思路可以完全照搬参考文献[2][3] 下面的安装假定是以root用户身份进行的,Linux服务器已经安装好系统,磁盘已经做好分区。 首先需要认识我们的Linux服务器的硬件配置和软件情况 硬件配置: DELL...

  • 该博文整理一些在使用stl编程过程中遇到的小经验: 1.在多线程环境下面打印调试,如何使用cout及时刷新到屏幕上? 在C中我们经常这样使用: printf("Hello World "); fflush(stdout); 如果使用stl,我们可以这样使用: cout << "Hello World" << endl <...

  • 2019独角兽企业重金招聘Python工程师标准>>> a different object with the same identifier value was already associated with the session 一个经典的hibernate错误:a different object with the...