軟件白盒測試之Mockito+JMockit+TestNG單元測試實踐總結(jié)
2018-03-05 16:07:10
3586瀏覽
單元測試實踐背景
·測試環(huán)境定位bug時,需要測試同學(xué)協(xié)助手動發(fā)起相關(guān)業(yè)務(wù)URL請求,開發(fā)進行遠程調(diào)試
問題:
1、遠程調(diào)試影響測試環(huán)境數(shù)據(jù)正常獲取,影響測試同學(xué)測試進度
2、遠程調(diào)試代碼有時并非最新代碼,與本地不一致增加調(diào)試難度,往往需要發(fā)最新的包再調(diào)試
3、controller層請求參數(shù)依賴特定客戶端版本發(fā)起,其他版本回歸驗證,增加模擬操作成本
·依賴第三方系統(tǒng),第三方系統(tǒng)請求不穩(wěn)定或希望第三方接口返回特定數(shù)據(jù)
為什么需要單測
編寫單元測試代碼并不是一件容易的事情,那為什么還需要去話費時間和精力來編寫單元測試呢?
減少Bug:如今的項目大多都是多人分模塊協(xié)同開發(fā),當(dāng)各個模塊集成時再去發(fā)現(xiàn)問題,定位和溝通成本是非常高的,通過單元測試來保證各個模塊的正確性,可以盡早的發(fā)現(xiàn)問題,而不時等到集成時再發(fā)現(xiàn)問題。
放心重構(gòu):如今持續(xù)型的項目越來越多,代碼不斷的在變化和重構(gòu),通過單元測試,開發(fā)可以放心的修改重構(gòu)代碼,減少改代碼時心理負擔(dān),提高重構(gòu)的成功率。
改進設(shè)計:越是良好設(shè)計的代碼,一般越容易編寫單元測試,多個小的方法的單測一般比大方法(成百上千行代碼)的單測代碼要簡單、要穩(wěn)定,一個依賴接口的類一般比依賴具體實現(xiàn)的類容易測試,所以在編寫單測的過程中,如果發(fā)現(xiàn)單測代碼非常難寫,一般表明被測試的代碼包含了太多的依賴或職責(zé),需要反思代碼的合理性,進而推進代碼設(shè)計的優(yōu)化,形成正向循環(huán)。
個人感受,將controller層請求參數(shù)抽取管理后,debug不依賴客戶端與測試環(huán)境,能夠迅速在本地執(zhí)行定位問題;同時,單元測試提供測試數(shù)據(jù)準(zhǔn)備與模擬特定測試數(shù)據(jù)返回,對業(yè)務(wù)測試起輔助作用。
單元測試需要理解的幾個概念
被測系統(tǒng):SUT(SystemUnderTest)
被測系統(tǒng)(Systemundertest,SUT)表示正在被測試的系統(tǒng),目的是測試系統(tǒng)能否正確操作。這一詞語常用于軟件測試中。軟件系統(tǒng)測試的一個特例是對應(yīng)用軟件的測試,稱為被測應(yīng)用程序(applicationundertest,AUT)。
SUT也表明軟件已經(jīng)到了成熟期,因為系統(tǒng)測試在測試周期中是集成測試的后一階段。
測試替身:TestDouble
在單元測試時,使用TestDouble減少對被測對象的依賴,使得測試更加單一。同時,讓測試案例執(zhí)行的時間更短,運行更加穩(wěn)定,同時能對SUT內(nèi)部的輸入輸出進行驗證,讓測試更加徹底深入。但是,TestDouble也不是萬能的,TestDouble不能被過度使用,因為實際交付的產(chǎn)品是使用實際對象的,過度使用TestDouble會讓測試變得越來越脫離實際。
要理解測試替身,需要了解一下DummyObjects、TestStub、TestSpy、FakeObject這幾個概念,下面我們對這些概念分別進行說明。
DummyObjects
DummyObjects泛指在測試中必須傳入的對象,而傳入的這些對象實際上并不會產(chǎn)生任何作用,僅僅是為了能夠調(diào)用被測對象而必須傳入的一個東西。
TestStub
測試樁是用來接受SUT內(nèi)部的間接輸入(indirectinputs),并返回特定的值給SUT??梢岳斫釺estStub是在SUT內(nèi)部打的一個樁,可以按照我們的要求返回特定的內(nèi)容給SUT,TestStub的交互完全在SUT內(nèi)部,因此,它不會返回內(nèi)容給測試案例,也不會對SUT內(nèi)部的輸入進行驗證。
TestSpy
TestSpy像一個間諜,安插在了SUT內(nèi)部,專門負責(zé)將SUT內(nèi)部的間接輸出(indirectoutputs)傳到外部。它的特點是將內(nèi)部的間接輸出返回給測試案例,由測試案例進行驗證,TestSpy只負責(zé)獲取內(nèi)部情報,并把情報發(fā)出去,不負責(zé)驗證情報的正確性。
MockObject
MockObject和TestSpy有類似的地方,它也是安插在SUT內(nèi)部,獲取到SUT內(nèi)部的間接輸出(indirectoutputs),不同的是,MockObject還負責(zé)對情報(intelligence)進行驗證,總部(外部的測試案例)信任MockObject的驗證結(jié)果。
FakeObject
經(jīng)常,我們會把FakeObject和TestStub搞混,因為它們都和外部沒有交互,對內(nèi)部的輸入輸出也不進行驗證。不同的是,F(xiàn)akeObject并不關(guān)注SUT內(nèi)部的間接輸入(indirectinputs)或間接輸出(indirectoutputs),它僅僅是用來替代一個實際的對象,并且擁有幾乎和實際對象一樣的功能,保證SUT能夠正常工作。實際對象過分依賴外部環(huán)境,F(xiàn)akeObject可以減少這樣的依賴。
看完TestDouble這幾個概念后,是不是一頭霧水?以下通俗解釋,DummyObjects就不做解釋了。
TestStub
系統(tǒng)測試需要某一指定數(shù)據(jù)返回時,開發(fā)將獲取數(shù)據(jù)邏輯代碼替換成指定數(shù)據(jù),發(fā)包測試完再替換回原來邏輯。替換代碼返回指定數(shù)據(jù),這就是測試樁。
TestSpy
TestStub只返回指定內(nèi)容給SUT,并沒有指定返回測試案例,所以我們引入單元測試,在單元測試用例調(diào)用引用該插樁的方法。
這時我們能獲測試樁間接輸出內(nèi)容,甚至是報錯信息,再也不用到服務(wù)器查找錯誤日志了,這就是TestSpy。
MockObject
MockObject就是在TestSpy的基礎(chǔ)上,加入驗證機制。調(diào)用引用該插樁的方法,我們要確保這個插樁正常被執(zhí)行或指定執(zhí)行n次,得到的結(jié)果是不是我們期望的結(jié)果,mock就以此為生。
FakeObject
FakeObject相對TestStub,是一個面向?qū)ο蟾拍?。我們只希望替換掉一個實際被引用對象里面的一個方法返回值,被替換某個方法返回值的對象就叫FakeOject,它與實際對象一樣的功能。MockObject也囊括FakeObject概念,可以看出TestStub<FakeObject<MockObject。
Mock框架模型
測試驗證過程,我們不可能每次都修改代碼stub一個方法,發(fā)包驗證完后再改回,發(fā)布外網(wǎng)回歸驗證階段這種操作根本不被允許。Mock框架應(yīng)運而生,我們在單元測試用例stub一個方法后,將之注入被測系統(tǒng)SUT,這個注入只會在testspy階段產(chǎn)生影響。
市面上很多mock框架,Jmockit、Mockito、PowerMock、EasyMock等,大體遵循record-replay-verify模型設(shè)計,有些地方稱之為expect-run-verify模式(期望--運行--驗證),有些地方稱之(AAA階段)Arrange、Act、Assert,大體一個意思。很明顯,Mock框架的應(yīng)用過程,我們先需要指定stub,然后運行被測方法,然后在驗證stub的正確性,這個過程就稱之為mock。
單元測試框架選擇
Testng
TestNG與Junit很相似,但testng更加靈活,以下為兩者對比。
[圖片上傳失敗...(image-93566-1513052813178)]
參考JUnit4VsTestNG比較
·Testng支持分組測試
·Testng參數(shù)化測試支持復(fù)雜類型參數(shù),而junit只支持基本類型
·Testng提供XML靈活配置測試運行套件
·Testng支持依賴測試
·Testng支持并發(fā)測試,上面文章未講到的,補充下。如@Test(threadPoolSize=3,invocationCount=6,timeout=500),而Junit的話可以引入JunitPref框架。
Jmockit
Jmockit是一個功能很強大的框架,可以mock靜態(tài)方法、final類、抽象類、接口、構(gòu)造函數(shù)等,幾乎無所不能,但編程語言不夠簡潔。
Jmockit的介紹和使用
這里需要補充的點:
·注解@Tested,標(biāo)識的被測對象實例,@Injectable的實例會自動注入到@Tested中,有時候在事件過程中實在無法注入,可以借助spring的反射工具ReflectionTestUtils進行注入。
·Expectations:期望,指定的方法必須被調(diào)用,且方法默認次數(shù)為1。如果指定打樁的方法在test用例不被調(diào)用,或者調(diào)用次數(shù)超過1,則會報錯,建議使用NonStrictExpectations配合Verifications使用。
·Expectations(T)/NonStrictExpectations(T),Expectations(.class){}這種方式只會模擬區(qū)域中包含的方法,這個類的其它方法將按照正常的業(yè)務(wù)邏輯運行,T就變成了一個FakeObject。
·MockUp(T)中,未mock的函數(shù)不受影響,T也是一個FakeObject。通常rpc接口(接口無具體實現(xiàn)方法)、構(gòu)造函數(shù)通過MockUp進行局部方法mock。
以下主要演示一個rpc接口的mock。
publicclassColumnArticlesControllerTest2extendsBaseContorllerMockTest{
privateMockMvcmockMvc;
@Autowired
privateConfigServiceconfigService;
@Autowired
privateICpDataKievHandlercpDataKievHandler;
@Autowired
privateIndexArticlesDaoCacheImplindexArticlesDao;
@Autowired
privateColumnArticlesControllercolumnArticlesController;
@BeforeMethod()
publicvoidsetUp()throwsException{
mockMvc=MockMvcBuilders.standaloneSetup(columnArticlesController).build();
}
//CSV最好使用gbk格式,目前不支持默認路徑,CSV文件位于到dataprovider目錄下
@Test(description="測試list.do接口",dataProvider="genData",dataProviderClass=CommonDataProvider.class)
@Csv("/dataprovider/ColumnArticlesControllerTest/testGetColumnArticleList.csv")
publicvoidtestGetColumnArticleList(StringcpChannelId,longcolumnId,StringucParam,Integerv,Stringflymeuid,
Stringnt,Stringvn,Stringdeviceinfo,StringdeviceType,Stringos,IntegersupportSDK,IntegercpType)
throwsException{
Stringimei=deviceinfo.substring(deviceinfo.indexOf("imei="),deviceinfo.indexOf("&"));
ArticleViewparams=newArticleView();
params.setCpChannelId(cpChannelId);
params.setColumnId(columnId);
params.setUcparam(ucParam);
params.setClientReqId(System.currentTimeMillis()+imei);
CommonParamscommonParams=newCommonParams();
commonParams.setV(v);
commonParams.setFlymeuid(flymeuid);
commonParams.setNt(nt);
commonParams.setVn(vn);
commonParams.setDeviceinfo(DeviceUtil.deviceToEncrypt(deviceinfo));
commonParams.setDeviceType(deviceType);
commonParams.setOs(os);
System.out.println(configService.getConfigValue(ConfigKeyEnum.UC_VIDEO_PER));
//jmock靜態(tài)方法mock掉ip,防止http請求獲取Ip報錯
newNonStrictExpectations(WebUtils.class,configService){
{
WebUtils.getClientIp();
result="172.17.132.66";
}
{
//后臺控制百分比,返回0則過濾掉類型為27的視頻,返回100則放開下發(fā)該視頻“XXX鍵盤”
configService.getConfigValue(ConfigKeyEnum.UC_VIDEO_PER);
result="100";
}
};
finalICpDataKievHandlercpDataKievHandler2=cpDataKievHandler;
try{
Stringvideo27Articles=FileUtils
.getFileText(FileUtils.getCurrentProjectPath()+"/src/test/resources/afdata/video27Articles.json");
finalCpDataResultvalue=JSON.parseObject(video27Articles,CpDataResult.class);
cpDataKievHandler=newMockUp<ICpDataKievHandler>(){
@mockit.Mock
CpDataResultgetUCArticleList(Stringimei,longchannelId,Stringmethod,Stringrecoid,longftime,
StringcityCode,StringcityName,intpageSize){
returnvalue;
}
}.getMockInstance();
ReflectionTestUtils.setField(indexArticlesDao,"cpDataKievHandler",cpDataKievHandler);
System.out.println(JSON
.toJSON(columnArticlesController.getColumnArticleList(params,supportSDK,cpType,commonParams)));
}finally{
//mock完還原接口方法取值,避免影響其他用例
ReflectionTestUtils.setField(indexArticlesDao,"cpDataKievHandler",cpDataKievHandler2);
}
}
以上就是關(guān)于扣丁學(xué)堂軟件測試培訓(xùn)之Mockito+JMockit+TestNG單元測試實踐總結(jié)的詳細介紹,最后想要了解更多關(guān)于軟件測試培訓(xùn)發(fā)展前景趨勢,請關(guān)注扣丁學(xué)堂官網(wǎng)、微信等平臺,扣丁學(xué)堂IT職業(yè)在線學(xué)習(xí)教育平臺為您提供權(quán)威的軟件測試視頻教程系統(tǒng),通過千鋒扣丁學(xué)堂金牌講師在線錄制的軟件測試在線視頻教程,讓你快速掌握軟件測試從入門到精通開發(fā)實戰(zhàn)技能。
【關(guān)注微信公眾號獲取更多學(xué)習(xí)資料】
查看更多關(guān)于“軟件測試技術(shù)資訊”的相關(guān)文章>>
標(biāo)簽:
軟件測試培訓(xùn)
軟件測試工程師
軟件測試在線視頻
軟件測試視頻教程
軟件測試教程
白盒測試
黑盒測試