《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
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??梢岳斫釺est 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的例子是,將一個依賴實際數據庫的數據庫訪問層對象替換成一個基于內存,使用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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux登录密码破解
- 下一篇: WinForm中给DataGridVie