• 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 =
                "<span class="error">Invalid Time</span>";
          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呢?

  • 相关阅读:
    2020软件工程作业——团队03
    2020软件工程作业 ——团队02
    2020软件工程作业05
    2020软件工程作业04
    2020软件工程作业03
    2020软件工程作业02
    2020软件工程作业01
    微服务:基本介绍
    excel模板解析前后设计变化,以及我对此的看法和感受
    纸上得来终觉浅,绝知此事要躬行——Spring boot任务调度
  • 原文地址:https://www.cnblogs.com/hushaojun/p/4381493.html
Copyright © 2020-2023  润新知