# Assertions 斷言
Assertions 是 Junit 的核心部份,用來對測試需要滿足的條件進行驗證。這些斷言方法都是 org.junit.jupiter.api.Assertions
的靜態方法。
- 檢查業務邏輯返回的數據是否合理
- 所有的測試運行結束以後,會有一個詳細的測試報告
# 簡單斷言
方法 | 說明 |
---|
assertEquals | 判斷兩個對象或兩個原始類型是否相等 |
assertNotEquals | 判斷兩個對象或兩個原始類型是否不相等 |
assertSame | 判斷兩個對象引用是否指向同一個對象 |
assertNotSame | 判斷兩個對象引用是否指向不同對象 |
assertTrue | 判斷給定的 boolean 值是否為 True |
assertFalse | 判斷給定的 boolean 值是否為 False |
assertNull | 判斷給定的對象引用是否為 null |
assertNotNull | 判斷給定的對象引用是否不為 null |
# assertEquals : 判斷兩個對象或兩個原始類型是否相等
assertEquals (期待值,actual, error message”)
| @Test |
| @DisplayName("Assertions 1") |
| void testAssertEquals(){ |
| String s1 = "apple"; |
| String s2 = "banana"; |
| String s3 = s1; |
| |
| |
| assertEquals("apple banana", addTwoString(s1, s2), "the result should be the same"); |
| assertEquals(s1, s3); |
| } |
| |
| String addTwoString(String s1, String s2) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(s1); |
| sb.append(" "); |
| sb.append(s2); |
| return sb.toString(); |
| } |
# assertNotEquals :判斷兩個對象或兩個原始類型是否不相等
| @Test |
| void testAssertNotEquals(){ |
| Object obj1 = new String("apple"); |
| Object obj2 = new Object(); |
| assertNotEquals(obj1, obj2, "They shouldn't be the same obj"); |
| } |
# assertSame : 判斷兩個對象的引用是否指向同一個對象
主要是看他的 reference, 不是看 value
| @Test |
| void testAssertSame(){ |
| String s1 = "apple"; |
| String s2 = "apple"; |
| String s3 = new String("banana"); |
| String s4 = "banana"; |
| |
| assertSame(s1, s2, "They should be the same object"); |
| |
| assertSame(s4, s3, "This should be the same, this msg should be popped up"); |
| |
| assertEquals(s3, s4); |
| |
| } |
# assertTrue :判斷給定的 boolean 值是否為 true
| @Test |
| void testAssertTrue(){ |
| |
| assertTrue(8 < 2); |
| } |
# assertNull : 判斷給的對象引用是否為 null
| @Test |
| void testAssertNull(){ |
| Object obj = null; |
| assertNull(obj); |
| } |
# 數組斷言
# assertArrayEquals : 判斷兩個對象或原始類型的數組是否相等
| @Test |
| void testAssertArrayEquals(){ |
| int[] arr = new int[] {1, 2}; |
| |
| assertArrayEquals(arr, new int[]{2, 1}); |
| } |
# 組合斷言
# assertAll()
為甚麼要使用? 因為當一個簡單的斷言報錯後,接下來的斷言也不會跑,就算錯了也不會知道哪錯了。但是 asserAll 的話,可以把所有的 assertions 都測試一遍,並會報錯
| @Test |
| void testGroupAssertions(){ |
| assertAll("outside", () -> { |
| assertEquals("apple", "apple"); |
| assertAll("inside1", |
| () -> { |
| assertTrue(2==2); |
| System.out.println("This is the example"); |
| } |
| ); |
| }); |
| } |
官方文檔的例子比較清楚:
| @Test |
| void dependentAssertions() { |
| |
| |
| assertAll("properties", |
| () -> { |
| String firstName = person.getFirstName(); |
| assertNotNull(firstName); |
| |
| |
| assertAll("first name", |
| () -> assertTrue(firstName.startsWith("J")), |
| () -> assertTrue(firstName.endsWith("e")) |
| ); |
| }, |
| () -> { |
| |
| |
| String lastName = person.getLastName(); |
| assertNotNull(lastName); |
| |
| |
| assertAll("last name", |
| () -> assertTrue(lastName.startsWith("D")), |
| () -> assertTrue(lastName.endsWith("e")) |
| ); |
| } |
| ); |
| } |
# 異常斷言
| @Test |
| void exceptionTesting() { |
| Exception exception = assertThrows(ArithmeticException.class, () -> |
| calculator.divide(1, 0)); |
| assertEquals("/ by zero", exception.getMessage()); |
| } |
# 超時斷言
| @Test |
| void timeoutNotExceeded() { |
| |
| assertTimeout(ofMinutes(2), () -> { |
| |
| }); |
| } |
# 快速失敗
讓你在某些 logic 上可以顯示失敗
| @Test |
| void testFail(){ |
| if(2 == 2) { |
| fail("This is fail()"); |
| } |
| } |
# 前置條件(Assumptions)
和 assertions 不一樣的是,assumption 會 skip 掉這個 method,匯報的時候不會出現 Error,反而和 @Disable
一樣,會被 skip 掉。
尚硅谷例子:
# assumeTrue()
| @Test |
| void assume(){ |
| assumeTrue(false, "This method will be disable if it's false"); |
| } |
| } |
# 嵌套 Test @nested
- 外層的 Test 不能驅動內層的 test, 但是內層的可以
- 比如:最外層有一個 beforeEach - 用來 new 一個 stack,最內層的也有 BeforeEach - 測 stack 是不是空
- 當只 test 外層的 unit test, 內層的 BeforeEach 不會 invoke, 所以不會測 stack 是否為空
- 但是當你 invoke 內層的 BeforeEach - 測 stack 是不是空,就會 invoke 到外層的 beforeEach - 用來 new 一個 stack,然後會看這個 外層所建的 stack 是否為空
官方文檔
| @DisplayName("A stack") |
| class TestingAStackDemo { |
| |
| Stack<Object> stack; |
| |
| @Test |
| @DisplayName("is instantiated with new Stack()") |
| void isInstantiatedWithNew() { |
| new Stack<>(); |
| } |
| |
| @Nested |
| @DisplayName("when new") |
| class WhenNew { |
| |
| |
| @BeforeEach |
| void createNewStack() { |
| stack = new Stack<>(); |
| } |
| |
| @Test |
| @DisplayName("is empty") |
| void isEmpty() { |
| assertTrue(stack.isEmpty()); |
| } |
| |
| @Test |
| @DisplayName("throws EmptyStackException when popped") |
| void throwsExceptionWhenPopped() { |
| assertThrows(EmptyStackException.class, stack::pop); |
| } |
| |
| @Test |
| @DisplayName("throws EmptyStackException when peeked") |
| void throwsExceptionWhenPeeked() { |
| assertThrows(EmptyStackException.class, stack::peek); |
| } |
| |
| @Nested |
| @DisplayName("after pushing an element") |
| class AfterPushing { |
| |
| String anElement = "an element"; |
| |
| @BeforeEach |
| void pushAnElement() { |
| stack.push(anElement); |
| } |
| |
| @Test |
| @DisplayName("it is no longer empty") |
| void isNotEmpty() { |
| assertFalse(stack.isEmpty()); |
| } |
| |
| @Test |
| @DisplayName("returns the element when popped and is empty") |
| void returnElementWhenPopped() { |
| assertEquals(anElement, stack.pop()); |
| assertTrue(stack.isEmpty()); |
| } |
| |
| @Test |
| @DisplayName("returns the element when peeked but remains not empty") |
| void returnElementWhenPeeked() { |
| assertEquals(anElement, stack.peek()); |
| assertFalse(stack.isEmpty()); |
| } |
| } |
| } |
| } |
# 參數化測試 (Parameter Tests)
# @ValueSource
例子
| @ParameterizedTest |
| @ValueSource(ints = {1, 2, 3, 4, 5}) |
| void testInt(int i){ |
| System.out.println(i); |
| } |
輸出:
- 因為 @ValueSource (ints = {1, 2, 3, 4, 5}) 有 5 個數,所以會測 5 次
# @MethodSource
例子:
| @ParameterizedTest |
| @MethodSource("streamProvider") |
| void testMethod(String i){ |
| System.out.println(i); |
| } |
| |
| |
| static Stream<String> streamProvider(){ |
| return Stream.of("apple", "banana", "orange"); |
| } |
output: (也會測三次,裡面有多少 element 就會測多少次)