天天看點

springboot單元測試 JUnit5

JUnit5簡介

Spring Boot 2.2.0 版本開始引入 JUnit 5 作為單元測試預設庫

​​JUnit 5官方文檔​​

作為最新版本的JUnit架構,JUnit5與之前版本的JUnit架構有很大的不同。由三個不同子項目的幾個不同子產品組成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: Junit Platform是在JVM上啟動測試架構的基礎,不僅支援Junit自制的測試引擎,其他測試引擎也都可以接入。
  • JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的程式設計模型,是JUnit5新特性的核心。内部包含了一個測試引擎,用于在Junit Platform上運作。
  • JUnit Vintage: 由于JUint已經發展多年,為了照顧老的項目,JUnit Vintage提供了相容JUnit4.x,JUnit3.x的測試引擎。
springboot單元測試 JUnit5

注意:

  • SpringBoot 2.4 以上版本移除了預設對 Vintage 的依賴。如果需要相容JUnit4需要自行引入(不能使用JUnit4的功能 @Test)。
  • JUnit 5’s Vintage已經從spring-boot-starter-test從移除。如果需要繼續相容Junit4需要自行引入Vintage依賴:
<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

      
  • 使用添加JUnit 5,添加對應的starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
      

JUnit的基本單元測試模闆

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;//注意不是org.junit.Test(這是JUnit4版本的)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootApplicationTests {

    @Autowired
    private Component component;
    
    @Test
    //@Transactional 标注後連接配接資料庫有復原功能
    public void contextLoads() {
        Assertions.assertEquals(5, component.getFive());
    }
}

      

常用注解

​​官方文檔 - Annotations​​

  • SpringBootTest: 表示使用spring做為測試驅動
  • **@Test : **表示方法是測試方法。
  • @ParameterizedTest : 表示方法是參數化測試,下方會有詳細介紹
  • @RepeatedTest(n) : 表示方法可重複執行n次
  • **@DisplayName : **給測試類或者測試方法設定展示名稱
  • @BeforeEach :表示在每個單元測試之前執行
  • @AfterEach :表示在每個單元測試之後執行
  • @BeforeAll :表示在所有單元測試之前執行
  • @AfterAll :表示在所有單元測試之後執行
  • @Tag :表示單元測試類别,類似于JUnit4中的@Categories
  • @Disabled : 表示測試類或測試方法不執行,類似于JUnit4中的@Ignore
  • @Timeout :表示測試方法運作如果超過了指定時間将會傳回錯誤
  • @ExtendWith : 為測試類或測試方法提供擴充類引用
@SpringBootTest
@DisplayName("junit5功能測試類")
public class JUnit5Test {

    @Test
    void test1(){
        System.out.println("測試方法1");
    }

    @Disabled
    @DisplayName("測試方法2")
    @Test
    void test2() {
        System.out.println(2);
    }

    @RepeatedTest(5)
    @Test
    void test3() {
        System.out.println(5);
    }

    /**
     * 規定方法逾時時間。超出則報異常
     *
     * @throws InterruptedException
     */
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    @Test
    void testTimeout() throws InterruptedException {
        Thread.sleep(100);
    }


    @BeforeEach
    void testBeforeEach() {
        System.out.println("@BeforeEach 測試就要開始了...");
    }

    @AfterEach
    void testAfterEach() {
        System.out.println("@AfterEach 測試結束了...");
    }

    @BeforeAll
    static void testBeforeAll() {
        System.out.println("@BeforeAll 所有測試就要開始了...");
    }

    @AfterAll
    static void testAfterAll() {
        System.out.println("@AfterAll 所有測試以及結束了...");

    }

}
      
  • ​@BeforeEach​

    ​ 和 ​

    ​@AfterEach​

    ​每個測試方法執行時都會生效。​

    ​@BeforeAll​

    ​和​

    ​@AfterAll​

    ​隻會生效一次

斷言機制

斷言Assertion是測試方法中的核心部分,用來對測試需要滿足的條件進行驗證。這些斷言方法都是org.junit.jupiter.api.Assertions的靜态方法。檢查業務邏輯傳回的資料是否合理。所有的測試運作結束以後,會有一個詳細的測試報告。

JUnit 5 内置的斷言可以分成如下幾個類别:

方法 說明
assertEquals 判斷兩個對象或兩個原始類型是否相等
assertNotEquals 判斷兩個對象或兩個原始類型是否不相等
assertSame 判斷兩個對象引用是否指向同一個對象
assertNotSame 判斷兩個對象引用是否指向不同的對象
assertTrue 判斷給定的布爾值是否為 true
assertFalse 判斷給定的布爾值是否為 false
assertNull 判斷給定的對象引用是否為 null
assertNotNull 判斷給定的對象引用是否不為 null

簡單斷言

@Test
@DisplayName("simple assertion")
public void simple() {
     assertEquals(3, 1 + 2, "simple math");
     assertNotEquals(3, 1 + 1);

     assertNotSame(new Object(), new Object());
     Object obj = new Object();
     assertSame(obj, obj);

     assertFalse(1 > 2);
     assertTrue(1 < 2);

     assertNull(null);
     assertNotNull(new Object());
}
      

數組斷言

@Test
@DisplayName("array assertion")
public void array() {
  assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
      

組合斷言

​assertAll()​

​方法接受多個 ​

​org.junit.jupiter.api.Executable​

​ 函數式接口的執行個體作為要驗證的斷言,且所有斷言都需通過,可以通過 lambda 表達式很容易的提供這些斷言。

@Test
@DisplayName("assert all")
public void all() {
 assertAll("Math",
    () -> assertEquals(2, 1 + 1),
    () -> assertTrue(1 > 0)
 );
}
      

異常斷言

在JUnit4時期,想要測試方法的異常情況時,需要用​

​@Rule​

​注解的​

​ExpectedException​

​變量還是比較麻煩的。而JUnit5提供了一種新的斷言方式​

​Assertions.assertThrows()​

​,配合函數式程式設計就可以進行使用。

@Test
@DisplayName("異常測試")    
public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(
        //抛出指定異常才測試通過
        ArithmeticException.class, () -> System.out.println(1 % 0));    
}
      

逾時斷言

JUnit5還提供了​

​Assertions.assertTimeout()​

​為測試方法設定了逾時時間。

@Test
@DisplayName("逾時測試")
public void timeoutTest() {
    //如果測試方法執行時間超過1s将會異常
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
      

快速失敗

通過 fail 方法直接使得測試失敗。

@Test
@DisplayName("fail")
public void shouldFail() {
  fail("This should fail");
}
      

前置條件

JUnit 5 中的前置條件​

​assumptions​

​(假設)類似于斷言,不同之處在于不滿足的斷言會使得測試方法失敗,而不滿足的前置條件隻會使得測試方法的執行終止。前置條件可以看成是測試方法執行的前提,當該前提不滿足時,就沒有繼續執行的必要。

@DisplayName("前置條件")
public class AssumptionsTest {
 private final String environment = "DEV";
 
 @Test
 @DisplayName("simple")
 public void simpleAssume() {
    assumeTrue(Objects.equals(this.environment, "DEV"));
    assumeFalse(() -> Objects.equals(this.environment, "PROD"));
 }
 
 @Test
 @DisplayName("assume then do")
 public void assumeThenDo() {
    assumingThat(
       Objects.equals(this.environment, "DEV"),
       () -> System.out.println("In DEV")
    );
 }
}
      
  • assumeTrue 和 assumFalse 確定給定的條件為 true 或 false,不滿足條件會使得測試執行終止。
  • assumingThat 的參數是表示條件的布爾值和對應的 Executable 接口的實作對象。隻有條件滿足時,Executable 對象才會被執行;當條件不滿足時,測試執行并不會終止。

嵌套測試

​​官方文檔 - Nested Tests​​

JUnit 5 可以通過 Java 中的内部類和​

​@Nested​

​ 注解實作嵌套測試,進而可以更好的把相關的測試方法組織在一起。在内部類中可以使用​

​@BeforeEach​

​ 和​

​@AfterEach​

​注解,而且嵌套的層次沒有限制。

@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());
            }
        }
    }
}

      

參數化測試

​​官方文檔 - Parameterized Tests​​

參數化測試是JUnit5很重要的一個新特性,它使得用不同的參數多次運作測試成為了可能,也為我們的單元測試帶來許多便利。利用@ValueSource等注解,指定入參,我們将可以使用不同的參數進行多次單元測試,而不需要每新增一個參數就新增一個單元測試,省去了很多備援代碼。

  • @ValueSource: 為參數化測試指定入參來源,支援八大基礎類以及String類型、Class類型
  • @NullSource: 表示為參數化測試提供一個null的入參
  • @EnumSource: 表示為參數化測試提供一個枚舉入參
  • @CsvFileSource:表示讀取指定CSV檔案内容作為參數化測試入參
  • @MethodSource:表示讀取指定方法的傳回值作為參數化測試入參(注意方法傳回需要是一個流)

當然如果參數化測試僅僅隻能做到指定普通的入參還達不到讓我覺得驚豔的地步。讓我真正感到他的強大之處的地方在于他可以支援外部的各類入參。如:CSV,YML,JSON 檔案甚至方法的傳回值也可以作為入參。隻需要去實作ArgumentsProvider接口,任何外部檔案都可以作為它的入參。

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("參數化測試1")
public void parameterizedTest1(String string) {
    System.out.println(string);
    Assertions.assertTrue(StringUtils.isNotBlank(string));
}


@ParameterizedTest
@MethodSource("method")    //指定方法名
@DisplayName("方法來源參數")
public void testWithExplicitLocalMethodSource(String name) {
    System.out.println(name);
    Assertions.assertNotNull(name);
}

static Stream<String> method() {
    return Stream.of("apple", "banana");
}

      

繼續閱讀