單元測試--Junit和Mockito
說到測試,大家都不會陌生,從我們開始學習程式設計開始,就知道測試。測試和程式設計就像兩個雙胞胎似的,可是,顯然我們更鐘情於雙胞胎中的一個--程式設計。一些人可能對測試瞭然於胸,卻匱乏於行動,一些人也可能對測試只是聞其名不知其意。下面這篇博文就是給大家在零基礎上講解一下Java中單元測試的使用。
---------------------------什 麼是-----------------------------
首先來說說,究竟什麼是單元測試?單元測試是指對軟體中的最小可測試單元進行檢查和驗證。對於單元測試中單元的含義,一般來說,要根據實際情況去判定其具體含義,如C語言中單元指一個函式,Java裡單元指一個類,圖形化的軟體中可以指一個視窗或一個選單等。可以說,單元就是人為規定的最小的被測功能模組。單元測試是在軟體開發過程中要進行的最低級別的測試活動,軟體的獨立單元將在與程式的其他部分相隔離的情況下進行測試。總的來說,可總結成以下四點:
1. 人為規定
2. 最小被測功能模組
3. 最低級別
4. 不依賴其他單元
--------------------------什 麼 用-----------------------------
知道什麼是單元測試了,我們馬上就要想到,單元測試有什麼用呢?我為什麼要進行單元測試呢?下面從以下四點來說明單元測試的好處:
1. 提高程式碼質量
----實現功能
----邏輯嚴密
稍有資訊素質的專業程式設計師總是追求著一件事情---寫出優雅的程式碼。這裡的優雅,不僅僅是指需求功能的準確實現,更是系統上線後的穩定和高效能。而測試用例的認真思考與書寫,就給了程式設計師一個“深思熟慮”的機會,讓我們在“做”之前先“想”好了
2. 減少除錯時間
我們以前的測試,基本上都是從web層開始,一條線的測試。首先這種測試需要我們打包部署後執行整個程式來執行,耗費時間較多;其次也是最重要的,出現錯誤後我們不能很快的定位是那一層的問題,只有一步一步的斷點除錯,方可定位到錯誤,這樣除錯的時間是很長的。
而在Java中的單元測試,一般是對一個類的測試。而這個恰恰讓coder極為迅速並且準確的定位錯誤的來源---就是本類!因此,極大的減少了我們除錯的時間。
3. 隔離測試
在一個大專案或者關係比較緊密的專案中,很有可能出現兩個子系統之間的介面依賴,例如這次高校雲平臺的專案,其他子系統都需要基礎系統為其提供介面,因此極可能會造成這種情況,前期開發中基礎系統一直在開發介面,而自己的功能只能放後!
怎麼才能解決這個問題呢?隔離測試!它使得我們可以測試還未寫完的程式碼(只要你又介面可使用),另外,隔離測試能幫助團隊單元測試程式碼的一部分,而無需等待全部程式碼的完成。
---------------------------怎 麼用----------------------------
知道什麼是單元測試,並且單元測試有什麼用了,下面我們就舉幾個很簡單的例子來說明一下單元測試該怎麼用!網上一查,單元測試的工具類有很多,再此我選用最流行的Junit和Mockito進行測試演示。
先來說說準備條件吧:
首先是引入相關Jar包--Junit4.11和Mockito-groovy-support1.2。本次高校平臺是使用的Maven進行jar包管理,因此我只需要在專案的pom.xml檔案下寫入該jar包即可;如果讀者你的專案沒有使用Maven,那麼就需要手動引入相關jar了。
其次就是建立測試類了,直接右擊實際類--new--Junit test case即可。這裡需要使用Maven的朋友注意,測試類要求放在Mavenproject自動生成的src/test/java資料夾下,這樣每次執行maven的時候都會自動執行該檔案下的測試類,並且這些類在打包的時候不會打入,僅為了測試存在。
有了條件了,下面我們就要開始填充一下新建的測試類了。
(一)普通測試
待測方法:
//加法
publicint add(inta, int b) {
returna + b;
}
測試方法:
@Test
publicvoidtestAdd(){
//--------------------第一種寫法----------------------
//(1)待測方法的“引數賦值”
inta =1;
intb=3;
//(2)賦值後的“期望值”
intexpectedReturn=6;
//(3)呼叫待測方法,得到“實際值”
intactualReturn = firstDemo.add(1, 3);
//(4)通過斷言,判斷“期望值”和“實際值”是否相等
assertEquals(expectedReturn,actualReturn);
//---------------------第二種寫法------------------------
//assertEquals(4,firstDemo.add(1,3));
}
看一看執行結果:
其實一個測試方法的書寫就是四個步驟:(1)引數賦值(2)寫出期望值(3)獲取實際值(4)斷言--比較期望值和實際值。所以其實一個測試方法的填充是很簡單的,我們只需要把這幾個步驟都寫出來就好啦。當然,在實際專案中,我上述方法太簡單,一般情況下是不用進行測試的。我們只需要對一些業務邏輯複雜的方法進行測試即可。這裡舉這個例子,只是為了讓零基礎的讀者能夠很快並且容易的理解單元測試怎麼寫。
(二)引數化測試
上面第一個普通測試,是針對一個方法只需要一個測試用例即可完成測試的情況。在實際專案中,我們會遇到一些分支語句,這時候一個測試用例已經不能滿足我們覆蓋全部分支語句了。所以我們就需要寫多個測試用例,可是我們必須針對這個待測方法,寫多個測試方法嗎?這也太麻煩了吧!沒有什麼解決辦法嗎?如果是Junit3的話,我們只能這樣做,可是從Junit4開始,加入了一個新的概念--引數化測試。這就為我們解決了這個問題。
引數化測試主要包括五個步驟:
(1)為準備使用引數化測試的測試類指定特殊的執行器org.junit.runners.Parameterized。
(2)為測試類宣告幾個變數,分別用於存放期望值和測試所用資料。
(3)為測試類宣告一個帶有引數的公共建構函式,並在其中為第二個環節中宣告的幾個變數賦值。
(4)為測試類宣告一個使用註解org.junit.runners.Parameterized.Parameters修飾的,返回值為 java.util.Collection的公共靜態方法,並在此方法中初始化所有需要測試的引數對。
(5)編寫測試方法,使用定義的變數作為引數進行測試。
待測方法:
//多分支語句
public boolean Parameterization(int a){
if(a>10) {
returntrue;
}else{
returnfalse;
}
}
測試方法:
@RunWith(Parameterized.class)//第一步:指定特殊的執行器org.junit.runners.Parameterized
publicclassFirstDemoTestParameterization {
//要測試的類
private FirstDemo firstDemo;
//第二步:為測試類宣告幾個變數,分別用於存放期望值和測試所用資料。
privateint input1;
private boolean expected;
@Before //執行每個測試方法之前都執行一次
publicvoid setUp() throws Exception {
firstDemo=newFirstDemo();
}
//第三步:帶有引數的公共建構函式,並在其中為宣告的幾個變數賦值。
public FirstDemoTestParameterization(intinput1,boolean expected) {
this.input1= input1; //引數1
this.expected= expected; //期待的結果值
}
//-------------------(1)引數賦值 &&&(2)寫出期望值----------------------------
//第四步:為測試類宣告一個註解@Parameters,返回值為Collection的公共靜態方法,並初始化所有需要測試的引數對。
@Parameters
publicstaticCollection prepareData() {
Object[][]object = { { -1,true }, { 13, true } }; //測試資料
returnArrays.asList(object); //將陣列轉換成集合返回
}
@Test
publicvoidtestParameterization() {
//-----------(3)獲取實際值&&&(4)斷言--比較期望值和實際值。---------------
//第五步:編寫測試方法,使用定義的變數作為引數進行測試。
assertEquals(expected,firstDemo.Parameterization(input1));
}
}
關於引數化測試,我們需要注意以下幾點:
(1)@RunWith(Parameterized.class):在每個需要引數化測試的類上面,我們都需要寫上@RunWith(Parameterized.class)因為這樣JUnit才會使用Parameterized執行器來執行測試,否則JUnit會選擇預設的執行器,而預設執行器的不支援引數化測試。
(2)@Parameters:在提供資料的方法上加上一個@Parameters註解,這個方法必須是靜態static的,並且返回一個集合Collection。
看一下兩組測試用例的執行結果:
(三)隔離測試
隔離測試也是我們常用的一個防止多類之間依賴的測試。最基礎的就是B層對D層的依賴。測試B層時,我們不可能還要跑D層,這樣的話就不是單元測試。那麼我們怎麼來解決這個問題呢?我們不需要跑D層,但是又需要D層的返回值。隔離測試就幫助我們解決了這個問題。在本次專案中,我選用Mockito來進行隔離測試。
其實說白了,隔離測試,就是一個Mock--模擬的功能。當我們依賴其他類時,不需要真實呼叫,只需模擬出該類即可。具體使用以在下面demo中給出了詳細的解釋。
直接上B層的程式碼,待測方法:
publicbooleanIsExist(Student student) {
ListstudentList = null;
booleanisExist =true;
//若輸入學生姓名不為空
if(student.getName() != null) {
//呼叫Dao層的查詢學生方法,並將資訊返回List中
studentList= studentDao.queryStudent(student.getName());
//判斷Dao層返回List是否為空,如果為空及學生不存在,則返回false,否則返回true
if(studentList.isEmpty()) {
isExist=false;
}else{
isExist=true;
}
}
returnisExist;
}
測試方法:
packagecom.sld.service;
importstaticorg.junit.Assert.*;
importjava.util.ArrayList;
importjava.util.Collection;
importjava.util.List;
importorg.junit.Before;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.mockito.InjectMocks;
importorg.mockito.Mock;
importorg.mockito.runners.MockitoJUnitRunner;
importstaticorg.mockito.Mockito.when;
importcom.sld.dao.StudentDao;
importcom.sld.entity.Student;
@RunWith(MockitoJUnitRunner.class)
publicclassStudentServiceTest {
@Mock //建立Mock物件,模擬studentDao類
StudentDaostudentDao;
@InjectMocks //自動注入Mock類(StudentDao)到被測試類(StudentService),作為一個屬性
StudentServicestudentService;
@Test
publicvoid testIsExist(){
//------------------(一)期望值:隔離Dao層後,自己設定的期望Dao層的返回值-----------
//(1)例項化一個實體類expectStudent,併為其賦值
Student expectStudent = new Student();
expectStudent.setName("001");
//(2)例項化一個List集合,並將賦值後的expectStudent實體類放入集合中
List<Student> mockStudentList =newArrayList<Student>();
mockStudentList.add(expectStudent);
//(3)當呼叫模擬類的方法時,返回List集合
when(studentDao.queryStudent(expectStudent.getName())).thenReturn(
mockStudentList);
//------------------(二)實際值:測試Service層方法,並且返回實際值-----------
StudentactualStudent1 = new Student();
actualStudent1.setName("001");
//學生資訊:存在,應該返回true
booleanres= studentService.IsExist(actualStudent1);
System.out.print(res);
//------------------(三)期望值與實際值比較:測試Service層方法,並且返回實際值-----------
assertTrue(studentService.IsExist(actualStudent1));
//------------------補充:為了對比寫的-----------
StudentactualStudent2 = new Student();
actualStudent2.setName("002");
//學生資訊:不存在,應該返回false
booleanres2= studentService.IsExist(actualStudent2);
System.out.print(res2);
assertTrue(studentService.IsExist(actualStudent2));
//assertTrue(studentService.IsExist(actualStudent2));
}
}
通過一步步的講解,不知道你對單元測試瞭解多少了呢?不管是普通測試,還是引數化測試,還是隔離測試,我們新接觸時肯定不可能寫的特別完善。寫測試和寫程式碼是一樣的,都需要我們一點一滴經驗的積累。所以不要覺得測試類難或者麻煩,慢慢來,習慣都是培養出來的。一句很老套的話:學習--就是一個過程~~