《xUnit Test Patterns》学习笔记6 - Test Double
我不知道Test Double翻譯成中文是什么,測(cè)試替身?Test Double就像是陳龍大哥電影里的替身,起到以假亂真的作用。在單元測(cè)試時(shí),使用Test Double減少對(duì)被測(cè)對(duì)象的依賴,使得測(cè)試更加單一,同時(shí),讓測(cè)試案例執(zhí)行的時(shí)間更短,運(yùn)行更加穩(wěn)定,同時(shí)能對(duì)SUT內(nèi)部的輸入輸出進(jìn)行驗(yàn)證,讓測(cè)試更加徹底深入。但是,Test Double也不是萬(wàn)能的,Test Double不能被過(guò)度使用,因?yàn)閷?shí)際交付的產(chǎn)品是使用實(shí)際對(duì)象的,過(guò)度使用Test Double會(huì)讓測(cè)試變得越來(lái)越脫離實(shí)際。
我感覺(jué),Test Double這玩意比較適合在Java,C#等完全面向?qū)ο蟮恼Z(yǔ)言中使用。并且需要很好的使用依賴注入(Dependency injection)設(shè)計(jì)。如果被測(cè)系統(tǒng)是使用C或C++開發(fā),使用Test Double將是一個(gè)非常困難和痛苦的事情。
要理解Test Double,必須非常清楚以下幾個(gè)東西的關(guān)系,本文的重點(diǎn)也是說(shuō)明一下他們之間的關(guān)系。他們分別是:
Dummy Object
Dummy Objects泛指在測(cè)試中必須傳入的對(duì)象,而傳入的這些對(duì)象實(shí)際上并不會(huì)產(chǎn)出任何作用,僅僅是為了能夠調(diào)用被測(cè)對(duì)象而必須傳入的一個(gè)東西。
使用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
測(cè)試樁是用來(lái)接受SUT內(nèi)部的間接輸入(indirect inputs),并返回特定的值給SUT。可以理解Test Stub是在SUT內(nèi)部打的一個(gè)樁,可以按照我們的要求返回特定的內(nèi)容給SUT,Test Stub的交互完全在SUT內(nèi)部,因此,它不會(huì)返回內(nèi)容給測(cè)試案例,也不會(huì)對(duì)SUT內(nèi)部的輸入進(jìn)行驗(yàn)證。
使用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像一個(gè)間諜,安插在了SUT內(nèi)部,專門負(fù)責(zé)將SUT內(nèi)部的間接輸出(indirect outputs)傳到外部。它的特點(diǎn)是將內(nèi)部的間接輸出返回給測(cè)試案例,由測(cè)試案例進(jìn)行驗(yàn)證,Test Spy只負(fù)責(zé)獲取內(nèi)部情報(bào),并把情報(bào)發(fā)出去,不負(fù)責(zé)驗(yàn)證情報(bào)的正確性。
使用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內(nèi)部,獲取到SUT內(nèi)部的間接輸出(indirect outputs),不同的是,Mock Object還負(fù)責(zé)對(duì)情報(bào)(indirect outputs)進(jìn)行驗(yàn)證,總部(外部的測(cè)試案例)信任Mock Object的驗(yàn)證結(jié)果。
Mock的測(cè)試框架有很多,比如:NMock,JMock等等。如果使用Mock Object,建議使用現(xiàn)成的Mock框架,因?yàn)榭蚣転槲覀冏隽撕芏喱嵥榈氖虑?#xff0c;我們只需要對(duì)Mock對(duì)象進(jìn)行一些描述。比如,通常Mock框架都會(huì)使用基于行為(Behavior)的描述性調(diào)用方法,即,在調(diào)用SUT前,只需要描述Mock對(duì)象預(yù)期會(huì)接收什么參數(shù),會(huì)執(zhí)行什么操作,返回什么內(nèi)容等,這樣的案例更加具有可讀性。比如下面使用Mock的測(cè)試案例:
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
經(jīng)常,我們會(huì)把Fake Object和Test Stub搞混,因?yàn)樗鼈兌己屯獠繘](méi)有交互,對(duì)內(nèi)部的輸入輸出也不進(jìn)行驗(yàn)證。不同的是,Fake Object并不關(guān)注SUT內(nèi)部的間接輸入(indirect inputs)或間接輸出(indirect outputs),它僅僅是用來(lái)替代一個(gè)實(shí)際的對(duì)象,并且擁有幾乎和實(shí)際對(duì)象一樣的功能,保證SUT能夠正常工作。實(shí)際對(duì)象過(guò)分依賴外部環(huán)境,Fake Object可以減少這樣的依賴。需要使用Fake Object通常符合以下情形:
一個(gè)使用Fake Object的例子是,將一個(gè)依賴實(shí)際數(shù)據(jù)庫(kù)的數(shù)據(jù)庫(kù)訪問(wèn)層對(duì)象替換成一個(gè)基于內(nèi)存,使用Hash Table對(duì)數(shù)據(jù)進(jìn)行管理的數(shù)據(jù)訪問(wèn)層對(duì)象,它具有和實(shí)際數(shù)據(jù)庫(kù)訪問(wèn)層一樣的接口實(shí)現(xiàn)。
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);
}
說(shuō)了這么多,可能更加糊涂了。在實(shí)際使用時(shí),并不需要過(guò)分在意使用的是哪種Test Double。當(dāng)然,作為思考,可以想一想,以前測(cè)試過(guò)程中做的一些所謂的“假的”東西,到底是Dummy Object, Test Stub, Test Spy, Mock Object, 還是Fake Object呢?
總結(jié)
以上是生活随笔為你收集整理的《xUnit Test Patterns》学习笔记6 - Test Double的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux登录密码破解
- 下一篇: WinForm中给DataGridVie