JaCoCo、Mockito來做單元測試真香!
JaCoCo、Mockito來做單元測試真香!
一、單元測試
1.1 單元測試說明
定義:單元測試(unit testing)是用來對一個模組、一個函式或者一個類來進行正確性檢驗的測試工作,在軟體開發過程中要進行的最低級別的測試活動,軟體的獨立單元將在與程式的其他部分相隔離的情況下進行測試。
優缺點: 單元測試從長期來看,可以提高程式碼質量,減少維護成本,減少除錯時間,降低重構難度。但是從短期來看,加大了工作量,對於進度緊張的專案中的開發人員來說,可能會成為不少的負擔。
1.2 什麼時候寫單元測試?
寫單元測試的時機不外乎三種情況:
1、在具體實現程式碼之前。這是測試驅動開發(TDD)所提倡的;
2、與具體實現程式碼同步進行。先寫少量功能程式碼,緊接著寫單元測試(重複這兩個過程,直到完成功能程式碼開發)。其實這種方案跟第一種已經很接近,基本上功能程式碼開發完,單元測試也差不多完成了。
3、編寫完功能程式碼再寫單元測試。事後編寫的單元測試“粒度”都比較粗。
建議:推薦單元測試與具體實現程式碼同步進行這個方案的。只有對需求有一定的理解後才能知道什麼是程式碼的正確性,才能寫出有效的單元測試來驗證正確性,而能寫出一些功能程式碼則說明對需求有一定理解了。
1.3 單元測試程式碼覆蓋率
程式碼覆蓋率 = 程式碼的覆蓋程度,一種度量方式。
在做單元測試時,程式碼覆蓋率常常被拿來作為衡量測試好壞的指標,甚至,用程式碼覆蓋率來考核測試任務完成情況,比如,程式碼覆蓋率必須達到80%或 90%。
1.4 單元測試覆蓋方式
- 語句覆蓋(StatementCoverage)
又稱行覆蓋(LineCoverage),段覆蓋(SegmentCoverage),基本塊覆蓋(BasicBlockCoverage),這是最常用也是最常見的一種覆蓋方式,就是度量被測程式碼中每個可執行語句是否被執行到了。它只管覆蓋程式碼中的執行語句,卻不考慮各種分支的組合等等。但是,換來的測試效果不明顯,很難更多地發現程式碼中的問題。舉個簡單的例子:
int divide(int a, int b){
return a / b;
}
TeseCase: a = 10, b = 5 。測試結果會告訴你,程式碼覆蓋率達到了100%,並且所有測試案例都通過了。然而遺憾的是,我們的語句覆蓋率達到了所謂的100%,但是卻沒有發現最簡單的Bug,比如,當我讓b=0時,會丟擲一個除零異常。
- 判定(分支)覆蓋(DecisionCoverage)
又稱分支覆蓋(BranchCoverage),所有邊界覆蓋(All-EdgesCoverage),基本路徑覆蓋(BasicPathCoverage),判定路徑覆蓋(Decision-Decision-Path)。它度量程式中每一個判定的分支是否都被測試到了。
3.條件覆蓋(ConditionCoverage)
它度量判定中的每個子表示式結果true和false是否被測試到了。
判定覆蓋和條件覆蓋的區別舉例:
int foo(int a, int b){
if (a < 10 || b < 10){ // 判定
return 0; // 分支一
}else{
return 1; // 分支二
}
}
設計判定覆蓋案例時,我們只需要考慮判定結果為true和false兩種情況,因此,我們設計如下的案例就能達到判定覆蓋率100%:
TestCaes1: a = 5, b = 任意數字 //覆蓋了分支一
TestCaes2: a = 15, b = 15 //覆蓋了分支二
設計條件覆蓋案例時,我們需要考慮判定中的每個條件表示式結果,為了覆蓋率達到100%,我們設計瞭如下的案例:(全部為true和全部為false)
TestCase1: a = 5, b = 5 true, true
TestCase2: a = 15, b = 15 false, false
需要特別注意的是:條件覆蓋不是將判定中的每個條件表示式的結果進行排列組合,而是隻要每個條件表示式的結果true和false測試到了就OK了。因此,我們可以這樣推論:完全的條件覆蓋並不能保證完全的判定覆蓋。比如上面的例子,假如我設計的案例為:
TestCase1: a = 5, b = 15 true, false 分支一
TestCase1: a = 15, b = 5 false, true 分支一
可以看到,雖然我們完整的做到了條件覆蓋,但是我們卻沒有做到完整的判定覆蓋,我們只覆蓋了分支一。
- 路徑覆蓋(PathCoverage)
又稱斷言覆蓋(PredicateCoverage)。它度量了是否函式的每一個分支都被執行了。就是所有可能的分支都執行一遍,有多個分支巢狀時,需要對多個分支進行排列組合,可想而知,測試路徑隨著分支的數量指數級別增加。
1.5 四種單元測試覆蓋的比較:
int foo(int a, int b){
int nReturn = 0;
if (a < 10){ // 分支一
nReturn += 1;
}
if (b < 10){ // 分支二
nReturn += 10;
}
return nReturn;
}
a. 語句覆蓋 語句覆蓋率100%
TestCase a = 5, b = 5 nReturn = 11
b. 判定覆蓋 判定覆蓋率100%
TestCase1 a = 5, b = 5 nReturn = 11
TestCase2 a = 15, b = 15 nReturn = 0
c. 條件覆蓋 條件覆蓋率100%
TestCase1 a = 5, b = 15 nReturn = 1 true fasle
TestCase2 a = 15, b = 5 nReturn = 10 false true
上面三種覆蓋率結果看起來都都達到了100%!但是,nReturn的結果一共有四種可能的返回值:0,1,10,11,而我們上面的針對每種覆蓋率設計的測試案例只覆蓋了部分返回值,並沒有測試完全。
d.路徑覆蓋 將所有可能的返回值都測試到了
TestCase1 a = 5, b = 5 nReturn = 0
TestCase2 a = 15, b = 5 nReturn = 1
TestCase3 a = 5, b = 15 nReturn = 10
TestCase4 a = 15, b = 15 nReturn = 11
其他程式碼覆蓋率的指標還有函式覆蓋率(function coverage);呼叫覆蓋率(call coverage);迴圈覆蓋率(loop coverage)等等,不同的公司對不同的覆蓋率有不同的要求。
注意:
- 不要一味的追求資料,為了提高程式碼測試覆蓋率而去設計測試案例;
- 要以程式碼測試覆蓋率為參考,讓它來幫助我們設計更加有意義,高效的測試用例;
二、Jacoco技術調研
2.1 程式碼測試工具說明
idea中支援三種外掛來檢視程式碼覆蓋率,每種外掛統計明細各有千秋,分別是idea自帶外掛Coverage、JaCoCo、Emma。目前市場上主要程式碼覆蓋率工具有:
-
Emma 通過對編譯後的 Java 位元組碼檔案進行插樁,在測試執行過程中收集覆蓋率資訊,並通過支援多
種報表格式對覆蓋率結果進行展示。
-
Cobertura 配置內容很豐富
-
Jacoco Jacoco 也是 Emma 團隊開發,可以認為是Emma的升級版
-
Clover(商用) 最早的JAVA測試程式碼覆蓋率工具之一,收費
工具 | Jacoco | Emma | Cobertura |
---|---|---|---|
原理 | 使用 ASM(致力於位元組碼操作和分析的框架,它可用來修改一個已存在的類或者動態產生一個新的類)修改位元組碼 | 修改 jar 檔案,class 檔案位元組碼檔案 | 基於 jcoverage,基於 asm 框架對 class 檔案插樁 |
覆蓋粒度 | 行,類,方法,指令,分支 | 行,類,方法,基本塊,指令,無分支覆蓋 | 專案,包,類,方法的語句覆蓋/分支覆蓋 |
生成結果 | html、csv、xml | html、xml、txt,二進位制格式報表 | html,xml |
缺點 | 需要原始碼 | 1、需要 debug 版本,並打來 build.xml 中的 debug 編譯項; 2、需要原始碼,且必須與插樁的程式碼完全一致 | 1、不能捕獲測試用例中未考慮的異常; 2、關閉伺服器才能輸出覆蓋率資訊(已有修改原始碼的解決方案,定時輸出結果; |
效能 | 快 | 小巧 | 插入的位元組碼資訊更多 |
執行方式 | maven,ant,命令列 | 命令列 | maven,ant |
Jenkins 整合 | 生成 html 報告,直接與 hudson 整合,展示報告,無趨勢圖 | 無法與 hudson 整合 | 有整合的外掛,美觀的報告,有趨勢圖 |
報告實時性 | 預設關閉,可以動態從 jvm dump 出資料 | 可以不關閉伺服器 | 預設是在關閉伺服器時才寫結果 |
維護狀態 | 持續更新中 | 停止維護 | 停止維護 |
注:Hudson是Jenkins的前身,是基於Java開發的一種持續整合工具,用於監控程式重複的工作。
2.2 Jacoco優勢
- Jacoco是一個開源的覆蓋率工具,社群比較活躍,官網也在不斷的維護更新。
- 它針對的開發語言是java,可嵌入到Ant、Maven中,或者作為Idea和Eclipse的外掛使用。
- 很多第三方的工具提供了對Jacoco的整合,如sonar(程式碼質量管理的開源平臺)、Jenkins等。
- 可以使用JavaAgent (實現位元組碼層面的程式碼修改) 技術監控Java程式。
2.3 Jacoco基本概念
行覆蓋率:度量被測程式的每行程式碼是否被執行,判斷標準行中是否至少有一個指令被執行。
類覆蓋率:度量計算class類檔案是否被執行。
分支覆蓋率:度量if和switch語句的分支覆蓋情況,計算一個方法裡面的總分支數,確定執行和不執行的 分支數量。
方法覆蓋率:度量被測程式的方法執行情況,是否執行取決於方法中是否有至少一個指令被執行。
指令覆蓋:計數單元是單個java二進位制程式碼指令,指令覆蓋率提供了程式碼是否被執行的資訊,度量完全獨立原始碼格式。
圈複雜度:程式碼複雜度的衡量標準,在(線性)組合中,計算在一個方法裡面所有可能路徑的最小數目,缺失的複雜度同樣表示測試案例沒有完全覆蓋到這個模組。 簡單來說就是覆蓋所有的可能情況最少使用的測試用例數。
2.4 Jacoco原理
(1) JaCoCo在Byte Code時使用的ASM技術修改位元組碼方法,可以修改Jar檔案、class檔案位元組碼檔案。
(2) JaCoCo通過注入來修改和生成java位元組碼,使用的是ASM庫。
(3) JaCoCo同時支援on-the-fly(及時)和offline(離線)的兩種插樁模式。
On-the-fly插樁:
JVM通過-javaagent引數指定特定的jar檔案啟動Instrumentation代理程式,代理程式在裝載class檔案前判斷是否已經轉換修改了該檔案,若沒有則將探針(統計程式碼)插入class檔案,最後在JVM執行測試程式碼的過程中完成對覆蓋率的分析。
Offline模式:
先對位元組碼檔案進行插樁,然後執行插樁後的位元組碼檔案,生成覆蓋資訊並匯出報告。
On-the-fly和offline比較:
On-the-fly無需提前進行位元組碼插樁,無需停機(offline需要停機),可以實時獲取覆蓋率。
存在如下情況不適合on-the-fly,需要採用offline提前對位元組碼插樁:
(1) 執行環境不支援java agent。
(2) 部署環境不允許設定JVM引數。
(3) 位元組碼需要被轉換成其他的虛擬機器如Android Dalvik VM。
(4) 動態修改位元組碼過程中和其他agent衝突。
(5) 無法自定義使用者載入類。
2.5 Jacoco使用
Jacoco支援Apache Ant、命令列、Apache Maven方式使用,這裡只對在IDEA中Maven使用說明。
1 . Maven配置
(1)pom.xml檔案中新增依賴
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
</dependency>
(2)配置plugins外掛資訊(比較詳細的配置,包括覆蓋率和缺失率的詳細設定)
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
<configuration>
<destFile>target/coverage-reports/jacoco-unit.exec</destFile>
<dataFile>target/coverage-reports/jacoco-unit.exec</dataFile>
<includes>
<include>**/service/**</include>
<include>**/controller/**</include>
<!--<include>**/service/impl/*.class</include>-->
</includes>
<!-- rules裡面指定覆蓋規則 -->
<rules>
<rule implementation="org.jacoco.maven.RuleConfiguration">
<element>BUNDLE</element>
<limits>
<!-- 指定方法覆蓋到50% -->
<limit implementation="org.jacoco.report.check.Limit">
<counter>METHOD</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
<!-- 指定分支覆蓋到50% -->
<limit implementation="org.jacoco.report.check.Limit">
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
<!-- 指定類覆蓋到100%,不能遺失任何類 -->
<limit implementation="org.jacoco.report.check.Limit">
<counter>CLASS</counter>
<value>MISSEDCOUNT</value>
<maximum>0</maximum>
</limit>
</limits>
</rule>
</rules>
</configuration>
<executions>
<execution>
<id>jacoco-initialize</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<!--這個check:對程式碼進行檢測,控制專案構建成功還是失敗-->
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
</execution>
<!--這個report:對程式碼進行檢測,然後生成index.html在 target/site/index.html中可以檢視檢測的詳細結果-->
<execution>
<id>jacoco-site</id>
<phase>package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
在rule中配置的規則,有的是 COVEREDRATIO,有的是MISSEDCOUNT,這說明有的統計的是覆蓋率,有的統計的是丟失率(也即未覆蓋到的)。
我們可以在pom檔案的plugin裡面配置rule規則和check 目標,所以在覆蓋率不滿足的情況下,mvn install是不會成功的,並且會報錯。
上面配置主要做了一下的構建過程:
(1) 專案已jar包方式打包,引入junit和jacoco。
(2) Build時執行instrument、report、check。
(3) 覆蓋率生成到target/jacoco.exec
當JaCoCo外掛配置好以後,要獲得 JaCoCo的統計資料,就要執行mvn install 命令。執行完以後,target/site/jacoco/目錄下會生成一個index.html檔案,這是統計資料總覽頁面,可以在瀏覽器開啟檢視。
注:如果沒生成site目錄,則需要手動通過Jacoco外掛點選jacoco:report生成index.html
2.6 Jacoco報告分析
1、 報告文件分析
Jacoco 包含了多種尺度的覆蓋率計數器,包含指令級(Instructions,C0 coverage),分支(Branches,C1 coverage)、圈複雜度(Cyclomatic Complexity)、行(Lines)、方法(Non-abstract Methods)、類(Classes)。
-
Instructions: Jacoco 計算的最小單位就是位元組碼指令。指令覆蓋率表明了在所有的指令中,哪些被執行過以及哪些沒有被執行。這項指數完全獨立於原始碼格式並且在任何情況下有效,不需要類檔案的除錯資訊。
-
Branches: Jacoco 對所有的 if 和 switch 指令計算了分支覆蓋率。這項指標會統計所有的分支數量,並同時支出哪些分支被執行,哪些分支沒有被執行。這項指標也在任何情況都有效。異常處理不考慮在分支範圍內。
-
Cyclomatic Complexity: Jacoco 為每個非抽象方法計算圈複雜度,並也會計算每個類、包、組的複雜度。根據 McCabe 1996 的定義,圈複雜度可以理解為覆蓋所有的可能情況最少使用的測試用例數。這項引數也在任何情況下有效。
-
Lines: 該項指數在有除錯資訊的情況下計算。
-
Methods: 每一個非抽象方法都至少有一條指令。若一個方法至少被執行了一條指令,就認為它被執行過。因為 Jacoco 直接對位元組碼進行操作,所以有些方法沒有在原始碼顯示(比如某些構造方法和由編譯器自動生成的方法)也會被計入在內。
-
Classes:每個類中只要有一個方法被執行,這個類就被認定為被執行。同 Methods 一樣,有些沒有在原始碼宣告的方法被執行,也認定該類被執行。
2、詳細文件分析
如下所示,標示綠色的為分支覆蓋充分,標黃色的為部分分支覆蓋,標紅色的為未執行該分支。
在有除錯資訊的情況下,分支點可以被對映到原始碼中的每一行,並且被高亮表示。
紅色鑽石:無覆蓋,沒有分支被執行。
黃色鑽石:部分覆蓋,部分分支被執行。
綠色鑽石:全覆蓋,所有分支被執行。
通過這個報告的結果就可以知道程式碼真實的執行情況,便於我們分析評估結果,並且可以提高程式碼的測試覆蓋率。
三、Mockito技術調研
3.1 技術背景
在實際專案中寫單元測試的過程中我們會發現需要測試的類有很多依賴,這些依賴項又會有依賴,導致在單元測試程式碼裡幾乎無法完成構建,尤其是當依賴項尚未構建完成時會導致單元測試無法進行。Mockito是mocking框架,它讓你用簡潔的API做測試。
簡單來說,所謂的mock就是建立一個類的虛假的物件,在測試環境中,用來替換掉真實的物件。Mockito 的優點是通過在執行後校驗哪些函式已經被呼叫,消除了對期望行為(expectations)的需要。其它的 mocking 庫需要在執行前記錄期望行為(expectations),而這導致了醜陋的初始化程式碼。
Mockito區別於其他模擬框架的地方主要是允許開發者在沒有建立“預期”時驗證被測系統的行為。
3.2 Mockito使用
1、新增Maven依賴 (同時也需要匯入junit包依賴)
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
官方文件地址:http://static.javadoc.io/org.mockito/mockito-core/2.23.4/org/mockito/Mockito.html
2、新增引用
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
3、常用Mockito模擬方法
方法名 | 描述 |
---|---|
Mockito.mock(classToMock) | 模擬物件 |
Mockito.verify(mock) | 驗證行為是否發生 |
Mockito.when(methodCall).thenReturn(value1).thenReturn(value2) | 發時第一次返回value1,第n次都返回value2 |
Mockito.doThrow(toBeThrown).when(mock).[method] | 模擬丟擲異常 |
Mockito.mock(classToMock,defaultAnswer) | 使用預設Answer模擬物件 |
Mockito.when(methodCall).thenReturn(value) | 引數匹配,執行方法得到返回值value |
Mockito.doReturn(toBeReturned).when(mock).[method] | 引數匹配(直接執行不判斷) |
Mockito.when(methodCall).thenAnswer(answer)) | 預期回撥介面生成期望值 |
Mockito.doAnswer(answer).when(methodCall).[method] | 預期回撥介面生成期望值(直接執行不判斷) |
Mockito.spy(Object) | 用spy監控真實物件,設定真實物件行為 |
Mockito.doNothing().when(mock).[method] | 不做任何返回 |
Mockito.doCallRealMethod().when(mock).[method] //等價於Mockito.when(mock.[method]).thenCallRealMethod(); | 呼叫真實的方法 |
reset(mock) | 重置mock |
3.3 Mockito 示例
1、驗證行為是否發生
//模擬建立一個List物件
List<Integer> mock = Mockito.mock(List.class);
//呼叫mock物件的方法
mock.add(1);
mock.clear();
//驗證方法是否執行
Mockito.verify(mock).add(1);
Mockito.verify(mock).clear();
驗證是否發生add(0)和clear()這兩種方法,如果和驗證的不一致,則測試不通過。
2、多次觸發返回不同值
//mock一個Iterator類
Iterator iterator = mock(Iterator.class);
//預設當iterator呼叫next()時第一次返回hello,第n次都返回world
Mockito.when(iterator.next()).thenReturn("hello").thenReturn("world");
//使用mock的物件
String result = iterator.next() + " " + iterator.next() + " " + iterator.next();
//驗證結果
Assert.assertEquals("hello world world",result);
3、模擬丟擲異常
@Test(expected = IOException.class) //期望報Io異常
public void when_thenThrow() throws IOException{
OutputStream mock = Mockito.mock(OutputStream.class);
//預設當流關閉時丟擲異常
Mockito.doThrow(new IOException()).when(mock).close();
mock.close();
}
4、引數匹配
@Test
public void with_arguments(){
B b = Mockito.mock(B.class);
//預設根據不同的引數返回不同的結果
Mockito.when(b.getSex(1)).thenReturn("男");
Mockito.when(b.getSex(2)).thenReturn("女");
Assert.assertEquals("男", b.getSex(1));
Assert.assertEquals("女", b.getSex(2));
//對於沒有預設的情況會返回預設值
Assert.assertEquals(null, b.getSex(0));
}
5、匹配任意引數
Mockito.anyInt() 任何int值 、Mockito.anyLong() 任何long值 、Mockito.anyString() 任何String值
@Test
public void with_unspecified_arguments(){
List list = Mockito.mock(List.class);
//匹配任意引數
Mockito.when(list.get(Mockito.anyInt())).thenReturn(1);
Mockito.when(list.contains(Mockito.argThat(new IsValid()))).thenReturn(true);
Assert.assertEquals(1,list.get(1));
Assert.assertEquals(1,list.get(999));
Assert.assertTrue(list.contains(1));
Assert.assertTrue(!list.contains(3));
}
class IsValid extends ArgumentMatcher<List>{
@Override
public boolean matches(Object obj) {
return obj.equals(1) || obj.equals(2);
}
}
6、匹配自定義引數
@Test
public void argumentMatchersTest(){
//建立mock物件
List<String> mock = mock(List.class);
//argThat(Matches<T> matcher)方法用來應用自定義的規則,可以傳入任何實現Matcher介面的實現類。
Mockito.when(mock.addAll(Mockito.argThat(new IsListofTwoElements()))).thenReturn(true);
Assert.assertTrue(mock.addAll(Arrays.asList("one","two","three")));
}
class IsListofTwoElements extends ArgumentMatcher<List>{
public boolean matches(Object list){
return((List)list).size()==3;
}
}
7、用spy監控真實物件,設定真實物件行為
@Test(expected = IndexOutOfBoundsException.class)
public void spy_on_real_objects(){
List list = new LinkedList();
List spy = Mockito.spy(list);
//下面預設的spy.get(0)會報錯,因為會呼叫真實物件的get(0),所以會丟擲越界異常
//Mockito.when(spy.get(0)).thenReturn(3);
//使用doReturn-when可以避免when-thenReturn呼叫真實物件api
Mockito.doReturn(999).when(spy).get(999);
//預設size()期望值
Mockito.when(spy.size()).thenReturn(100);
//呼叫真實物件的api
spy.add(1);
spy.add(2);
Assert.assertEquals(100,spy.size());
Assert.assertEquals(1,spy.get(0));
Assert.assertEquals(2,spy.get(1));
Assert.assertEquals(999,spy.get(999));
}
8、呼叫真實的方法
@Test
public void Test() {
A a = Mockito.mock(A.class);
//void 方法才能呼叫doNothing()
Mockito.when(a.getName()).thenReturn("bb");
Assert.assertEquals("bb",a.getName());
//等價於Mockito.when(a.getName()).thenCallRealMethod();
Mockito.doCallRealMethod().when(a).getName();
Assert.assertEquals("zhangsan",a.getName());
}
class A {
public String getName(){
return "zhangsan";
}
}
9、重置mock
@Test
public void reset_mock(){
List list = mock(List.class);
Mockito. when(list.size()).thenReturn(10);
list.add(1);
Assert.assertEquals(10,list.size());
//重置mock,清除所有的互動和預設
Mockito.reset(list);
Assert.assertEquals(0,list.size());
}
它的API介面還有很多呼叫方法…
3.4、註解方式快速模擬
1、使用@Mock註釋
public class MockitoTest {
@Mock
private List mockList;
//必須在基類中新增初始化mock的程式碼,否則報錯mock的物件為NULL
public MockitoTest(){
MockitoAnnotations.initMocks(this);
}
@Test
public void AnnoTest() {
mockList.add(1);
Mockito.verify(mockList).add(1);
}
}
2、指定測試類使用執行器:MockitoJUnitRunner
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest2 {
@Mock
private List mockList;
@Test
public void shorthand(){
mockList.add(1);
Mockito.verify(mockList).add(1);
}
}
3.5 MockMvc配合Mockito單元測試
MockMvc為spring測試下的一個非常好用的類,配合Mockito使用能達到非常好的效果,他們的初始化需要在setUp中進行。
// mock api 模擬http請求
private MockMvc mockMvc;
@Autowired
private WebApplicationContext context;
// 初始化工作
@Before
public void setUp() {
// 整合Web環境測試(此種方式並不會整合真正的web環境,而是通過相應的Mock API進行模擬測試,無須啟動伺服器)
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@After
public void tearDown()throws Exception {
// Add additional tear down code here
}
mockMvc.perform( MockMvcRequestBuilders.post("/parkingsearch")
.accept(MediaType.APPLICATION_JSON_UTF8).content(new String(content.getBytes("GB2312"), "utf-8"))
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.jsonPath("$.resCode").value(0))
.andExpect(MockMvcResultMatchers.jsonPath("$.resData.data").isArray())
.andExpect(MockMvcResultMatchers.status().isOk());
perform:執行一個RequestBuilder請求,會自動執行SpringMVC的流程並對映到相應的控制器執行處理;andExpect:新增ResultMatcher驗證規則,驗證控制器執行完成後結果是否正確;
andDo:新增ResultHandler結果處理器,比如除錯時列印結果到控制檯;
andReturn:最後返回相應的MvcResult;然後進行自定義驗證/進行下一步的非同步處理;
MockMvc是基於RESTful風格的SpringMVC的測試框架,可以測試完整的Spring MVC流程,即從URL請求到控制器處理,再到檢視渲染都可以測試。
整合Web環境方式
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SearchServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IncotermsRestServiceTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); //構造MockMvc
}
...
}