JUnit4教程+實踐
- JUnit 是一個開放的資源框架,用於編寫和執行測試。
- 提供註解來識別測試方法。
- 提供斷言來測試預期結果。
- JUnit 測試允許你編寫程式碼更快,並能提高質量。
- JUnit 優雅簡潔。沒那麼複雜,花費時間較少。
- JUnit測試可以自動執行並且檢查自身結果並提供即時反饋。所以也沒有必要人工梳理測試結果的報告。
- JUnit測試可以被組織為測試套件,包含測試用例,甚至其他的測試套件。
- JUnit在一個條中顯示進度。如果執行良好則是綠色;如果執行失敗,則變成紅色。
三、JUnit註解
註解 | 描述 |
---|---|
@Test | 測試註解,標記一個方法可以作為一個測試用例 。 |
@Before | Before註解表示,該方法必須在類中的每個測試之前執行,以便執行某些必要的先決條件。 |
@BeforeClass | BeforeClass註解指出這是附著在靜態方法必須執行一次並在類的所有測試之前,這種情況一般用於測試計算、共享配製方法(如資料庫連線)。 |
@After | After註釋表示,該方法在每項測試後執行(如執行每一個測試後重置某些變數,刪除臨時變數等)。 |
@AfterClass | 當需要執行所有測試在JUnit測試用例類後執行,AlterClass註解可以使用以清理一些資源(如資料庫連線),注意:方法必須為靜態方法。 |
@Ignore | 當想暫時禁用特定的測試執行可以使用這個註解,每個被註解為@Ignore的方法將不再執行 |
@Runwith | @Runwith就是放在測試類名之前,用來確定這個類怎麼執行的。也可以不標註,會使用預設執行器。 |
@Parameters | 用於使用引數化功能。 |
@SuiteClasses | 用於套件測試 |
四、JUnit斷言
斷言 | 描述 |
---|---|
void assertEquals([String message],expected value,actual value) | 斷言兩個值相等。值型別可能是int,short,long,byte,char,Object,第一個引數是一個可選字串訊息 |
void assertTrue([String message],boolean condition) | 斷言一個條件為真 |
void assertFalse([String message],boolean condition) | 斷言一個條件為假 |
void assertNotNull([String message],java.lang.Object object) | 斷言一個物件不為空(null) |
void assertNull([String message],java.lang.Object object) | 斷言一個物件為空(null) |
void assertSame([String message],java.lang.Object expected,java.lang.Object actual) | 斷言兩個物件引用相同的物件 |
void assertNotSame([String message],java.lang.Object unexpected,java.lang.Object actual) | 斷言兩個物件不是引用同一個物件 |
void assertArrayEquals([String message],expectedArray,resultArray) | 斷言預期陣列和結果陣列相等,陣列型別可能是int,short,long,byte,char,Object |
讓我們看下使用斷言的例子。 AssertionTest.java
public class AssertionTest { @Test public void test() { String obj1 = "junit"; String obj2 = "junit"; String obj3 = "test"; String obj4 = "test"; String obj5 = null; int var1 = 1; int var2 = 2; int[] array1 = {1, 2, 3}; int[] array2 = {1, 2, 3}; Assert.assertEquals(obj1, obj2); Assert.assertSame(obj3, obj4); Assert.assertNotSame(obj2, obj4); Assert.assertNotNull(obj1); Assert.assertNull(obj5); Assert.assertTrue(var1 < var2); Assert.assertFalse(var1 > var2); Assert.assertArrayEquals(array1, array2); } } 複製程式碼
在以上類中我們可以看到,這些斷言方法是可以工作的。
- assertEquals() 如果比較的兩個物件是相等的,此方法將正常返回;否則失敗顯示在JUnit的視窗測試將中止。
- assertSame() 和 assertNotSame() 方法測試兩個物件引用指向完全相同的物件。
- assertNull() 和 assertNotNull() 方法測試一個變數是否為空或不為空(null)。
- assertTrue() 和 assertFalse() 方法測試if條件或變數是true還是false。
- assertArrayEquals() 將比較兩個陣列,如果它們相等,則該方法將繼續進行不會發出錯誤。否則失敗將顯示在JUnit視窗和中止測試。
五、JUnit執行過程
JuntiTest.java
public class JunitTest { @BeforeClass public static void beforeClass() { System.out.println("in before class"); } @AfterClass public static void afterClass() { System.out.println("in after class"); } @Before public void before() { System.out.println("in before"); } @After public void after() { System.out.println("in after"); } @Test public void testCase1() { System.out.println("in test case 1"); } @Test public void testCase2() { System.out.println("in test case 2"); } } 複製程式碼
通過idea執行整個測試類後,執行結果:
in before class in before in test case 1 in after in before in test case 2 in after in after class 複製程式碼
六、忽略測試
- 一個帶有@Ignore註解的測試方法不會被執行
- 如果一個測試類帶有@Ignore註解,則它的測試方法將不會被執行
我們把剛才測試類中的testCase2()方法標記為@Ignore,
@Ignore @Test public void testCase2() { System.out.println("in test case 2"); } 複製程式碼
然後在執行測試類的時候就會忽視這個方法,結果為:
in before class in before in test case 1 in after Test ignored. in after class 複製程式碼
七、時間測試
JUnit提供了一個暫停的方便選項,如果一個測試用例比起指定的毫秒數花費了更多的時間,那麼JUnit將自動將它標記為失敗,timeout引數和@Test註解一起使用,例如@Test(timeout=1000)。 繼續使用剛才的例子,現在將testCase1的執行時間延長到5000毫秒,並加上時間引數,設定超時為1000毫秒,然後執行測試類
@Test(timeout = 1000) public void testCase1() throws InterruptedException { TimeUnit.SECONDS.sleep(5000); System.out.println("in test case 1"); } 複製程式碼
testCase1被標記為失敗,並且丟擲異常,執行結果:
in before class in before in after org.junit.runners.model.TestTimedOutException: test timed out after 1000 milliseconds at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at com.lxs.JUnit.JunitTest.testCase1(JunitTest.java:35) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.FailOnTimeout$CallableStatement.call(FailOnTimeout.java:298) at org.junit.internal.runners.statements.FailOnTimeout$CallableStatement.call(FailOnTimeout.java:292) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.lang.Thread.run(Thread.java:748) in before in test case 2 in after in after class 複製程式碼
八、異常測試
Junit 用程式碼處理提供了一個追蹤異常的選項。你可以測試程式碼是否它丟擲了想要得到的異常。expected 引數和 @Test 註釋一起使用。現在讓我們看看 @Test(expected)。新建測試方法testCase3()。
@Test(expected = ArithmeticException.class) public void testCase3() { System.out.println("in test case 3"); int a = 0; int b = 1 / a; } 複製程式碼
單獨執行testCase3()方法,由於得到了一個預期異常,所以測試通過,結果為
in before class in before in test case 3 in after in after class 複製程式碼
如果沒有得到預期異常:
in before class in before in test case 3 in after java.lang.AssertionError: Expected exception: java.lang.ArithmeticException in after class 複製程式碼
九、引數化測試
Junit 4 引入了一個新的功能引數化測試。引數化測試允許開發人員使用不同的值反覆運行同 一個測試。你將遵循 5 個步驟來建立引數化測試:
-為準備使用引數化測試的測試類指定特殊的執行器 org.junit.runners.Parameterized。
- 為測試類宣告幾個變數,分別用於存放期望值和測試所用資料。
- 為測試類宣告一個帶有引數的公共建構函式,並在其中為第二個環節中宣告的幾個變數賦值。
- 為測試類宣告一個使用註解 org.junit.runners.Parameterized.Parameters 修飾的,返回值為 java.util.Collection 的公共靜態方法,並在此方法中初始化所有需要測試的引數對。
- 編寫測試方法,使用定義的變數作為引數進行測試。
什麼是@RunWith?
首先要分清幾個概念:測試方法、測試類、測試集、測試執行器。
- 其中測試方法就是用@Test註解的一些函式。
- 測試類是包含一個或多個測試方法的一個**Test.java檔案,
- 測試集是一個suite,可能包含多個測試類。
- 測試執行器則決定了用什麼方式偏好去執行這些測試集/類/方法。
而@Runwith就是放在測試類名之前,用來確定這個類怎麼執行的。也可以不標註,會使用預設執行器。常見的執行器有:
- @RunWith(Parameterized.class) 引數化執行器,配合@Parameters使用JUnit的引數化功能
- @RunWith(Suite.class) @SuiteClasses({ATest.class,BTest.class,CTest.class}) 測試集執行器配合使用測試集功能
- @RunWith(JUnit4.class),junit4的預設執行器
- @RunWith(JUnit38ClassRunner.class),用於相容junit3.8的執行器
- 一些其它執行器具備更多功能。例如@RunWith(SpringJUnit4ClassRunner.class)集成了spring的一些功能
PrimeNumberCheckerTest.java
/** * 步驟一: 指定定引數執行器 */ @RunWith(Parameterized.class) public class PrimeNumberCheckerTest { /** * 步驟二:宣告變數 */ private Integer inputNumber; private Boolean expectedResult; private PrimeNumberChecker primeNumberChecker; /** * 步驟三:為測試類宣告一個帶有引數的公共建構函式,為變數賦值 */ public PrimeNumberCheckerTest(Integer inputNumber, Boolean expectedResult) { this.inputNumber = inputNumber; this.expectedResult = expectedResult; } /** * 步驟四:為測試類宣告一個使用註解 org.junit.runners.Parameterized.Parameters 修飾的,返回值為 * java.util.Collection 的公共靜態方法,並在此方法中初始化所有需要測試的引數對 *1)該方法必須由Parameters註解修飾 2)該方法必須為public static的 3)該方法必須返回Collection型別 4)該方法的名字不做要求 5)該方法沒有引數 */ @Parameterized.Parameters public static Collection primeNumbers() { return Arrays.asList(new Object[][]{ {2, true}, {6, false}, {19, true}, {22, false}, {23, true} }); } @Before public void initialize() { primeNumberChecker = new PrimeNumberChecker(); } /** * 步驟五:編寫測試方法,使用自定義變數進行測試 */ @Test public void testPrimeNumberChecker() { System.out.println("Parameterized Number is : " + inputNumber); Assert.assertEquals(expectedResult, primeNumberChecker.validate(inputNumber)); } } 複製程式碼
PrimeNumberChecker.java
public class PrimeNumberChecker { public Boolean validate(final Integer parimeNumber) { for (int i = 2; i < (parimeNumber / 2); i++) { if (parimeNumber % i == 0) { return false; } } return true; } } 複製程式碼
JUnit會按照設定的引數多次執行,執行結果:
Parameterized Number is : 2 Parameterized Number is : 6 Parameterized Number is : 19 Parameterized Number is : 22 Parameterized Number is : 23 複製程式碼
十、套件測試
“套件測試”是指捆綁了幾個單元測試用例並執行起來。在JUnit中,@RunWith 和 @Suite 這兩個註解是用來執行套件測試。先來建立幾個測試類
public class JunitTest1 { @Test public void printMessage(){ System.out.println("in JunitTest1"); } } 複製程式碼
public class JunitTest2 { @Test public void printMessage(){ System.out.println("in JunitTest2"); } } 複製程式碼
@RunWith(Suite.class) @Suite.SuiteClasses({ /** * 此處類的配置順序會影響執行順序 */ JunitTest1.class, JunitTest2.class }) public class JunitSuite { } 複製程式碼
執行JunitSuite測試類,執行結果:
in JunitTest1 in JunitTest2 複製程式碼