日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

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

發布時間:2025/3/20 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《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,必須非常清楚以下幾個東西的關系,本文的重點也是說明一下他們之間的關系。他們分別是:

  • Dummy Object
  • Test Stub
  • Test Spy
  • Mock Object
  • Fake Object
  • Dummy Object

    Dummy Objects泛指在測試中必須傳入的對象,而傳入的這些對象實際上并不會產出任何作用,僅僅是為了能夠調用被測對象而必須傳入的一個東西。

    使用Dummy Object的例子:

    public?void?testInvoice_addLineItem_DO()?{
    ???????nal?
    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
    ??????
    //???De?ne?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("?ight?still?exists?after?being?removed",
    ??????????????????facade.?ightExists(?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?con?guration
    ??????Con?gurableMockAuditLog?mockLog?=
    ?????????
    new?Con?gurableMockAuditLog();
    ??????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("?ight?still?exists?after?being?removed",
    ??????????????????facade.?ightExists(?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呢?

    總結

    以上是生活随笔為你收集整理的《xUnit Test Patterns》学习笔记6 - Test Double的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。