1. 程式人生 > >Java學習筆記(八)——JUnit單元測試

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)(這裡的計量單位是毫秒) 注意:超時測試不能取代效能測試和壓力測試