Java學習筆記(八)——JUnit單元測試
一、JUnit Test
(一)單元測試的概念
1 . 單元測試是針對最小的功能單元編寫測試程式碼 2 . Java程式最小的功能單元是方法 3 . 單元測試就是針對單個Java方法的測試
(二)測試驅動開發
測試驅動開發TDD:Test-Driven Development 在TDD開發模式下,在編寫一個介面之後,便可以立即編寫一個測試,之後再編寫實現類,之後執行測試。如果測試沒有通過,就繼續返回並修改程式碼,直至測試通過,完成整個測試任務。
(三)main方法測試的缺點
1 . 只能存在一個main方法,不能將測試程式碼分離 2 . 沒有打印出測試結果和期望結果
所以就需要一種測試框架來幫助我們編寫測試,這就產生了單元測試的概念
(四)單元測試的優點
1 . 可以確保單個方法執行正常 2 . 如果修改了飯方法程式碼,只需確保其對應的單元測試通過 3 . 測試程式碼本身就可以作為示例程式碼,用來演示如何呼叫該方法 4 . 可以自動化執行所有測試並獲得報告(報告可以統計成功的測試與失敗的測試,還可以統計程式碼覆蓋率)
(五)JUnit單元測試框架
1 . JUnit的概念
JUnit是一個開源的Java語言單元測試框架:
- 專門針對Java語言設計,使用最為廣泛
- JUnit是事實上的標準單元測試框架
2 . JUnit的特點
- 可以使用斷言(Assertion)來測試期望結果
- 可以方便的組織和執行測試
- 可有方便的檢視結果
- 常用IDE都集成了JUnit
- 可以方便的繼承到Maven中
3 . JUnit的設計
- TestCase:一個TestCase代表一個測試
- TestSuite:一個TestSuite包含一組TestCase,代表一組測試
- TestFixture:一個TestFixture代表一個測試環境
- TestResult:用於收集測試結果
- TestRunner:用於執行測試
- TestListener:用於監聽測試過程,收集測試資料
- Assert:用於斷言測試結果是否正確
4 . 使用Assert斷言
在使用Assert斷言時,常使用import static org.junit.Assert.*;
- 斷言相等:assertEquals(100, x)
- 斷言陣列內容相等:assertArrayEquals({1, 2, 3 }, x)
- 斷言浮點數相等:assertEquals(3.1416, x, 0.0001)(必須設定誤差值)
- 斷言為null:assertNull(x)
- 斷言真偽性:assertTrue(x > 0)/assertFalse(x < 0)
- 其他斷言:assertNotEquals/assertNotNull等
(六)編寫單元測試
- 一個TestSuite包含一組相關的測試方法
- 使用assert斷言測試結果(注意浮點數assertEquals要指定delta)
- 每個測試方法必須完全獨立
- 測試程式碼必須非常簡單
- 不能為測試程式碼再編寫測試
- 測試需要覆蓋各種輸入條件,特別是邊界條件
二、使用JUnit
(一)使用Before與After
1 . FixTure
在JUnit中,同一個測試單元內的多個測試方法,在測試前都需要初始化某些物件,並且在測試後可能需要清理資源fileInputStream.close(),也就是Test FixTure(初始化測試資源) 在JUnit中
- 使用@Before方法初始化測試資源
- 使用@After方法清理測試資源
public class CalculatorTest {
Calculator calc;
@Before
//初始化測試資源
public void setUp() {
calc = new Calculator();
}
@After
//清理測試資源
public void tearDown() {
calc = null;
}
@Test
public void testCalcAdd2Numbers() {
assertEquals(3, calc.calculate("1+2"));
}
}
2 . 執行方法
JUnit對於每一個@Test方法:
- 首先例項化方法
- 執行@Before方法
- 執行@Test方法
- 執行@After方法
所以使用@Before和@After方法可以保證:
- 單個@Test方法在執行測試前都會建立新的Test例項
- 例項變數的狀態不會傳遞給下一個@Test方法
- 單個@Test執行前後便會執行@Before和@After方法
在編寫程式碼時,@Before方法初始化的物件存放在例項欄位中,因為例項欄位的狀態不會影響到下一個@Test
3 . @BeforeClass和@AfterClass靜態方法
- 在執行所有@Test之前,會執行@BeforeClass靜態方法
- 之後進行所有測試
- 在執行所有@Test之後,執行@AfterClass靜態方法
注意:
- @BeforeClass靜態方法初始化的物件只能存放在靜態欄位中
- 靜態欄位的狀態會影響到所有@Test
4 . JUnit Fixture
- @Before:初始化測試物件,例如:Input = new FileInputStream();
- @After:銷燬@Before建立的測試物件,例如:input.close();
- @BeforeClass:初始化非常耗時的資源,例如:建立資料庫
- @AfterClass:清理@BeforeClass建立的資源,例如:刪除資料庫
下面是一段測試程式碼:
import static org.junit.Assert.*;
import org.junit.Test;
public class CalculatorTest {
@Test
public void testCalcAdd2Numbers() {
Calculator calc = new Calculator();
int r = calc.calculate("1+2");
assertEquals(3, r);
}
@Test
public void testCalcAdd3Numbers() {
Calculator calc = new Calculator();
int r = calc.calculate("1+2+5");
assertEquals(8, r);
}
@Test
public void testCalcAddLargeNumbers() {
Calculator calc = new Calculator();
int r = calc.calculate("123+456");
assertEquals(579, r);
}
@Test
public void testCalcWithWhiteSpaces() {
Calculator calc = new Calculator();
int r = calc.calculate("1 + 5 + 10 ");
assertEquals(16, r);
}
}
我們可以將通用部分改寫:
package com.feiyangedu.sample;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class CalculatorTest {
Calculator calc;
@Before
public void setUp() {
calc = new Calculator();
}
@Test
public void testCalcAdd2Numbers() {
int r = calc.calculate("1+2");
assertEquals(3, r);
}
@Test
public void testCalcAdd3Numbers() {
int r = calc.calculate("1+2+5");
assertEquals(8, r);
}
@Test
public void testCalcAddLargeNumbers() {
int r = calc.calculate("123+456");
assertEquals(579, r);
}
@Test
public void testCalcWithWhiteSpaces() {
int r = calc.calculate("1 + 5 + 10 ");
assertEquals(16, r);
}
}
可以看到在上面改寫之後的程式碼中,可以讓每一個@Test共享@Before的資源,然後便可以刪除每一個@Test中需要初始化的資源,方便進行單元測試和修改程式碼。
(二)異常測試
在Java中,我們需要對可能丟擲的異常進行測試,由於異常本身是方法簽名的一部分,所以需要我們測試錯誤的輸入是否會導致特定的異常。
1 . 如何測試異常
在Java中可以使用try…catch語句來捕獲異常,可是如果需要捕獲的異常較多,就需要我們編寫大量的try…catch語句程式碼。 所以我們測試異常還可以使用@Test(expected=Exception.class)
@Test(expected = NumberFormatException.class)
public void testNumberFormatException() {
Integer.parseInt(null);
}
在上面這段程式碼中,我們使用expected來指定異常的class。
- 在測試程式碼中,如果丟擲了指定型別的異常,則測試通過
- 如果沒有丟擲指定型別的異常,或者丟擲的異常型別錯誤,則測試失敗
- 對可能發生的每種型別的異常進行測試
(三)引數化測試
如果待測試的輸入和輸出是一組資料,可以把測試資料組織起來,用不同的測試資料呼叫相同的測試方法,這種方法稱為引數化測試。
在引數化測試過程中:
- 引數必須由靜態方法date()返回
- 返回型別必須為Collection<Object[]>
- 靜態方法必須標記為@Pararmeters
- 測試類必須標記為@RunWith(Parameterized.class)
- 構造方法引數必須與測試引數相對應
可以通過@Parameter(index)標記public欄位,這樣就不必去編寫構造方法。JUnit會自動建立@Test例項,然後將引數注入到public欄位中。
package com.feiyangedu.sample;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
//標記@RunWith
public class CalculatorTest {
@Parameters
//標記@Patameters註解
//定義靜態方法data(),返回型別是Collection,返回List型別的資料
public static Collection<?> data() {
return Arrays
.asList(new Object[][] { { "1+2", 3 }, { "1+2+5", 8 }, { "123+456", 579 }, { " 1 + 5 + 10 ", 16 } });
}
Calculator calc;
@Parameter(0)
public String input;
@Parameter(1)
public int expected;
@Before
//初始化測試資源
public void setUp() {
calc = new Calculator();
}
@Test
//編寫測試方法
public void testCalculate() {
int r = calc.calculate(this.input);
assertEquals(this.expected, r);
}
}
(四)超時測試
在JUnit測試中,可以為單個測試設定超時: 例如:超時設定為1秒:@Test(timeout=1000)(這裡的計量單位是毫秒) 注意:超時測試不能取代效能測試和壓力測試