Powermock2.0.0 詳細 總結

1 單元測試

本身認爲,單元測試最重要的做用有以下兩點java

①開發人員實現某個功能或者修補了某個bug,若是有相應的單元測試支持的話,開發人員能夠立刻經過運行單元測試來驗證以前完成的代碼是否正確,而不須要反覆經過發佈war包、啓動jboss、經過瀏覽器輸入數據等繁瑣的步驟來驗證所完成的功能git

②保證你最後的代碼修改不會破壞以前代碼的功能。項目越作越大,代碼愈來愈多,特別涉及到一些公用接口之類的代碼或是底層的基礎庫,誰也不敢保證此次修改的代碼不會破壞以前的功能,因此與此相關的需求會被擱置或推遲,因爲不敢改進代碼,代碼也變得愈來愈難以維護,質量也愈來愈差。而單元測試就是解決這種問題的很好方法(不敢說最好的)。因爲代碼的歷史功能都有相應的單元測試保證,修改了某些代碼之後,經過運行相關的單元測試就能夠驗證出新調整的功能是否有影響到以前的功能。固然要實現到這種程度須要很大的付出,不但要可以達到比較高的測試覆蓋率,並且單元測試代碼的編寫質量也要有保證程序員

2 Junit測試框架

2.1 Junit是什麼

JUnit是一個Java語言的單元測試框架。它由Kent Beck和Erich Gamma創建,逐漸成爲源於Kent Beck的sUnit的xUnit家族中最爲成功的一個JUnit有它本身的JUnit擴展生態圈。多數Java的開發環境都已經集成了JUnit做爲單元測試的工具。github

注意:Junit 測試也是程序員測試,即所謂的白盒測試,它須要程序員知道被測試的代碼如何完成功能,以及完成什麼樣的功能spring

2.2 Junit 能作什麼?

咱們知道 Junit 是一個單元測試框架,那麼使用 Junit 能讓咱們快速的完成單元測試。數據庫

一般咱們寫完代碼想要測試這段代碼的正確性,那麼必須新建一個類,而後建立一個 main() 方法,而後編寫測試代碼。若是須要測試的代碼不少呢?那麼要麼就會建不少main() 方法來測試,要麼將其所有寫在一個 main() 方法裏面。這也會大大的增長測試的複雜度,下降程序員的測試積極性。而 Junit 能很好的解決這個問題,簡化單元測試,寫一點測一點,在編寫之後的代碼中若是發現問題能夠較快的追蹤到問題的緣由,減少迴歸錯誤的糾錯難度。apache

3 Junit測試的侷限性

一、在某些很是複雜的業務邏輯,會準備大量的數據。api

二、有的時候會依賴數據庫,中間件、文件系統等外部環境,這個時候咱們不能控制這些外部依賴的對象。瀏覽器

試想一下,若是咱們依賴真實的數據庫環境,那麼每次的單元測試結果可能都是不同的

爲了解決上述兩個問題,咱們須要使用Mock技術

4 Mock技術

截取一段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主要被用於在單測中,某個對象在測試過程當中有可能依賴於其餘的複雜對象,經過mocks去模擬真實的其餘對象(模塊)去代替你想要去測試的其餘的對象(模塊),若是其餘對象(模塊)是很難從單元測試中剝離開來的話,這是很是有用的

Mock有如下幾個好處:

一、Mock能夠用來解除測試對象對外部服務的依賴(好比數據庫,第三方接口等),使得測試用例能夠獨立運行。無論是傳統的單體應用,仍是如今流行的微服務,這點都特別重要,由於任何外部依賴的存在都會極大的限制測試用例的可遷移性和穩定性。可遷移性是指,若是要在一個新的測試環境中運行相同的測試用例,那麼除了要保證測試對象自身可以正常運行,還要保證全部依賴的外部服務也可以被正常調用。穩定性是指,若是外部服務不可用,那麼測試用例也可能會失敗。經過Mock去除外部依賴以後,無論是測試用例的可遷移性仍是穩定性,都可以上一個臺階。

二、Mock的第二個好處是替換外部服務調用,提高測試用例的運行速度。任何外部服務調用至少是跨進程級別的消耗,甚至是跨系統、跨網絡的消耗,而Mock能夠把消耗下降到進程內。好比原來一次秒級的網絡請求,經過Mock能夠降至毫秒級,整整3個數量級的差異

三、Mock的第三個好處是提高測試效率。這裏說的測試效率有兩層含義。第一層含義是單位時間運行的測試用例數,這是運行速度提高帶來的直接好處。而第二層含義是一個測試人員單位時間建立的測試用例數。如何理解這第二層含義呢?以單體應用爲例,隨着業務複雜度的上升,爲了運行一個測試用例可能須要準備不少測試數據,與此同時還要儘可能保證多個測試用例之間的測試數據互不干擾。爲了作到這一點,測試人員每每須要花費大量的時間來維護一套可運行的測試數據。有了Mock以後,因爲去除了測試用例之間共享的數據庫依賴,測試人員就能夠針對每個或者每一組測試用例設計一套獨立的測試數據,從而很容易的作到不一樣測試用例之間的數據隔離性。而對於微服務,因爲一個微服務可能級聯依賴不少其餘的微服務,運行一個測試用例甚至須要跨系統準備一套測試數據,若是沒有Mock,基本上能夠說是不可能的。所以,無論是單體應用仍是微服務,有了Mock以後,QE就能夠省去大量的準備測試數據的時間,專一於測試用例自己,天然也就提高了單人的測試效率。

5 相關的Mock工具

5.1 Mockito、EasyMock

EasyMock 以及 Mockito 都由於能夠極大地簡化單元測試的書寫過程而被許多人應用在本身的工做中,可是這兩種 Mock 工具都不能夠實現對靜態函數、構造函數、私有函數、Final 函數以及系統函數的模擬,可是這些方法每每是咱們在大型系統中須要的功能。

5.2 powermock

PowerMock是一個擴展了其它如EasyMock等mock框架的、功能更增強大的框架。PowerMock使用一個自定義類加載器和字節碼操做來模擬靜態方法,構造函數,final類和方法,私有方法,去除靜態初始化器等等。經過使用自定義的類加載器,簡化採用的IDE或持續集成服務器不須要作任何改變。熟悉PowerMock支持的mock框架的開發人員會發現PowerMock很容易使用,由於對於靜態方法和構造器來講,整個的指望API是同樣的。PowerMock旨在用少許的方法和註解擴展示有的API來實現額外的功能。目前PowerMock支持EasyMock和Mockito。

5.3 mock底層原理

①Mockito底層使用了動態代理,用到了CGLIB。所以須要被mock的對象,Mockito都會生成一個子類繼承該類,這也就是爲何final類、private方法、static方法不能夠被Mock的緣由

②powermock的底層原理

咱們首先看powermock的依賴

能夠看出來,它有兩個重要的依賴:javassist和objenesis。
javassist是一個修改java字節碼的工具包,objenesis是一個繞過構造方法來實例化一個對象的工具包。由此看來,PowerMock的本質是經過修改字節碼來實現對靜態和final等方法的mock的

下面是PowerMock的簡單實現原理:

  • 當某個測試方法被註解@PrepareForTest標註之後,在運行測試用例時,會建立一個新的org.powermock.core.classloader.MockClassLoader實例,而後加載該測試用例使用到的類(系統類除外)。
  • PowerMock會根據你的mock要求,去修改寫在註解@PrepareForTest裏的class文件(當前測試類會自動加入註解中),以知足特殊的mock需求。例如:去除final方法的final標識,在靜態方法的最前面加入本身的虛擬實現等。
  • 若是須要mock的是系統類的final方法和靜態方法,PowerMock不會直接修改系統類的class文件,而是修改調用系統類的class文件,以知足mock需求。

6 powermock的使用

這裏吐血推薦一本電子書
網盤連接: https://pan.baidu.com/s/1ZndwumRgSTqmtn__RNVosA 密碼:e8w4

7 springboot和powermock整合

必定要注意powermock的版本號,我在這裏就踩了不少坑,血淋淋的教訓啊,因爲公司與springboot繼承用的是最新的powermock,截止2019年4月12日,powermock-module-junit4的Maven地址倉庫最新版本是2.0.0 ,也正是powermock使用的太新了,致使後面遇到的問題,百度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>

7.1 重要註解

@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);
    }

}

下面主要對上面幾個註解作相關解釋:

@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,根據我測試預期的行爲去準備

至此,springboot和powermock的整合就完成了!

7.2 PrepareForTest不能隨便加

首先來看一段代碼:不使用@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提早準備這個類文件,那麼當程序執行的時候,須要的該類的時候,就會使用到powermock準備的類

到目前爲止,讀者可能會認爲 HSSFWorkbook wb = new HSSFWorkbook();將會建立powermock準備的HSSFWorkbook對象,那麼我debug程序,一探究竟

能夠看到,這裏new HSSFWorkbook()對象徹底是一個正常的對象,而非powermock的對象,而且在該類中使用的也是這個真對象

直到運行到MockGateway這個類 纔出現問題,在powermock中會有大量的代理類,攔截器,這些類中會使用到pokwermock的HSSFWorkbook的對象,而非真正的HSSFWorkbook對象,所以會出現問題

7.3 不是全部的類均可以Powermock

一個私有類是徹底能夠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行

咱們發現這是一個編譯期常量,跟ThreadLocal,是沒辦法跟powermock對象一塊兒建立的

切記:powermock對象是沒法改變編譯期常量的

7.4 @InjectMock @Mock區別

@InjectMocks
    private SomeHandler someHandler;
 
    @Mock 或者 @Spy
    private OneDependency oneDependency; // 此mock將被注入到someHandler

這裏的@InjectMocks和@Autowired功能完徹底全同樣,惟一不一樣的是,@InjectMocks可使oneDependency這個Mock對象自動注入到someHandler這個對象中。注意:①@InjectMocks所表示的對象及someHandler是一個普通的對象 ②Mock所表示的對象及oneDependency是一個Mock對象

7.5 @Mock和@MockBean的區別

@MockBean 會被裝配到相關的類中 代替@Autowired

@Mock 不會被裝配到相關的類中 沒法代替@Autowired

7.6 Mock方法中的嵌套方法

Mockito.when(alarmRulesDao.changeAlarmLevel(Mockito.anyInt(),Mockito.anyInt()))
                .thenReturn(-1);

Integer changeNumber = alarmRulesDao.changeAlarmLevel(changeAlarmlevelRequest.getId(), changeAlarmlevelRequest.getAlarmLevel());

即便Mock了changeAlarmLevel方法,其中的

changeAlarmlevelRequest.getId()
changeAlarmlevelRequest.getAlarmLevel()

仍是會正常執行的

7.7 mock對象中的參數不要再作運算

this.getHSSFWorkbook(downloadVO.getSheetName(), downloadList));

mock的時候不能

Mockito.anyString(),Mockito.anyList()

而要

Mockito.any(),Mockito.anyList() 由於mock對象中的參數執行了相關運算

參考資料:

https://zhidao.baidu.com/question/390585793246337165.html

http://www.javashuo.com/article/p-anejzaeq-ke.html

http://www.managershare.com/post/355904

http://www.javashuo.com/article/p-xjcubdfo-bh.html

https://qicen.iteye.com/blog/1928257

請尊重做者勞動成果,轉載請註明出處。以上內容如有侵權,請聯繫做者,當即刪除。
相關文章
相關標籤/搜索