前言
在慕課網上聽了一位老師的JUnit的基礎講解,感覺對於新人來說還是很友好,故總結下來做此筆記
什麼是單元測試
你的程序主要是由一個個的 Class 組成的,一個類或一個對象當然也是一個單元,而比類更小的單元是類的方法(函式)。如果你的類中的基本單元——如某些方法不能正常工作,在某些輸入條件下會得出錯誤的執行結果,那麼如何保證你的類/對象乃至整個應用軟件或系統作爲一個整體能正常工作呢?所以,簡單說,單元測試(優先)的目的就是首先保證一個系統的基本組成單元、模塊(如對象以及對象中的方法)能正常工作。
爲什麼要進行單元測試
看上去像是增加了代碼量,畢竟光是業務代碼就已經花了我們大部分時間精力,但是實際上卻是減少了我們之後可能對代碼修改的機率,儘可能的實現我們一次性編寫就能成功的這個夢想。
工具
本次筆記採用JUnit4來進行測試。JUnit4比起JUnit3而言使用起來更加容易方便,而且目前爲止要比JUnit5穩定,網上的參考文檔也更多。編譯器採用的eclipse。
什麼是JUnit
基於測試驅動開發的測試框架,是xUnit系列的一個子系列。說簡單點就是一個用於java測試的工具箱,裏面有很多方法和規範可以供我們直接使用。
官網
- 第1條是關於如何在項目中配置JUnit,包括具體的jar包,在maven中的配置之類的,但由於本次實驗所使用的編譯器是Eclipse, Eclipse很好的集成了JUnit框架,所以我們也就不用再單獨下載。
- 第2條是提供一套樣例,方便初學者依葫蘆畫瓢。
- 第3條是下載的JUnit4的版本。
- 第4條JUnit的java API文檔。裏面詳細講述了各種方法。
- 第5條列舉了jUnit使用的語法規範
- 第6條是指第三方擴展。
基本使用步驟
創建被測試類
- 在eclipse裏新建一個工程,取名JUnitDemo,並新建一個名叫Number的類,包名寫上my.demo。
- 給這個Number類裏寫上一些測試用方法,爲了方便我們寫加減乘除即可。
public class Number {
public int add(int a, int b) {
return a+b;
}
public int subtraction(int a ,int b) {
return a-b;
}
public int division(int a,int b) {
return a/b;
}
public int multiplication(int a,int b) {
return a*b;
}
}
- 先將jUnit所需的jar包引入項目。右鍵項目->Build Path->Add Libraries。
之後會彈出一個框,選擇其中的JUnit
然後選擇JUnit4,最後Finish即可。 - 創建測試類。爲了方便我們以後對測試類進行管理,建議在在和src平級的地方新建一個文件夾叫做“test”,並在裏面創建測試代碼,test文件裏的目錄結構和src的建議保持一致。
右鍵被測試類->new->Other
之後在文本框中輸入junit,然後選中JUnit Test Case
彈出一個創建框,點擊browse按鈕選擇路徑爲test包後,點擊next
隨後勾選該類,選擇所有方法
測試類就創建完成了
斷言
JUnit測試中使用“斷言”來進行測試。斷言是編寫測試用例的核心實現方式,即對比期望值和測試的結果是否相同,以此來判斷測試是否通過。斷言的核心方法有以下幾種
方法名 | 作用 |
---|---|
assertArrayEquals(expecteds, actuals) | 查看兩個數組是否相等 |
assertEquals(expected, actual) | 查看兩個對象是否相等。類似於字符串比較使用的equals()方法 |
assertNotEquals(first, second) | 查看兩個對象是否不相等 |
assertNull(object) | 查看對象是否爲空。 |
assertNotNull(object) | 查看對象是否不爲空。 |
assertSame(expected, actual) | 查看兩個對象的引用是否相等。類似於使用“==”比較兩個對象 |
assertNotSame(unexpected, actual) | 查看運行結果是否爲true。 |
assertFalse(condition) | 查看運行結果是否爲false。 |
assertThat(actual, matcher) | 查看實際值是否滿足指定的條件 |
fail() | 讓測試失敗 |
註解
註解與註釋不同,註釋是由“//”等開頭,而註解是“@”開頭。兩者的區別是註解不會對程序本身有任何影響,其作用僅僅是方便我們更好的理解代碼。而註解是對程序本身又影響的,它相當於是一個可執行的代碼。
JUnit常用註解及其作用有:
註解名 | 作用 |
---|---|
@Before | 初始化方法 |
@After | 釋放資源 |
@Test | 測試方法,在這裏可以測試期望異常和超時時間 |
@Ignore | 忽略的測試方法 |
@BeforeClass | 針對所有測試,只執行一次,且必須爲static void |
@AfterClass | 針對所有測試,只執行一次,且必須爲static void |
@RunWith | 指定測試類使用某個運行器 |
@Parameters | 指定測試類的測試數據集合 |
@Rule | 允許靈活添加或重新定義測試類中的每個測試方法的行爲 |
@FixMethodOrder | 指定測試方法的執行順序 |
測試代碼的編寫
初級使用我們採用assertEquals(excepted, actual)來進行舉例。
public class NumberTest {
@Test
public void testAdd() {
assertEquals(5, new Number().add(2, 3)); //判斷2+3是不是等於5
}
@Test
public void testSubtraction() {
assertEquals(4, new Number().subtraction(8, 4)); //判斷8-4是不是等於4
}
@Test
public void testDivision() {
assertEquals(2, new Number().division(10, 5)); //判斷10/5是不是等於2
}
@Test
public void testMultiplication() {
assertEquals(12, new Number().multiplication(3, 4)); //判斷3*4是不是等於12
}
}
如果我們想要測試單獨的某個方法,需要右鍵該方法->Run as->JUnit Test,
如果是想要測試一個類中的全部方法,則右鍵類名->Run as ->JUnit Test
之後會出現一個測試窗體,如果全部是綠條,則表明所測試的對象是無誤的。測試成功。
錯誤案例
當綠條變成紅色的時候,代表測試對象有誤,其中包括兩種錯誤,一個是Failure,另一個是Error。Failure的出現代表程序運行的實際值和預期值不符,而Error的出現代表代碼本身有誤或者有隱藏的bug。
這裏我們編寫了兩個錯誤代碼,一個是測試2+3等不等於4,另一個是測試6/0等不等於3。右邊的測試框中顯示了測試結果,一個Failure,一個是Error。
Failure方法的錯誤描述是java.lang.AssertionError: expected:<4> but was:<5>,表示預期是4,但實際值是5.
Error的錯誤描述是java.lang.ArithmeticException: / by zero。意思是除數不能是0。
@Test
public void testAdd() {
assertEquals(4, new Number().add(2, 3)); //判斷2+3是不是等於4
}
@Test
public void testDivision() {
assertEquals(3, new Number().division(6, 0)); //判斷6/0是不是等於3
}
JUnit運行流程
再次新建一個測試類,這次取名爲NumberTest2,並勾選框中的四個選項
之後會自動生成代碼,需要我們手動補充,補充後的代碼如下:
public class NumberTest2 {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
System.out.println("beforeClass()");
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
System.out.println("afterClass()");
}
@Before
public void setUp() throws Exception {
System.out.println("before()");
}
@After
public void tearDown() throws Exception {
System.out.println("after()");
}
@Test
public void test() {
System.out.println("test()");
}
@Test
public void test2() {
System.out.println("test2()");
}
}
執行程序後查看程序執行順序
從以上結果我們得出結論
1. 被@BeforeClass註解修飾的方法第一個執行
2. 被@Before註解修飾的方法會在每個@Test方法執行前執行一次
3. 被@After註解修飾的方法會在每個@Test方法執行後執行一次
4. 被@AfterClass註解修飾的方法最後一個執行
利用以上特性我們可以有效的實現初始化以及結尾清除功能
進階使用
@Test的兩個參數
@Test有兩個可控的參數,分別爲timeout和expected
新建一個測試類取名爲NumberTest3,並在裏面添加如下代碼
public class NumberTest3 {
//第一個test()用來闡述timeout參數
@Test(timeout=1)
public void test() {
while(true) {
System.out.println("running.....");
}
}
//test2()用來闡述excepted參數
@Test(expected=ArithmeticException.class)
public void test2() {
assertEquals(3, new Number().division(6, 0));
}
}
test()方法中寫入的是一個死循環,按照常理來說應該是死循環直到內存溢出,現在我們對test()進行測試,控制檯的結果是
可以看到,程序自動停止了。這就是timeout參數的作用,規定程序在n毫秒之內結束,如果測試代碼沒有在n毫秒內結束,在第n毫秒時會強制停止。test()上的timeout規定在了1毫秒,所以死循環也就只執行了1毫秒。
test2()函數中寫入的是一個斷言,判斷6/0是否等於3,按照之前的測試來看,測試應該是報Error,因爲除數不能爲0,但我們加上expected之後,程序測試結果如下
綠條表示測試成功。
expected參數的含義是指期望拋出什麼異常(也就是說加上這個參數後,測試的目的就變成了看它是否會拋出指定異常,如果拋出了就是綠條,沒拋出指定異常就變紅條)
測試套件
測試套件出現的目的是爲了方便我們一次運行多個類的測試。加入我們有Test1,Test2,Test3……n個測試類,我們想要全部測試他們的功能,總不可能一個類一個類的去右鍵點擊Run as junit Test吧,因此測試套件的作用就是我們可以只執行一次run as,就可以測試所有的我們想測試的類的測試方法。可以類比工具箱,我們在搬運的時候,肯定不是一件工具一件工具的來回拿放,而是把工具放在工具箱裏,一齊進行搬運。
先在test裏創建三個測試類,也就是“工具”,裏面分別有一個test方法,該方法的內容是打印函數名稱
最後創建一個“工具箱”
注意,這個“工具箱”類裏有如下幾個特點
1. 這整個類是個空類,什麼都不要寫,它存在的意義就僅僅只是爲了我們一次性進行多類測試
2. 兩個註解放在類名上面,且第一個註解裏的內容固定爲“Suite.class”
3. 第二個註解裏的內容是說明具體有哪些“工具”需要被一次性測試。裏面放了幾個類,就會測試幾個。
參數化設置
參數化設置的目的是方便我們進行多組數據測試。
在test裏新建一個測試類,取名MultipTest,其代碼如下
//該類必須加上註釋 @RunWith(Parameterized.class),表示這個類是參數化的測試類
@RunWith(Parameterized.class)
public class MultipTest {
//參數的類型和數量取決於test()方法,這裏因爲測試的是add()方法
//因此有一個預期值,兩個加數,總共三個參數。
int expected = 0;
int input1 = 0;
int input2 = 0;
public MultipTest(int expected,int input1, int input2) {
this.expected = expected;
this.input1 = input1;
this.input2 = input2;
}
//JUnit規定了測試數據組必須是一個靜態的collection,且裏面得是一個數組。
@Parameters
public static Collection<Object[]> t(){
return Arrays.asList(new Object[][] {
{3,1,2},
{4,2,2}
});
}
@Test
public void testAdd() {
assertEquals(expected, new Number().add(input1, input2));
}
}
運行結果如圖
發現有兩條數據,這兩條數據分別對應我們往collection裏放的數據。