目錄
自己認為,單元測試最重要的作用有如下兩點
①開發(fā)人員實現(xiàn)某個功能或者修補了某個bug,如果有相應(yīng)的單元測試支持的話,開發(fā)人員可以馬上通過運行單元測試來驗證之前完成的代碼是否正確,而不需要反復(fù)通過發(fā)布war包、啟動jboss、通過瀏覽器輸入數(shù)據(jù)等繁瑣的步驟來驗證所完成的功能
②保證你最后的代碼修改不會破壞之前代碼的功能。項目越做越大,代碼越來越多,特別涉及到一些公用接口之類的代碼或是底層的基礎(chǔ)庫,誰也不敢保證這次修改的代碼不會破壞之前的功能,所以與此相關(guān)的需求會被擱置或推遲,由于不敢改進代碼,代碼也變得越來越難以維護,質(zhì)量也越來越差。而單元測試就是解決這種問題的很好方法(不敢說最好的)。由于代碼的歷史功能都有相應(yīng)的單元測試保證,修改了某些代碼以后,通過運行相關(guān)的單元測試就可以驗證出新調(diào)整的功能是否有影響到之前的功能。當然要實現(xiàn)到這種程度需要很大的付出,不但要能夠達到比較高的測試覆蓋率,而且單元測試代碼的編寫質(zhì)量也要有保證
JUnit是一個Java語言的單元測試框架。它由Kent Beck和Erich Gamma建立,逐漸成為源于Kent Beck的sUnit的xUnit家族中最為成功的一個JUnit有它自己的JUnit擴展生態(tài)圈。多數(shù)Java的開發(fā)環(huán)境都已經(jīng)集成了JUnit作為單元測試的工具。
注意:Junit 測試也是程序員測試,即所謂的白盒測試,它需要程序員知道被測試的代碼如何完成功能,以及完成什么樣的功能
我們知道 Junit 是一個單元測試框架,那么使用 Junit 能讓我們快速的完成單元測試。
通常我們寫完代碼想要測試這段代碼的正確性,那么必須新建一個類,然后創(chuàng)建一個 main() 方法,然后編寫測試代碼。如果需要測試的代碼很多呢?那么要么就會建很多main() 方法來測試,要么將其全部寫在一個 main() 方法里面。這也會大大的增加測試的復(fù)雜度,降低程序員的測試積極性。而 Junit 能很好的解決這個問題,簡化單元測試,寫一點測一點,在編寫以后的代碼中如果發(fā)現(xiàn)問題可以較快的追蹤到問題的原因,減小回歸錯誤的糾錯難度。
1、在某些非常復(fù)雜的業(yè)務(wù)邏輯,會準備大量的數(shù)據(jù)。
2、有的時候會依賴數(shù)據(jù)庫,中間件、文件系統(tǒng)等外部環(huán)境,這個時候我們不能控制這些外部依賴的對象。
試想一下,如果我們依賴真實的數(shù)據(jù)庫環(huán)境,那么每次的單元測試結(jié)果可能都是不一樣的
為了解決上述兩個問題,我們需要使用Mock技術(shù)
截取一段stackflow中的解釋:
Mocking isprimarily used in unit testing. An object under test may have dependencies onother (complex) objects. To isolate the behaviour of the object you want totest you replace the other objects by mocks that simulate the behavior of thereal objects. This is useful if the real objects are impractical to incorporateinto the unit test.
MOCK主要被用于在單測中,某個對象在測試過程中有可能依賴于其他的復(fù)雜對象,通過mocks去模擬真實的其他對象(模塊)去代替你想要去測試的其他的對象(模塊),如果其他對象(模塊)是很難從單元測試中剝離開來的話,這是非常有用的
Mock有以下幾個好處:
1、Mock可以用來解除測試對象對外部服務(wù)的依賴(比如數(shù)據(jù)庫,第三方接口等),使得測試用例可以獨立運行。不管是傳統(tǒng)的單體應(yīng)用,還是現(xiàn)在流行的微服務(wù),這點都特別重要,因為任何外部依賴的存在都會極大的限制測試用例的可遷移性和穩(wěn)定性??蛇w移性是指,如果要在一個新的測試環(huán)境中運行相同的測試用例,那么除了要保證測試對象自身能夠正常運行,還要保證所有依賴的外部服務(wù)也能夠被正常調(diào)用。穩(wěn)定性是指,如果外部服務(wù)不可用,那么測試用例也可能會失敗。通過Mock去除外部依賴之后,不管是測試用例的可遷移性還是穩(wěn)定性,都能夠上一個臺階。
2、Mock的第二個好處是替換外部服務(wù)調(diào)用,提升測試用例的運行速度。任何外部服務(wù)調(diào)用至少是跨進程級別的消耗,甚至是跨系統(tǒng)、跨網(wǎng)絡(luò)的消耗,而Mock可以把消耗降低到進程內(nèi)。比如原來一次秒級的網(wǎng)絡(luò)請求,通過Mock可以降至毫秒級,整整3個數(shù)量級的差別
3、Mock的第三個好處是提升測試效率。這里說的測試效率有兩層含義。第一層含義是單位時間運行的測試用例數(shù),這是運行速度提升帶來的直接好處。而第二層含義是一個測試人員單位時間創(chuàng)建的測試用例數(shù)。如何理解這第二層含義呢?以單體應(yīng)用為例,隨著業(yè)務(wù)復(fù)雜度的上升,為了運行一個測試用例可能需要準備很多測試數(shù)據(jù),與此同時還要盡量保證多個測試用例之間的測試數(shù)據(jù)互不干擾。為了做到這一點,測試人員往往需要花費大量的時間來維護一套可運行的測試數(shù)據(jù)。有了Mock之后,由于去除了測試用例之間共享的數(shù)據(jù)庫依賴,測試人員就可以針對每一個或者每一組測試用例設(shè)計一套獨立的測試數(shù)據(jù),從而很容易的做到不同測試用例之間的數(shù)據(jù)隔離性。而對于微服務(wù),由于一個微服務(wù)可能級聯(lián)依賴很多其他的微服務(wù),運行一個測試用例甚至需要跨系統(tǒng)準備一套測試數(shù)據(jù),如果沒有Mock,基本上可以說是不可能的。因此,不管是單體應(yīng)用還是微服務(wù),有了Mock之后,QE就可以省去大量的準備測試數(shù)據(jù)的時間,專注于測試用例本身,自然也就提升了單人的測試效率。
EasyMock 以及 Mockito 都因為可以極大地簡化單元測試的書寫過程而被許多人應(yīng)用在自己的工作中,但是這兩種 Mock 工具都不可以實現(xiàn)對靜態(tài)函數(shù)、構(gòu)造函數(shù)、私有函數(shù)、Final 函數(shù)以及系統(tǒng)函數(shù)的模擬,但是這些方法往往是我們在大型系統(tǒng)中需要的功能。
PowerMock是一個擴展了其它如EasyMock等mock框架的、功能更加強大的框架。PowerMock使用一個自定義類加載器和字節(jié)碼操作來模擬靜態(tài)方法,構(gòu)造函數(shù),final類和方法,私有方法,去除靜態(tài)初始化器等等。通過使用自定義的類加載器,簡化采用的IDE或持續(xù)集成服務(wù)器不需要做任何改變。熟悉PowerMock支持的mock框架的開發(fā)人員會發(fā)現(xiàn)PowerMock很容易使用,因為對于靜態(tài)方法和構(gòu)造器來說,整個的期望API是一樣的。PowerMock旨在用少量的方法和注解擴展現(xiàn)有的API來實現(xiàn)額外的功能。目前PowerMock支持EasyMock和Mockito。
①Mockito底層使用了動態(tài)代理,用到了CGLIB。因此需要被mock的對象,Mockito都會生成一個子類繼承該類,這也就是為什么final類、private方法、static方法不可以被Mock的原因
②powermock的底層原理
我們首先看powermock的依賴
下面是PowerMock的簡單實現(xiàn)原理:
當某個測試方法被注解@PrepareForTest標注以后,在運行測試用例時,會創(chuàng)建一個新的org.powermock.core.classloader.MockClassLoader實例,然后加載該測試用例使用到的類(系統(tǒng)類除外)。
PowerMock會根據(jù)你的mock要求,去修改寫在注解@PrepareForTest里的class文件(當前測試類會自動加入注解中),以滿足特殊的mock需求。例如:去除final方法的final標識,在靜態(tài)方法的最前面加入自己的虛擬實現(xiàn)等。
如果需要mock的是系統(tǒng)類的final方法和靜態(tài)方法,PowerMock不會直接修改系統(tǒng)類的class文件,而是修改調(diào)用系統(tǒng)類的class文件,以滿足mock需求。
這里吐血推薦一本電子書
網(wǎng)盤鏈接: https://pan.baidu.com/s/1ZndwumRgSTqmtn__RNVosA 密碼:e8w4
一定要注意powermock的版本號,我在這里就踩了很多坑,血淋淋的教訓(xùn)啊,由于公司與springboot繼承用的是最新的powermock,截止2019年4月12日,powermock-module-junit4的Maven地址倉庫最新版本是2.0.0 ,也正是powermock使用的太新了,導(dǎo)致后面遇到的問題,百度google根本無法解決(因為他們都還停留在1.x版本中),最后也是通過github官方文檔才最終解決的
<dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>2.0.0</version><scope>test</scope></dependency>
@SpringBootTest // 表明這是一個springboot測試類,會自動加載springboot主啟動程序@RunWith(PowerMockRunner.class) //使用powermock自己的Runner@PowerMockRunnerDelegate(SpringRunner.class) //將powermock整合到spring容器中@PowerMockIgnore({"javax.*.*", "com.sun.*", "org.xml.*", "org.apache.*"})@PrepareForTest({HSSFWorkbook.class,HSSFCellStyle.class})public class Demo { @Test public void test() throws Exception { EmployeeService service = PowerMockito.mock(EmployeeService.class); PowerMockito.when(service.hello()).thenReturn(999); int result = service.hello(); Assert.assertEquals(999, result); }}
下面主要對上面幾個注解做相關(guān)解釋:
@SpringBootTest:表明這是一個springboot測試類,會自動加載springboot主啟動程序
@RunWith(PowerMockRunner.class): 使用powermock自己的Runner
@PowerMockRunnerDelegate(SpringRunner.class): 將powermock整合到spring容器中
@PowerMockIgnore({"javax.*
.*
", "com.sun.", "org.xml.", "org.apache.*"}) : 這個注解很重要,這也是powermock2.0.0與1.x版本重大不一樣的地方,因為powermock自帶一個類加載器,使用該注解來禁止powermock類加載器加載一些類,避免和JVM類加載器沖突
@PrepareForTest({HSSFWorkbook.class,HSSFCellStyle.class}): 這個注解是告訴PowerMock為我提前準備一個xxx的class,根據(jù)我測試預(yù)期的行為去準備
至此,springboot和powermock的整合就完成了!
首先來看一段代碼:不使用@PrepareForTest
@SpringBootTest@RunWith(PowerMockRunner.class)@PowerMockRunnerDelegate(SpringRunner.class)@PowerMockIgnore({"javax.*.*", "com.sun.*", "org.xml.*", "org.apache.*"})// @PrepareForTest({HSSFWorkbook.class,HSSFCellStyle.class}) 不使用該注解public class Demo { @Test public void test() throws Exception{ HSSFWorkbook wb = new HSSFWorkbook(); wb.createSheet(); }}
程序運行成功!
使用@PrepareForTest
@SpringBootTest@RunWith(PowerMockRunner.class)@PowerMockRunnerDelegate(SpringRunner.class)@PowerMockIgnore({"javax.*.*", "com.sun.*", "org.xml.*", "org.apache.*"})@PrepareForTest({HSSFWorkbook.class}) public class Demo { @Test public void test() throws Exception{ HSSFWorkbook wb = new HSSFWorkbook(); wb.createSheet(); }}
我們在test()測試中完全沒有用到powermock,但是為什么會失敗呢?
原因:@PrepareForTest中的HSSFWorkbook.class,會告訴powermock提前準備這個類文件,那么當程序執(zhí)行的時候,需要的該類的時候,就會使用到powermock準備的類
到目前為止,讀者可能會認為 HSSFWorkbook wb = new HSSFWorkbook();
將會創(chuàng)建powermock準備的HSSFWorkbook對象,那么我debug程序,一探究竟
可以看到,這里new HSSFWorkbook()對象完全是一個正常的對象,而非powermock的對象,并且在該類中使用的也是這個真對象
直到運行到MockGateway這個類 才出現(xiàn)問題,在powermock中會有大量的代理類,攔截器,這些類中會使用到pokwermock的HSSFWorkbook的對象,而非真正的HSSFWorkbook對象,因此會出現(xiàn)問題
一個私有類是完全可以powermock的,那么是不是所有的類都可以powermock嗎?
答案是:否定的()
@SpringBootTest@RunWith(PowerMockRunner.class)@PowerMockRunnerDelegate(SpringRunner.class)@PowerMockIgnore({"javax.*.*", "com.sun.*", "org.xml.*", "org.apache.*"})@PrepareForTest({HSSFWorkbook.class})public class Demo { @Test public void test3() throws Exception{ PowerMockito.mock(HSSFCellStyle.class); } }
定位至HSSFCellStyle 133行
我們發(fā)現(xiàn)這是一個編譯期常量,跟ThreadLocal,是沒辦法跟powermock對象一起創(chuàng)建的
切記:powermock對象是無法改變編譯期常量的
@InjectMocks private SomeHandler someHandler; @Mock 或者 @Spy private OneDependency oneDependency; // 此mock將被注入到someHandler
這里的@InjectMocks和@Autowired功能完完全全一樣,唯一不同的是,@InjectMocks可以使oneDependency這個Mock對象自動注入到someHandler這個對象中。注意:①@InjectMocks所表示的對象及someHandler是一個普通的對象 ②Mock所表示的對象及oneDependency是一個Mock對象
@MockBean 會被裝配到相關(guān)的類中 代替@Autowired
@Mock 不會被裝配到相關(guān)的類中 無法代替@Autowired
Mockito.when(alarmRulesDao.changeAlarmLevel(Mockito.anyInt(),Mockito.anyInt())) .thenReturn(-1);Integer changeNumber = alarmRulesDao.changeAlarmLevel(changeAlarmlevelRequest.getId(), changeAlarmlevelRequest.getAlarmLevel());
即使Mock了changeAlarmLevel方法,其中的
changeAlarmlevelRequest.getId()changeAlarmlevelRequest.getAlarmLevel()
還是會正常執(zhí)行的
this.getHSSFWorkbook(downloadVO.getSheetName(), downloadList));
mock的時候不能
Mockito.anyString(),Mockito.anyList()
而要
Mockito.any(),Mockito.anyList() 因為mock對象中的參數(shù)執(zhí)行了相關(guān)運算
參考資料:
https://zhidao.baidu.com/question/390585793246337165.html
https://www.cnblogs.com/ysocean/p/6889906.html
http://www.managershare.com/post/355904
https://www.cnblogs.com/hunterCecil/p/5721468.html
https://qicen.iteye.com/blog/1928257
請尊重作者勞動成果,轉(zhuǎn)載請注明出處。以上內(nèi)容若有侵權(quán),請聯(lián)系作者,立即刪除。來源:http://www.icode9.com/content-4-162901.html