單元測試是編寫測試代碼,用來檢測特定的、明確的、細顆粒的功能。單元測試並不必定保證程序功能是正確的,更不保證總體業務是準備的。java
單元測試不只僅用來保證當前代碼的正確性,更重要的是用來保證代碼修復、改進或重構以後的正確性。數據結構
通常來講,單元測試任務包括ide
- 接口功能測試:用來保證接口功能的正確性。
- 局部數據結構測試(不經常使用):用來保證接口中的數據結構是正確的
- 好比變量有無初始值
- 變量是否溢出
- 邊界條件測試
- 變量沒有賦值(即爲NULL)
- 變量是數值(或字符)
- 主要邊界:最小值,最大值,無窮大(對於DOUBLE等)
- 溢出邊界(指望異常或拒絕服務):最小值-1,最大值+1
- 臨近邊界:最小值+1,最大值-1
- 變量是字符串
- 引用「字符變量」的邊界
- 空字符串
- 對字符串長度應用「數值變量」的邊界
- 變量是集合
- 空集合
- 對集合的大小應用「數值變量」的邊界
- 調整次序:升序、降序
- 變量有規律
- 好比對於Math.sqrt,給出n^2-1,和n^2+1的邊界
- 全部獨立執行通路測試:保證每一條代碼,每一個分支都通過測試
- 代碼覆蓋率
- 語句覆蓋:保證每個語句都執行到了
- 斷定覆蓋(分支覆蓋):保證每個分支都執行到
- 條件覆蓋:保證每個條件都覆蓋到true和false(即if、while中的條件語句)
- 路徑覆蓋:保證每個路徑都覆蓋到
- 相關軟件
- Cobertura:語句覆蓋
- Emma: Eclipse插件Eclemma
- 各條錯誤處理通路測試:保證每個異常都通過測試
單元測試單元測試
Junit : Junit3 和Junit4 測試
Junit3ui
JUnit3中,測試用例須要繼承TestCase
類。JUnit4中,測試用例無需繼承TestCase
類,只須要使用@Test
等註解。spa
先看一個Junit3的樣例.net
- // 測試java.lang.Math
- // 必須繼承TestCase
- public class Junit3TestCase extends TestCase {
- public Junit3TestCase() {
- super();
- }
-
- // 傳入測試用例名稱
- public Junit3TestCase(String name) {
- super(name);
- }
-
- // 在每一個Test運行以前運行
- @Override
- protected void setUp() throws Exception {
- System.out.println("Set up");
- }
- // 測試方法。
- // 方法名稱必須以test開頭,沒有參數,無返回值,是公開的,能夠拋出異常
- // 也即相似public void testXXX() throws Exception {}
- public void testMathPow() {
- System.out.println("Test Math.pow");
- Assert.assertEquals(4.0, Math.pow(2.0, 2.0));
- }
-
- public void testMathMin() {
- System.out.println("Test Math.min");
- Assert.assertEquals(2.0, Math.min(2.0, 4.0));
- }
-
- // 在每一個Test運行以後運行
- @Override
- protected void tearDown() throws Exception {
- System.out.println("Tear down");
- }
- }
若是採用默認的TestSuite,則測試方法必須是public void testXXX() [throws Exception] {}
的形式,而且不能存在依賴關係,由於測試方法的調用順序是不可預知的。
上例執行後,控制檯會輸出插件
- Set up
- Test Math.pow
- Tear down
- Set up
- Test Math.min
- Tear down
從中,能夠猜想到,對於每一個測試方法,調用的形式是:code
- testCase.setUp();
- testCase.testXXX();
- testCase.tearDown();
運行測試方法
在Eclipse中,能夠直接在類名或測試方法上右擊,在彈出的右擊菜單中選擇Run As -> JUnit Test。
在Mvn中,能夠直接經過mvn test
命令運行測試用例。
也能夠經過Java方式調用,建立一個TestCase
實例,而後重載runTest()
方法,在其方法內調用測試方法(能夠多個)。
- TestCase test = new Junit3TestCase("mathPow") {
- // 重載
- protected void runTest() throws Throwable {
- testMathPow();
- };
- };
- test.run();
更加便捷地,能夠在建立TestCase
實例時直接傳入測試方法名稱,JUnit會自動調用此測試方法,如
- TestCase test = new Junit3TestCase("testMathPow");
- test.run();
Junit TestSuite
TestSuite是測試用例套件,可以運行過個測試方法。若是不指定TestSuite,會建立一個默認的TestSuite。默認TestSuite會掃描當前內中的全部測試方法,而後運行。
若是不想採用默認的TestSuite,則能夠自定義TestSuite。在TestCase中,能夠經過靜態方法suite()
返回自定義的suite。
- import junit.framework.Assert;
- import junit.framework.Test;
- import junit.framework.TestCase;
- import junit.framework.TestSuite;
-
- public class Junit3TestCase extends TestCase {
- //...
- public static Test suite() {
- System.out.println("create suite");
- TestSuite suite = new TestSuite();
- suite.addTest(new Junit3TestCase("testMathPow"));
- return suite;
- }
- }
容許上述方法,控制檯輸出
寫道
create suite
Set up
Test Math.pow
Tear down
而且只運行了testMathPow
測試方法,而沒有運行testMathMin
測試方法。經過顯式指定測試方法,能夠控制測試執行的順序。
也能夠經過Java的方式建立TestSuite,而後調用TestCase,如
- // 先建立TestSuite,再添加測試方法
- TestSuite testSuite = new TestSuite();
- testSuite.addTest(new Junit3TestCase("testMathPow"));
-
- // 或者 傳入Class,TestSuite會掃描其中的測試方法。
- TestSuite testSuite = new TestSuite(Junit3TestCase.class,Junit3TestCase2.class,Junit3TestCase3.class);
-
- // 運行testSuite
- TestResult testResult = new TestResult();
- testSuite.run(testResult);
testResult中保存了不少測試數據,包括運行測試方法數目(runCount
)等。
JUnit4
與JUnit3不一樣,JUnit4經過註解的方式來識別測試方法。目前支持的主要註解有:
@BeforeClass
全局只會執行一次,並且是第一個運行
@Before
在測試方法運行以前運行
@Test
測試方法
@After
在測試方法運行以後容許
@AfterClass
全局只會執行一次,並且是最後一個運行
@Ignore
忽略此方法
下面舉一個樣例:
- import org.junit.After;
- import org.junit.AfterClass;
- import org.junit.Assert;
- import org.junit.Before;
- import org.junit.BeforeClass;
- import org.junit.Ignore;
- import org.junit.Test;
-
- public class Junit4TestCase {
-
- @BeforeClass
- public static void setUpBeforeClass() {
- System.out.println("Set up before class");
- }
-
- @Before
- public void setUp() throws Exception {
- System.out.println("Set up");
- }
-
- @Test
- public void testMathPow() {
- System.out.println("Test Math.pow");
- Assert.assertEquals(4.0, Math.pow(2.0, 2.0), 0.0);
- }
-
- @Test
- public void testMathMin() {
- System.out.println("Test Math.min");
- Assert.assertEquals(2.0, Math.min(2.0, 4.0), 0.0);
- }
-
- // 指望此方法拋出NullPointerException異常
- @Test(expected = NullPointerException.class)
- public void testException() {
- System.out.println("Test exception");
- Object obj = null;
- obj.toString();
- }
-
- // 忽略此測試方法
- @Ignore
- @Test
- public void testMathMax() {
- Assert.fail("沒有實現");
- }
- // 使用「假設」來忽略測試方法
- @Test
- public void testAssume(){
- System.out.println("Test assume");
- // 當假設失敗時,則會中止運行,但這並不會意味測試方法失敗。
- Assume.assumeTrue(false);
- Assert.fail("沒有實現");
- }
-
- @After
- public void tearDown() throws Exception {
- System.out.println("Tear down");
- }
-
- @AfterClass
- public static void tearDownAfterClass() {
- System.out.println("Tear down After class");
- }
-
- }
若是細心的話,會發現Junit3的package是junit.framework
,而Junit4是org.junit
。
執行此用例後,控制檯會輸出
寫道
Set up before class
Set up
Test Math.pow
Tear down
Set up
Test Math.min
Tear down
Set up
Test exception
Tear down
Set up
Test assume
Tear down
Tear down After class
能夠看到,執行次序是@BeforeClass
-> @Before
-> @Test
-> @After
-> @Before
-> @Test
-> @After
-> @AfterClass
。@Ignore
會被忽略。
運行測試方法
與Junit3相似,能夠在Eclipse中運行,也能夠經過mvn test
命令運行。
Assert
Junit3和Junit4都提供了一個Assert類(雖然package不一樣,可是大體差很少)。Assert類中定義了不少靜態方法來進行斷言。列表以下:
- assertTrue(String message, boolean condition) 要求condition == true
- assertFalse(String message, boolean condition) 要求condition == false
- fail(String message) 必然失敗,一樣要求代碼不可達
- assertEquals(String message, XXX expected,XXX actual) 要求expected.equals(actual)
- assertArrayEquals(String message, XXX[] expecteds,XXX [] actuals) 要求expected.equalsArray(actual)
- assertNotNull(String message, Object object) 要求object!=null
- assertNull(String message, Object object) 要求object==null
- assertSame(String message, Object expected, Object actual) 要求expected == actual
- assertNotSame(String message, Object unexpected,Object actual) 要求expected != actual
- assertThat(String reason, T actual, Matcher matcher) 要求matcher.matches(actual) == true