JUnit5在2017年就發佈了,你還在用junit4嗎?html
與之前的JUnit版本不一樣,JUnit 5由三個不一樣子項目的多個不一樣模塊組成。java
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintagegit
JUnit Platform爲在JVM上啓動測試框架提供基礎。它還定義了TestEngine API, 用來開發在平臺上運行的測試框架。此外,平臺提供了一個控制檯啓動器],用於從命令行啓動平臺,併爲Gradle和Maven提供構建插件以[基於JUnit 4的Runner,用於在平臺上運行任意TestEngine
。github
JUnit Jupiter是在JUnit 5中編寫測試和擴展的新型編程模型和[擴展模型][]的組合.Jupiter子項目提供了TestEngine
,用於在平臺上運行基於Jupiter的測試。編程
JUnit Vintage提供TestEngine
,用於在平臺上運行基於JUnit 3和JUnit 4的測試。api
自從有了相似 JUnit 之類的測試框架,Java 單元測試領域逐漸成熟,開發人員對單元測試框架也有了更高的要求:更多的測試方式,更少的其餘庫的依賴。app
所以,你們期待着一個更強大的測試框架誕生,JUnit 做爲Java測試領域的領頭羊,推出了 JUnit 5 這個版本,主要特性:框架
@Test: 表示方法是測試方法。可是與JUnit4的@Test不一樣,他的職責很是單一不能聲明任何屬性,拓展的測試將會由Jupiter提供額外測試maven
@ParameterizedTest: 表示方法是參數化測試ide
@RepeatedTest: 表示方法可重複執行
@DisplayName: 爲測試類或者測試方法設置展現名稱
@BeforeEach: 表示在每一個單元測試以前執行
@AfterEach: 表示在每一個單元測試以後執行
@BeforeAll: 表示在全部單元測試以前執行
@AfterAll: 表示在全部單元測試以後執行
@Tag: 表示單元測試類別,相似於JUnit4中的@Categories
@Disabled: 表示測試類或測試方法不執行,相似於JUnit4中的@Ignore
@Timeout: 表示測試方法運行若是超過了指定時間將會返回錯誤
@ExtendWith: 爲測試類或測試方法提供擴展類引用
經常使用註解格式:
class StandardTests { //與junit4的@beforeClass相似,每一個測試類運行一次 @BeforeAll static void initAll() { } //與junit4中@before相似,每一個測試用例都運行一次 @BeforeEach void init() { } @Test @DisplayName("成功測試") void succeedingTest() { } @Test @DisplayName("失敗測試") void failingTest() { fail("a failing test"); } //禁用測試用例 @Test @Disabled("for demonstration purposes") void skippedTest() { // not executed } @Test void abortedTest() { assumeTrue("abc".contains("Z")); fail("test should have been aborted"); } //與@BeforeEach對應,每一個測試類執行一次,通常用於恢復環境 @AfterEach void tearDown() { } //與@BeforeAll對應,每一個測試類執行一次,通常用於恢復環境 @AfterAll static void tearDownAll() { } }
@DisplayName("顯示名稱測試") class DisplayNameDemo { @Test @DisplayName("個人 第一個 測試 用例") void testWithDisplayNameContainingSpaces() { } @Test @DisplayName("╯°□°)╯") void testWithDisplayNameContainingSpecialCharacters() { } @Test @DisplayName("😱") void testWithDisplayNameContainingEmoji() { } }
IDE運行測試結果顯示:
優勢:經過這種方式,能夠在方法名是英文特別長或者很難用英文描述清楚的場景下,增長中文解釋
JUnit Jupiter提供了許多JUnit4已有的斷言方法,並增長了一些適合與Java 8 lambda一塊兒使用的斷言方法。全部JUnit Jupiter斷言都是[org.junit.jupiter.Assertions]類中的靜態方法。
分組斷言:
多個條件同時知足時才斷言成功
@Test void groupedAssertions() { Person person = new Person(); Assertions.assertAll("person", () -> assertEquals("niu", person.getName()), () -> assertEquals(18, person.getAge()) ); }
異常斷言:
Junit4時須要使用rule方式,junit5提供了assertThrows更優雅的異常斷言
@Test void exceptionTesting() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("a message"); }); assertEquals("a message", exception.getMessage()); }
超時斷言:
@Test @DisplayName("超時測試") public void timeoutTest() { Assertions.assertTimeout(Duration.ofMillis(100), () -> Thread.sleep(50)); }
經過標籤把測試分組,在不一樣階段執行不一樣的邏輯測試,好比劃分爲快速冒煙測試和執行慢但也重要的測試
@Test @Tag("fast") void testing_faster() { } @Test @Tag("slow") void testing_slow() { }
而後經過配置maven-surefire-plugin插件
<plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.0</version> <configuration> <properties> <includeTags>fast</includeTags> <excludeTages>slow</excludeTages> </properties> </configuration> </plugin>
當咱們編寫的類和代碼逐漸增多,隨之而來的須要測試的對應測試類也會愈來愈多。
爲了解決測試類數量爆炸的問題,JUnit 5提供了@Nested 註解,可以以靜態內部成員類的形式對測試用例類進行邏輯分組。
而且每一個靜態內部類均可以有本身的生命週期方法, 這些方法將按從外到內層次順序執行。
此外,嵌套的類也能夠用@DisplayName 標記,這樣咱們就可使用正確的測試名稱。下面看下簡單的用法:
@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()); } @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()); } } } }
junit沒有限制嵌套層數,除非必要通常不建議使用超過3層,過於複雜的層次結構會增長開發者理解用例關係的難度
在以前的全部JUnit版本中,測試構造函數或方法都不容許有參數(至少不能使用標準的Runner實現)。做爲JUnit Jupiter的主要變化之一,測試構造函數和方法如今都容許有參數。這帶來了更大的靈活性,併爲構造函數和方法啓用依賴注入
@Test @DisplayName("test-first") @Tag("my-tag") void test1(TestInfo testInfo) { assertEquals("test-first", testInfo.getDisplayName()); assertTrue(testInfo.getTags().contains("my-tag")); } @Test @DisplayName("test-second") @Tag("my-tag") void test2(TestReporter testReporter) { testReporter.publishEntry("a key", "a value"); }
屢次調用同一個測試用例
@RepeatedTest(10) @DisplayName("重複測試") public void testRepeated() { //... }
動態測試只須要編寫一處代碼,就能一次性對各類類型的輸入和輸出結果進行驗證
@TestFactory @DisplayName("動態測試") Stream<DynamicTest> dynamicTests() { List<Person> persons = getAllPerson(); return persons.stream() .map(person -> DynamicTest.dynamicTest(person.getName() + "-test", () -> assertTrue(person.getName().contains("niu")))); }
經過時間來驗證用例是否超時,通常要求單個單元測試不該該超過1秒
class TimeoutDemo { @BeforeEach @Timeout(5) void setUp() { // fails if execution time exceeds 5 seconds } @Test @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) void failsIfExecutionTimeExceeds1000Milliseconds() { // fails if execution time exceeds 1000 milliseconds //也可用這種方式 Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(1500)); } }
參數測試我以爲是最好用的特性,能夠大量減小重複模板式代碼,也是junit5最驚豔的提高,強烈推薦使用
@ValueSource: 爲參數化測試指定入參來源,支持八大基礎類以及String類型,Class類型
@NullSource: 表示爲參數化測試提供一個null的入參
@EnumSource: 表示爲參數化測試提供一個枚舉入參
@CsvSource:表示讀取CSV格式內容做爲參數化測試入參
@CsvFileSource:表示讀取指定CSV文件內容做爲參數化測試入參
@MethodSource:表示讀取指定方法的返回值做爲參數化測試入參(注意方法返回須要是一個流)
@ArgumentsSource:指定一個自定義的,可重用的
ArgumentsProvider
。
看完用法描述,簡直太喜歡了
一個頂三個基礎測試用例
@ParameterizedTest @ValueSource(strings = {"one", "two", "three"}) @DisplayName("參數化測試1") public void parameterizedTest1(String string) { assertTrue(StringUtils.isNotBlank(string)); }
若是不是基礎的類型,可使用方法構造,只要返回值爲Stream類型就能夠,多個參數使用Arguments
實例流
@ParameterizedTest @MethodSource("method") @DisplayName("方法來源參數") public void testWithExplicitLocalMethodSource(String name) { Assertions.assertNotNull(name); } private static Stream<String> method() { return Stream.of("apple", "banana"); }
@CsvSource
容許您將參數列表表示爲以逗號分隔的值(例如,字符串文字)
@ParameterizedTest @CsvSource({"steven,18", "jack,24"}) @DisplayName("參數化測試-csv格式") public void parameterizedTest3(String name, Integer age) { System.out.println("name:" + name + ",age:" + age); Assertions.assertNotNull(name); Assertions.assertTrue(age > 0); }
@CsvFileSource
使用classpath中的CSV文件,CSV文件中的每一行都會致使參數化測試的一次調用
這種就徹底把測試數據與測試方法隔離,達到更好解耦效果
@ParameterizedTest @CsvFileSource(resources = "/persons.csv") //指定csv文件位置 @DisplayName("參數化測試-csv文件") public void parameterizedTest2(String name, Integer age) { System.out.println("name:" + name + ",age:" + age); Assertions.assertNotNull(name); Assertions.assertTrue(age > 0); }
其餘方式不在贅述,若是仍是知足不了需求,能夠經過@ArgumentsSource自定義本身的數據來源,必須封裝成去取JSON或者XMl等數據
當定義好須要運行的測試方法後,下一步則是須要關注測試方法的細節,這就離不開斷言和假設
斷言:封裝好了經常使用判斷邏輯,當不知足條件時,該測試用例會被認爲測試失敗
假設:與斷言相似,當條件不知足時,測試會直接退出而不是斷定爲失敗
由於不會影響到後續的測試用例,最經常使用的仍是斷言
除了Junit5自帶的斷言,AssertJ是很是好用的一個斷言工具,最大特色是提供了流式斷言,與Java8使用方法很是相似
@Test void testString() { // 斷言null或爲空字符串 assertThat("").isNullOrEmpty(); // 斷言空字符串 assertThat("").isEmpty(); // 斷言字符串相等 斷言忽略大小寫判斷字符串相等 assertThat("niu").isEqualTo("niu").isEqualToIgnoringCase("NIu"); // 斷言開始字符串 結束字符穿 字符串長度 assertThat("niu").startsWith("ni").endsWith("u").hasSize(3); // 斷言包含字符串 不包含字符串 assertThat("niu").contains("iu").doesNotContain("love"); // 斷言字符串只出現過一次 assertThat("niu").containsOnlyOnce("iu"); } @Test void testNumber() { // 斷言相等 assertThat(42).isEqualTo(42); // 斷言大於 大於等於 assertThat(42).isGreaterThan(38).isGreaterThanOrEqualTo(38); // 斷言小於 小於等於 assertThat(42).isLessThan(58).isLessThanOrEqualTo(58); // 斷言0 assertThat(0).isZero(); // 斷言正數 非負數 assertThat(1).isPositive().isNotNegative(); // 斷言負數 非正數 assertThat(-1).isNegative().isNotPositive(); } @Test void testCollection() { // 斷言 列表是空的 assertThat(newArrayList()).isEmpty(); // 斷言 列表的開始 結束元素 assertThat(newArrayList(1, 2, 3)).startsWith(1).endsWith(3); // 斷言 列表包含元素 而且是排序的 assertThat(newArrayList(1, 2, 3)).contains(1, atIndex(0)).contains(2, atIndex(1)).contains(3) .isSorted(); // 斷言 被包含與給定列表 assertThat(newArrayList(3, 1, 2)).isSubsetOf(newArrayList(1, 2, 3, 4)); // 斷言 存在惟一元素 assertThat(newArrayList("a", "b", "c")).containsOnlyOnce("a"); } @Test void testMap() { Map<String, Object> foo = ImmutableMap.of("A", 1, "B", 2, "C", 3); // 斷言 map 不爲空 size assertThat(foo).isNotEmpty().hasSize(3); // 斷言 map 包含元素 assertThat(foo).contains(entry("A", 1), entry("B", 2)); // 斷言 map 包含key assertThat(foo).containsKeys("A", "B", "C"); // 斷言 map 包含value assertThat(foo).containsValue(3); } // 其餘斷言,請自行探索......
想一想若是沒有使用AssertJ時咱們是如何寫斷言的,是否是須要多個assert,很繁瑣
AssertJ的斷言代碼清爽不少,流式斷言充分利用了java8以後的匿名方法和stream類型的特色,很好的對Junit斷言方法作了補充。
參考