我不知道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呢?

《xUnit Test Patterns》学习笔记6 - Test Double相关推荐

  1. 大卫的Design Patterns学习笔记19:Observer

    一.概述 Observer(观察者)模式又被称作发布-订阅(Publish -Subscribe)模式,用于定义对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自 ...

  2. Structural patterns (proxy、bridge、decorator)学习笔记(一)

    Structural patterns (proxy.bridge.decorator)学习笔记(一) 一.proxy pattern是实际应用中,一般都用于比较复杂的一些对象当中,尤其是创建对象需要 ...

  3. Redis学习笔记(五)——持久化及redis.conf配置文件叙述

    对于日常使用来说,学习完SpringBoot集成Redis就够我们工作中使用了,但是既然学习了,我们就学习一些Redis的配置及概念,使我们可以更深层次的理解Redis,以及增强我们的面试成功概率,接 ...

  4. 尚学堂JAVA高级学习笔记_1/2

    尚学堂JAVA高级学习笔记 文章目录 尚学堂JAVA高级学习笔记 写在前面 第1章 手写webserver 1. 灵魂反射 2. 高效解析xml 3. 解析webxml 4. 反射webxml 5. ...

  5. windows下32位汇编语言学习笔记

    windows下32位汇编语言学习笔记 第一章  第一章 背景知识 80x86处理器的存储器 4个数据寄存器 EAX,EBX,ECX,EDX EAX寄存器 所有API函数的返回值都保存在EAX里,注意 ...

  6. PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

    您的位置 首页 PyTorch 学习笔记系列 PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 发布: 2017年8月4日 7,195阅读 ...

  7. SVO 学习笔记(深度滤波)

    SVO 学习笔记(深度滤波) 这篇博客 论文中的深度滤波 深度滤波的代码流程 更新Seed对象 初始化Seed对象 结尾 这篇博客  这篇博客将介绍SVO论文中的Mapping部分,主要介绍深度滤波器 ...

  8. SVO 学习笔记(三)

    SVO 学习笔记(三) 这篇博客 Initialization Frame_Handler_Mono processFirstFrame processSecondFrame processFrame ...

  9. SVO学习笔记(二)

    SVO学习笔记(二) 这篇文章 稀疏图像对齐 地图点投影(地图与当前帧间的关系) reprojectMap reprojectPoint reprojectCell 特征点对齐中的非线性优化 结尾 这 ...

最新文章

  1. 进程状态控制-进程创建
  2. Linux nethack
  3. 其实,人的核心职场时间是有限的,一定要和高手玩
  4. BZOJ 3669 . JZOJ 3754. 【NOI2014】魔法森林
  5. 原生js cookie本地存储
  6. 'adb' 不是内部或外部命令,也不是可运行的程序或批处理文件
  7. feign rest_与Feign客户轻松进行REST通信
  8. 15-CSS基础-浮动流
  9. 安远职业高中计算机专业,安远中等专业学校2021年招生简章
  10. 征集大家的网站如何防范DDOS攻击解决方案
  11. Linux课程笔记 Day05 命令总结
  12. 【5G通信】基于matlab 5G通信新型多载波技术GFDM【含Matlab源码 106期】
  13. log4cpp 使用完全手册
  14. HTML图片跟随鼠标移动代码,网页怎么实现图片跟随鼠标移动
  15. R语言使用aov函数进行单因素方差分析(One-way ANOVA)、使用multcomp包的glht函数检验组均值之间所有成对对比差异、使用plot函数可视化Tukey HSD两两均值比较图
  16. Go语言 gorutine和channel协同工作经典应用案例 (Golang经典编程案例)
  17. python实时监听微博发文同步到微信
  18. 创维电信悦me,(YMB0300-CW)卡刷固件及教程
  19. echarts制作中国地图
  20. Inversion Lemma

热门文章

  1. 【视频课】一课彻底掌握深度学习图像分类各种问题,学习CV你值得拥有
  2. 全球及中国一般手术器械行业投资态势与发展价值评估报告2022版
  3. 中国中医药产业未来投资规划与前景风险预测报告2022-2027年版
  4. 中国半光纸市场供需形势分析及运行环境研究报告2021年版
  5. 智慧赋能黔货出山 丰收节交易会·李喜贵:贵州农业数字化
  6. 粤港澳大湾区菜篮子-哲商对话·林裕豪:从玉农业谋定标准
  7. 单链表的实现:增删改查
  8. 分享几个简单的WPF控件(代码)
  9. 杭州线下|2019产品经理年终轰趴
  10. 听说这里有让你膜拜的产品方法论?