單元測試並不僅是爲了驗證你當前所寫的代碼是否存在問題,更爲重要的是它能夠很大程度的保障往後因業務變動、修復Bug
或重構等引發的代碼變動而致使(或新增)的風險。css
同時將單元測試提早到編寫正式代碼進行(測試驅動開發),能夠很好的提升對代碼結構的設計。經過優先編寫測試用例,能夠很好的從用戶角度來對功能的分解、使用過程和接口等進行設計,從而提升代碼結構的高內聚、低耦合特性。使得對往後的需求變動或代碼重構等更加高效、簡潔。java
所以編寫單元測試對產品開發和維護、技術提高和積累具備重大意義!面試
首先寫一個單元測試,這樣有助於對後面內容的理解與實踐。算法
**IntelliJ IDEA **IntelliJ IDEA
默認自帶並啓用TestNG
和覆蓋率插件:spring
在設置窗口查看TestNG插件是否安裝與啓用:apache
一樣,查看覆蓋率插件能夠搜索「Coverage」。IntelliJ IDEA的覆蓋率統計工具備三種,JaCoCo、Emma和IntelliJ IDEA自帶。api
一樣,查看並安裝變異測試插件能夠搜索「PIT mutation testing」。數組
**Eclipse **Eclipse
須要自行安裝單元測試相關插件:tomcat
執行TestNG單元測試的插件。可在Eclipse Marketplace搜索「TestNG」安裝:安全
獲取單元測試覆蓋率的插件。可在Eclipse Marketplace搜索「EclEmma」安裝:
一樣,查看並安裝變異測試插件能夠搜索「Pitclipse」。
<dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>${testng.version}</version> <scope>test</scope> </dependency>
<dependency> <groupId>org.jmockit</groupId> <artifactId>jmockit</artifactId> <version>${jmockit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jmockit</groupId> <artifactId>jmockit-coverage</artifactId> <version>${jmockit.version}</version> <scope>test</scope> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.kubek2k</groupId> <artifactId>springockito</artifactId> <version>${springockito.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.kubek2k</groupId> <artifactId>springockito-annotations</artifactId> <version>${springockito.version}</version> <scope>test</scope> </dependency>
<dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-servlet-api</artifactId> <version>${tomcat.servlet.api.version}</version> <scope>test</scope> </dependency>
下面介紹經過IDE
自動建立單元測試的方法(也可手動完成):
IntelliJ IDEA
Eclipse:
2.在彈出的窗口中搜索「Test」,選擇「TestNG class」後點擊「Next」按鈕:
3.在窗口中選擇要建立的測試方法後點擊「Next」按鈕:
4.根據本身的狀況設置包名、類名和Annotations等:
示例代碼
可參考下例代碼編寫單元測試:
package org.light4j.unit.test; import mockit.Expectations; import mockit.Injectable; import mockit.Tested; import org.testng.Assert; import org.testng.annotations.Test; import wow.unit.test.remote.UserService; import java.util.List; /** * 單元測試demo * * @author jiazuo.ljz */ public class BookServiceTest { /** * 圖書持久化類,遠程接口 */ @Injectable private BookDAO bookDAO; /** * 用戶服務,遠程接口 */ @Injectable private UserService userService; /** * 圖書服務,本地接口 */ @Tested(availableDuringSetup = true) private BookService bookService; /** * 測試根據用戶的Nick查詢用戶的圖書列表方法 * 其中「getUserBooksByUserNick」方法最終須要經過UserID查詢DB, * 因此在調用此方法以前須要先對UserService類的getUserIDByNick方法進行Mock。 */ @Test public void testGetUserBooksByUserNick() throws Exception { new Expectations() { { userService.getUserIDByNick(anyString); // Mock接口 result = 1234567; // Mock接口的返回值 times = 1; // 此接口會被調用一次 } }; List<BookDO> bookList = bookService.getUserBooksByUserNick("moyuan.jcc"); Assert.assertNotNull(bookList); } }
IntelliJ IDEA
Eclipse
注:也可點擊工具欄選項運行,從左至右依次是:覆蓋率、調試、運行運行。
2.點擊「運行」:
左側框:單元測試運行結果
底側框:單元測試打印輸出的內容
Maven
IntelliJ IDEA
Eclipse
2.輸出報告
運行過程以及結果輸出的窗口中有一行「JMockit: Coverage report written to」,是EclEmma建立的覆蓋率報告文件目錄:
覆蓋率報告
變異測試是覆蓋率的一個很好的補充。相比覆蓋率,它可以使單元測試更加健壯。(具體可見5.4節)
IntelliJ IDEA
3. 輸出報告
運行過程以及結果輸出的窗口中最後一行「Open report in browser」即爲插件建立的報告鏈接。
點擊便可打開報告:
Eclipse
2. 輸出報告
可在此窗口中查看變異測試發現的可能存在的代碼缺陷:(這點比IDEA的PIT插件作的要好)
可在此窗口中查看測試報告:
爲從此更好的開展與落實單元測試,請繼續閱讀下面內容。
Junit4
和TestNG
是Java
很是流行的單元測試框架。因TestNG
更加簡潔、靈活和功能豐富,因此咱們選用TestNG
。
下面經過與Junit4
的比較來了解一下TestNG
的特性:
Junit4
和TestNG
的註解對比:
特性 | JUnit4 | TestNG |
---|---|---|
測試註解 | @Test | @Test |
在測試套件執行以前執行 | – | @BeforeSuite |
在測試套件執行以後執行 | – | @AfterSuite |
在測試以前執行 | – | @BeforeTest |
在測試以後執行 | – | @AfterTest |
在測試組執行以前執行 | – | @BeforeGroups |
在測試組執行以後執行 | – | @AfterGroups |
在測試類執行以前執行 | @BeforeClass | @BeforeClass |
在測試類執行以後執行 | @AfterClass | @AfterClass |
在測試方法執行以前執行 | @Before | @BeforeMethod |
在測試方法執行以後執行 | @After | @AfterMethod |
忽略測試 | @ignore | @Test(enbale=false) |
預期異常 | @Test(expected = Exception.class) | @Test(expectedExceptions = Exception.class) |
超時 | @Test(timeout = 1000) | @Test(timeout = 1000) |
// TODO 測試 測試方法 測試套件 測試組 的區別
在Junit4
中,@BeforeClass
和@AfterClass
只能用於靜態方法。TestNG
無此約束。
異常測試是指在單元測試中應該要拋出什麼異常是合理的。
@Test(expected = ArithmeticException.class) public void divisionWithException() { int i = 1/0; }
@Test(expectedExceptions = ArithmeticException.class) public void divisionWithException() { int i = 1/0; }
忽略測試是指這個單元測試能夠被忽略。
@Ignore("Not Ready to Run") @Test public void divisionWithException() { System.out.println("Method is not ready yet"); }
@Test(enabled=false) public void divisionWithException() { System.out.println("Method is not ready yet"); }
時間測試是指一個單元測試運行的時間超過了指定時間(毫秒數),那麼測試將失敗。
@Test(timeout = 1000) public void infinity() { while (true); }
@Test(timeOut = 1000) public void infinity() { while (true); }
套件測試是指把多個單元測試組合成一個模塊,而後統一運行。
@RunWith
和@Suite
註解被用於執行套件測試。下面的代碼是所展現的是在「JunitTest5
」被執行以後須要「JunitTest1
」和「JunitTest2
」也一塊兒執行。全部的聲明須要在類內部完成。
java
@RunWith(Suite.class) @Suite.SuiteClasses({JunitTest1.class, JunitTest2.class}) public class JunitTest5 {
是使用XML
配置文件來執行套件測試。下面的配置將「TestNGTest1
」和「TestNGTest2
」一塊兒執行。
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" > <suite name="My test suite"> <test name="testing"> <classes> <class name="com.fsecure.demo.testng.TestNGTest1" /> <class name="com.fsecure.demo.testng.TestNGTest2" /> </classes> </test> </suite>
TestNG
的另外一種方式使用了組的概念,每一個測試方法均可以根據功能特性分配到一個組裏面。例如:
@Test(groups="method1") public void testingMethod1() { System.out.println("Method - testingMethod1()"); } @Test(groups="method2") public void testingMethod2() { System.out.println("Method - testingMethod2()"); } @Test(groups="method1") public void testingMethod1_1() { System.out.println("Method - testingMethod1_1()"); } @Test(groups="method4") public void testingMethod4() { System.out.println("Method - testingMethod4()"); }
這是一個有4個方法,3個組(method1, method2 和 method4)的類。使用起來比XML的套件更簡潔。
下面XML
文件配置了一個執行組爲methed1
的單元測試。
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" > <suite name="My test suite"> <test name="testing"> <groups> <run> <include name="method1"/> </run> </groups> <classes> <class name="com.fsecure.demo.testng.TestNGTest5_2_0" /> </classes> </test> </suite>
分組使集成測試更增強大。例如,咱們能夠只是執行全部測試中的組名爲DatabaseFuntion
的測試。
參數化測試是指給單元測試傳多種參數值,驗證接口對多種不一樣參數的處理是否正確。
@RunWith
和@Parameter
註解用於爲單元測試提供參數值,@Parameters
必須返回List
,參數將會被做爲參數傳給類的構造函數。
@RunWith(value = Parameterized.class) public class JunitTest6 { private int number; public JunitTest6(int number) { this.number = number; } @Parameters public static Collection<Object[]> data() { Object[][] data = new Object[][] { { 1 }, { 2 }, { 3 }, { 4 } }; return Arrays.asList(data); } @Test public void pushTest() { System.out.println("Parameterized Number is : " + number); } }
它的使用很不方便:一個方法的參數化測試必須定義一個測試類。測試參數經過一個註解爲@Parameters且返回值爲List參數值列表的靜態方法。而後將方法返回值成員經過類的構造函數初始化爲類的成員。最後再將類的成員作爲參數去測試被測試方法。
使用XML
文件或@DataProvider註解兩種方式爲測試提供參數。
XML
文件配置參數化測試
方法上添加@Parameters註解,參數數據由TestNG的XML配置文件提供。這樣作以後,咱們可使用不一樣的數據集甚至是不一樣的結果集來重用一個測試用例。另外,甚至是最終用戶,QA或者QE能夠提供他們本身的XML文件來作測試。
public class TestNGTest6_1_0 { @Test @Parameters(value="number") public void parameterIntTest(int number) { System.out.println("Parameterized Number is : " + number); } }
XML 文件
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" > <suite name="My test suite"> <test name="testing"> <parameter name="number" value="2"/> <classes> <class name="com.fsecure.demo.testng.TestNGTest6_0" /> </classes> </test> </suite>
@DataProvider註解參數化測試
使用XML
文件初始化數據雖然方便,但僅支持基礎數據類型。如需複雜的類型可以使用@DataProvider
註解解決。
@Test(dataProvider = "Data-Provider-Function") public void parameterIntTest(Class clzz, String[] number) { System.out.println("Parameterized Number is : " + number[0]); System.out.println("Parameterized Number is : " + number[1]); } //This function will provide the patameter data @DataProvider(name = "Data-Provider-Function") public Object[][] parameterIntTestProvider() { return new Object[][]{ {Vector.class, new String[]{"java.util.AbstractList", "java.util.AbstractCollection"}}, {String.class, new String[] {"1", "2"}}, {Integer.class, new String[] {"1", "2"}} }; }
@DataProvider做爲對象的參數
P.S 「TestNGTest6_3_0」 是一個簡單的對象,使用了get和set方法。
@Test(dataProvider = "Data-Provider-Function") public void parameterIntTest(TestNGTest6_3_0 clzz) { System.out.println("Parameterized Number is : " + clzz.getMsg()); System.out.println("Parameterized Number is : " + clzz.getNumber()); } //This function will provide the patameter data @DataProvider(name = "Data-Provider-Function") public Object[][] parameterIntTestProvider() { TestNGTest6_3_0 obj = new TestNGTest6_3_0(); obj.setMsg("Hello"); obj.setNumber(123); return new Object[][]{{obj}}; }
TestNG的參數化測試使用起來很是方便,它能夠在一個測試類中添加多個方法的參數化測試(JUnit4一個方法就須要一個類)。
依賴測試是指測試的方法是有依賴的,在執行的測試以前須要執行的另外一測試。若是依賴的測試出現錯誤,全部的子測試都被忽略,且不會被標記爲失敗。
JUnit4框架主要聚焦於測試的隔離,暫時還不支持這個特性。
它使用dependOnMethods來實現了依賴測試的功能,以下:
@Test public void method1() { System.out.println("This is method 1"); } @Test(dependsOnMethods={"method1"}) public void method2() { System.out.println("This is method 2"); }
若是method1()成功執行,那麼method2()也將被執行,不然method2()將會被忽略。
TestNG
支持經過多個線程併發調用一個測試接口來實現性能測試。JUnit4
不支持,若要進行性能測試需手動添加併發代碼。
@Test(invocationCount=1000, threadPoolSize=5, timeOut=100) public void perfMethod() { System.out.println("This is perfMethod"); }
TestNG
支持經過多個線程併發調用多個測試接口執行測試,相對於傳統的單線程執行測試的方式,能夠很大程度減小測試運行時間。
public class ConcurrencyTest { @Test public void method1() { System.out.println("This is method 1"); } @Test public void method2() { System.out.println("This is method 2"); } }
並行測試配置:
<suite name="Concurrency Suite" parallel="methods" thread-count="2" > <test name="Concurrency Test" group-by-instances="true"> <classes> <class name="wow.unit.test.ConcurrencyTest" /> </classes> </test> </suite>
經過上面的對比,建議使用TestNG做爲Java項目的單元測試框架,由於TestNG在參數化測試、依賴測試以、套件測試(組)及併發測試方面功能更加簡潔、強大。另外,TestNG也涵蓋了JUnit4的所有功能。
好比Mock如下場景:
Mock
工具工做的原理大都以下:
歷史曾經或當前比較流行的Mock工具備EasyMock
、jMock
、Mockito
、Unitils Mock
、PowerMock
、JMockit
等工具。
從這裏能夠看到,JMockit
的的功能最全面、強大!因此咱們單元測試中的Mock工具也選擇了JMockit。同時在開發的過程當中,JMockit的「Auto-injection of mocks」及「Special fields for 「any」 argument matching」及各類有用的Annotation使單元測試的開發更簡潔和高效。
JMockit
是用以幫助開發人員編寫單元測試的Mock
工具。它基於java.lang.instrument包開發,並使用ASM庫來修改Java的Bytecode。正所以兩點,它能夠實現無所不能的Mock。
JMockit能夠Mock的種類包含了:
JMockit有兩種Mock的方式:
通俗點講,Behavior-oriented是基於行爲的Mock,對Mock目標代碼的行爲進行模仿,像是黑盒測試。State-oriented是基於狀態的Mock,是站在目標測試代碼內部的。能夠對傳入的參數進行檢查、匹配,才返回某些結果,相似白盒。而State-oriented的new MockUp基本上能夠Mock任何代碼或邏輯。
如下是JMockit的APIs和tools:
能夠看到JMockit經常使用的Expectation、StrictExpectations和NonStrictExpectations指望錄製及註解@Tested、@Mocked,@NonStrict、@Injectable等簡潔的Mock代碼風格。並且JMockit還自帶了Code Coverage的工具供本地單元測試時候邏輯覆蓋或代碼覆蓋率使用。
以「第一個單元測試」代碼爲例:
@Tested:JMockit會自動建立註解爲「@Tested」的類對象,並將其作爲被測試對象。 經過設置「availableDuringSetup=true」參數,可使得被測試對象在「setUp」方法執行前被建立出來。
@Tested(availableDuringSetup = true) private BookService bookService;
@Injectable:JMockit自動建立註解爲「@Injectable」的類對象,並將其自動注入被測試對象。
@Injectable private BookDAO bookDAO; @Injectable private UserService userService;
相關的註解還有:// TODO 待補充
Expectations:塊裏的內容是用來Mock方法,並指定方法的返回值、異常、調用次數和耗時。此塊中的方法是必須被執行的,不然單元測試失敗。
/** * 測試根據用戶的Nick查詢用戶的圖書列表方法 * 其中「getUserBooksByUserNick」方法最終須要經過UserId查詢DB, * 因此在調用此方法以前須要先對UserService類的getUserIdByNick方法進行Mock。 */ @Test public void testGetUserBooksByUserNick() throws Exception { new Expectations() { { userService.getUserIdByNick(anyString); result = 1234567; times = 1; } }; List<BookDO> bookList = bookService.getUserBooksByUserNick("moyuan.jcc"); Assert.assertNotNull(bookList); }
相關的類還有:
Assert:是最多見的斷言驗證
Assert.assertNotNull(bookList);
Verifications:一種特殊的驗證塊。好比:要驗證一個被測試類中,調用的某個方法是否爲指定的參數、調用次數。相比Expectations它放在單元測試的最後且沒有Mock功能。
注:以上列舉的註釋具體用法示例請查閱第7節內容
在單元測試時,測試人員根據設計文檔和源碼,瞭解模塊的接口和邏輯結構。主要採用白盒測試用例,輔之黑盒測試用例,使之對任何(合理和不合理)的輸入都要能鑑別和響應。這就要求對程序全部的局部和全局的數據結構、外部接口和程序代碼的關鍵部分進行檢查。
在單元測試中主要在5個方面對被測模塊進行檢查。
在單元測試開始時,應該對全部被測模塊的接口進行測試。若是數據不能正常地輸入和輸出,那麼其餘的測試毫無心義。Myers在關於軟件測試的書中爲接口測試提出了一個檢查表:
當模塊經過外部設備進行輸入/輸出操做時,必須擴展接口測試,附加以下的測試項目:
模塊的局部數據結構是最多見的錯誤來源,應設計測試用例以檢查如下各類錯誤:
檢查因爲計算、斷定和控制流錯誤而致使的程序錯誤。因爲在測試時不可能作到窮舉測試,因此在單元測試時要根據「白盒」測試和「黑盒」測試用例的設計方法設計測試用例,對模塊中重要的執行路徑進行測試。重要的執行路徑是一般指那些處在具體實現的算法、控制、數據處理等重要位置的路徑,也可指較複雜而容易出錯的路徑。儘量地對執行路徑進行測試很是重要,須要設計因錯誤的計算、比較或控制流而致使錯誤的測試用例。此外,對基本執行路徑和循環進行測試也可發現大量的路徑錯誤。
在路徑測試中,要檢查的錯誤有:死代碼、錯誤的計算優先級、算法錯誤、混用不一樣類的操做、初始化不正確、精度錯誤——比較運算錯誤、賦值錯誤、表達式的不正確符號——>、>=;=、==、!=和循環變量的使用錯誤——錯誤賦值以及其餘錯誤等。
比較操做和控制流向緊密相關,測試用例設計須要注意發現比較操做的錯誤:
錯誤處理路徑是指可能出現錯誤的路徑以及進行錯誤處理的路徑。當出現錯誤時會執行錯誤處理代碼,或通知用戶處理,或中止執行並使程序進入一種安全等待狀態。測試人員應意識到,每一行程序代碼均可能執行到,不能自認爲錯誤發生的機率很小而不進行測試。通常軟件錯誤處理測試應考慮下面幾種可能的錯誤:
在進行錯誤處理測試時,要檢查以下內容:
邊界測試是單元測試中最後的任務。代碼經常在邊界上出錯,好比:在代碼段中有一個n次循環,當到達第n次循環時就可能會出錯;或者在一個有n個元素的數組中,訪問第n個元素時是很容易出錯的。所以,要特別注意數據流、控制流中恰好等於、大於或小於肯定的比較值時可能會出現的錯誤。對這些地方須要仔細地認真加以測試。
此外,若是對模塊性能有要求的話,還要專門對關鍵路徑進行性能測試。以肯定最壞狀況下和平均意義下影響運行時間的因素。下面是邊界測試的具體要檢查的內容:
第4部分歸納的列舉了須要測試的5大點內容,此處爲服務端代碼層至少要包含或覆蓋的測試內容。
Service
HTTP接口
HSF接口
工具類
爲了使單元測試能充分細緻地展開,應在實施單元測試中遵照下述要求:
語句覆蓋達到100%
語句覆蓋指被測單元中每條可執行語句都被測試用例所覆蓋。語句覆蓋是強度最低的覆蓋要求,要注重語句覆蓋的意義。好比,用一段從沒執行過的程序控制航天飛機升上天空,而後使它精確入軌,這種行爲的後果不敢想象。實際測試中,不必定能作到每條語句都被執行到。第一,存在「死碼」,即因爲代碼設計錯誤在任何狀況下都不可能執行到的代碼。第二,不是「死碼」,可是因爲要求的輸入及條件很是難達到或單元測試的實現所限,使得代碼沒有獲得執行。所以,在可執行語句未獲得執行時,要深刻程序做作詳細的分析。若是是屬於以上兩種狀況,則能夠認爲完成了覆蓋。可是對於後者,也要儘可能測試到。若是以上二者都不是,則是由於測試用例設計不充分,須要再設計測試用例。
分支覆蓋達到100%
分支覆蓋指分支語句取真值和取假值各一次。分支語句是程序控制流的重要處理語句,在不一樣流向上設計能夠驗證這些控制流向正確性的測試用命。分支覆蓋使這些分支產生的輸出都獲得驗證,提升測試的充分性。
覆蓋錯誤處理路徑
即異常處理路徑
單元的軟件特性覆蓋
軟件的特性包括功能、性能、屬性、設計約束、狀態數目、分支的行數等。
對試用額定數據值、奇異數據值和邊界值的計算進行檢驗。用假想的數據類型和數據值運行測試,排斥不規則的輸入。
單元測試一般是由編寫程序的人本身完成的,可是項目負責人應當關心測試的結果。全部的測試用例和測試結果都是模塊開發的重要資料,需妥善保存。
測試覆蓋方法的確能夠幫咱們找到一些顯而易見的代碼冗餘或者測試遺漏的問題。不過,實踐證實,這些傳統的方法只能很是有限的發現測試中的問題。不少代碼和測試的問題在覆蓋達到100%的狀況下也沒法發現。然而,「代碼變異測試」這種方法能夠很好的彌補傳統方法的缺點,產生更加有效的單元測試。
代碼變異測試是經過對代碼產生「變異」來幫助咱們改進單元測試的。「變異」指的是修改一處代碼來改變代碼行爲(固然保證語法的合理性)。簡單來講,代碼變異測試先試着對代碼產生這樣的變異,而後運行單元測試,並檢查是否有測試是由於這個代碼變異而失敗。若是失敗,那麼說明這個變異被「消滅」了,這是咱們指望看到的結果。不然說明這個變異「存活」了下來,這種狀況下咱們就須要去研究一下「爲何」了。
總而言之,測試覆蓋這種方法是一種不錯的保障單元測試質量的手段。代碼變異測試則比傳統的測試覆蓋方法能夠更加有效的發現代碼和測試中潛在的問題,它可使單元測試更增強壯。
Service
層單元測試示例。
普通Mock測試:
/** * 測試根據用戶的Nick查詢用戶的圖書列表方法 * 其中「userService.getUserBooksByUserNick」方法最終須要經過UserId查詢DB, * 因此在調用此方法以前須要先對UserService類的getUserIdByNick方法進行Mock。 * 其中「bookDAO.getUserBooksByUserId」方法最終須要經過UserId查詢DB, * 因此在調用此方法以前須要先對BookDAO類的getUserBooksByUserId方法進行Mock。 */ @Test public void testGetUserBooksByUserNick4Success() throws Exception { final List<BookDO> bookList = new ArrayList<BookDO>(); bookList.add(new BookDO()); new Expectations() { { userService.getUserIdByNick(anyString); // Mock的接口 result = 1234567; // 接口返回值 times = 1; // 接口被調用的次數 bookDAO.getUserBooksByUserId(anyLong); result = bookList; times = 1; } }; List<BookDO> resultBookList = bookService.getUserBooksByUserNick("moyuan.jcc"); Assert.assertNotNull(resultBookList); }
2.錯誤(異常)處理:
/** * 測試根據用戶的Nick查詢用戶的圖書列表方法,注意在@Test添加expectedExceptions參數 * 驗證其中「userService.getUserBooksByUserNick」接口出現異常時,對異常的處理是否符合預期. * 其中「bookDAO.getUserBooksByUserId」方法不會被調用到。 */ @Test(expectedExceptions = {RuntimeException.class}) public void testGetUserBooksByUserNick4Exception() throws Exception { final List<BookDO> bookList = new ArrayList<BookDO>(); bookList.add(new BookDO()); new Expectations() { { userService.getUserIdByNick(anyString); // Mock的接口 result = new RuntimeException("exception unit test"); // 接口拋出異常 times = 1; // 接口被調用的次數 bookDAO.getUserBooksByUserId(anyLong); result = bookList; times = 0; // 上面接口出現異常後,此接口不會被調用 } }; List<BookDO> resultBookList = bookService.getUserBooksByUserNick("moyuan.jcc"); Assert.assertNotNull(resultBookList); }
3. Mock具體方法實現:
/** * 測試發送離線消息方法 * 消息隊列:當離線消息超過100條時,刪除最舊1條,添加最新一條。 * 但消息存在DB或Tair中,因此須要Mock消息的存儲。 */ @Test public void testAddOffLineMsg() throws Exception { final Map<Long, MsgDO> msgCache = new ArrayList<Long, MsgDO>(); new Expectations() { { new MockUp<BookDAO>() { @Mock public void addMsgByUserId(long userId, MsgDO msgDO) { msgCache.put(userId, msgDO); } }; new MockUp<BookDAO>() { @Mock public List<MsgDO> getUserBooksByUserId(long userId) { return msgCache.get(userId); } }; } }; final int testAddMsgCount = 102; for(int i = 0; i < testAddMsgCount; i++) { msgService.addMsgByUserId(123L, new MsgDO(new Date(), "this is msg" + i)); } List<MsgDO> msgList = msgService.getMsgByUserId(123L); Assert.assertTrue(msgList.size() == 100); new Verifications() { { // 驗證 addMsgByUserId 接口是否被調用了100次 MsgDAO.addMsgByUserId(anyLong, withInstanceOf(MsgDO.class)); times = testAddMsgCount; // 驗證是否對消息內容進行相就次數的轉義 SecurityUtil.escapeHtml(anyString); times = testAddMsgCount; } }; }
HTTP
接口單元測試示例。
1. Spring MVC Controller
public final class BookControllerTest { @Tested(availableDuringSetup = true) private BookController bookController; @Injectable private BookService bookService; private MockMvc mockMvc; @BeforeMethod public void setUp() throws Exception { this.mockMvc = MockMvcBuilders.standaloneSetup(bookController).build(); } /** *<strong> </strong>******************************** * getBookList unit test *<strong> </strong>******************************** */ @Test public void testgetBookList4Success() throws Exception { new StrictExpectations() { { new MockUp<CookieUtil>(){ @Mock public boolean isLogined(){ return true; } }; userService.getUserBooksByUserNick(anyString); result = null; times = 1; } }; ResultActions httpResult = this.mockMvc.perform(get("/education/getBookList.do?userNick=hello")) .andDo(print()).andExpect(status().isOk()); MockHttpServletResponse response = httpResult.andReturn().getResponse(); String responseStr = response.getContentAsString(); // 若是存在多版本客戶端的狀況下,注意返回值向後兼容,此處須要多種格式驗證. Assert.assertEquals(responseStr, "{\"code\":1,\"msg\":\"success\",\"data\":\"\"}"); } }
2. 參數化測試
@DataProvider(name = "getBookListParameterProvider") public Object[][] getBookListParameterProvider() { return new String[][]{ {"hello", "{\"code\":1,\"msg\":\"success\",\"data\":\"\"}"}, {"123", "{\"code\":301,\"msg\":\"parameter error\",\"data\":\"\"}"} }; } @Test(dataProvider = "getBookListParameterProvider") public void testgetBookList4Success(String nick ,String resultCheck) throws Exception { new StrictExpectations() { { new MockUp<CookieUtil>() { @Mock public boolean isLogined() { return true; } }; userService.getUserBooksByUserNick(anyString); result = null; times = 1; } }; ResultActions httpResult = this.mockMvc.perform(get("/education/getBookList.do?userNick=" + nick)) .andDo(print()).andExpect(status().isOk()); MockHttpServletResponse response = httpResult.andReturn().getResponse(); String responseStr = response.getContentAsString(); // 若是存在多版本客戶端的狀況下,注意返回值向後兼容,此處須要多種格式驗證. Assert.assertEquals(responseStr, resultCheck); }
靜態工具類測試示例。
1. 靜態方法:
java @Test public void testMethod() { new StrictExpectations(CookieUtil) { { CookieUtil.isLogined(); result =
或
java @Test public void testMethod() { new MockUp<CookieUtil>(){ @Mock public boolean isLogined(){ return true;
單元測試永遠沒法證實代碼的正確性!!
一個跑失敗的測試可能代表代碼有錯誤,但一個跑成功的測試什麼也證實不了。
單元測試最有效的使用場合是在一個較低的層級驗證並文檔化需求,以及迴歸測試:開發或重構代碼,不會破壞已有功能的正確性。
以上內容就是本篇的所有內容以上內容但願對你有幫助,有被幫助到的朋友歡迎點贊,評論。若是對軟件測試、接口測試、自動化測試、面試經驗交流。感興趣能夠關注博主主頁,會有同行一塊兒技術交流哦。