# 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;
        // 測試 value 是否相等,
        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";
				// 這個會返回 true, test will be passed
        assertSame(s1, s2, "They should be the same object");
				// 這個會報錯,因為沒有指向同一個 object
        assertSame(s4, s3, "This should be the same, this msg should be popped up");
				// 如果把 assertSame 改成 assertEquals 的話就不會報錯,因為他們的值都是 banana.
			  assertEquals(s3, s4);
	
    }

# assertTrue :判斷給定的 boolean 值是否為 true

@Test
    void testAssertTrue(){
				// 這樣會報錯,因為 8 不小於 2	
        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 都測試一遍,並會報錯

  • assertAll () 是可以嵌套用的
@Test
    void testGroupAssertions(){
        assertAll("outside", () -> {
            assertEquals("apple", "apple");
            assertAll("inside1",
                    () -> {
                        assertTrue(2==2);
                        System.out.println("This is the example");
                    }
            );
        });
    }

官方文檔的例子比較清楚:

@Test
    void dependentAssertions() {
        // Within a code block, if an assertion fails the
        // subsequent code in the same block will be skipped.
        assertAll("properties",
            () -> {
                String firstName = person.getFirstName();
                assertNotNull(firstName);
                // Executed only if the previous assertion is valid.
                assertAll("first name",
                    () -> assertTrue(firstName.startsWith("J")),
                    () -> assertTrue(firstName.endsWith("e"))
                );
            },
            () -> {
                // Grouped assertion, so processed independently
                // of results of first name assertions.
                String lastName = person.getLastName();
                assertNotNull(lastName);
                // Executed only if the previous assertion is valid.
                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() {
        // The following assertion succeeds.
        assertTimeout(ofMinutes(2), () -> {
            // Perform task that takes less than 2 minutes.
        });
    }

# 快速失敗

讓你在某些 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
    static Stream<String> streamProvider(){
        return Stream.of("apple", "banana", "orange");
    }

output: (也會測三次,裡面有多少 element 就會測多少次)