JUnit5註解學習指引
註解(Annotations)是JUnit的標誌性技術,本文就來對它的20個註解,以及元註解和組合註解進行學習。
20個註解
在org.junit.jupiter.api
包中定義了這些註解,它們分別是:
-
@Test
測試方法,可以直接運行。 -
@ParameterizedTest
參數化測試,比如:@ParameterizedTest @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" }) void palindromes(String candidate) { assertTrue(StringUtils.isPalindrome(candidate)); }
-
@RepeatedTest
重複測試,比如:@RepeatedTest(10) void repeatedTest() { // ... }
-
@TestFactory
測試工廠,專門生成測試方法,比如:import org.junit.jupiter.api.DynamicTest; @TestFactory Collection<DynamicTest> dynamicTestsFromCollection() { return Arrays.asList( dynamicTest("1st dynamic test", () -> assertTrue(isPalindrome("madam"))), dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) ); }
-
@TestTemplate
測試模板,比如:final List<String> fruits = Arrays.asList("apple", "banana", "lemon"); @TestTemplate @ExtendWith(MyTestTemplateInvocationContextProvider.class) void testTemplate(String fruit) { assertTrue(fruits.contains(fruit)); } public class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts( ExtensionContext context) { return Stream.of(invocationContext("apple"), invocationContext("banana")); } }
@TestTemplate必須註冊一個TestTemplateInvocationContextProvider,它的用法跟@Test類似。
-
@TestMethodOrder
指定測試順序,比如:import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; @TestMethodOrder(OrderAnnotation.class) class OrderedTestsDemo { @Test @Order(1) void nullValues() { // perform assertions against null values } @Test @Order(2) void emptyValues() { // perform assertions against empty values } @Test @Order(3) void validValues() { // perform assertions against valid values } }
-
@TestInstance
是否生成多個測試實例,默認JUnit每個測試方法生成一個實例,使用這個註解能讓每個類只生成一個實例,比如:@TestInstance(Lifecycle.PER_CLASS) class TestMethodDemo { @Test void test1() { } @Test void test2() { } @Test void test3() { } }
-
@DisplayName
自定義測試名字,會體現在測試報告中,比如:import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @DisplayName("A special test case") class DisplayNameDemo { @Test @DisplayName("Custom test name containing spaces") void testWithDisplayNameContainingSpaces() { } @Test @DisplayName("╯°□°)╯") void testWithDisplayNameContainingSpecialCharacters() { } @Test @DisplayName("😱") void testWithDisplayNameContainingEmoji() { } }
-
@DisplayNameGeneration
測試名字統一處理,比如:import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.IndicativeSentencesGeneration; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; class DisplayNameGeneratorDemo { @Nested @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class A_year_is_not_supported { @Test void if_it_is_zero() { } @DisplayName("A negative value for year is not supported by the leap year computation.") @ParameterizedTest(name = "For example, year {0} is not supported.") @ValueSource(ints = { -1, -4 }) void if_it_is_negative(int year) { } } @Nested @IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) class A_year_is_a_leap_year { @Test void if_it_is_divisible_by_4_but_not_by_100() { } @ParameterizedTest(name = "Year {0} is a leap year.") @ValueSource(ints = { 2016, 2020, 2048 }) void if_it_is_one_of_the_following_years(int year) { } } }
-
@BeforeEach
在每個@Test
,@RepeatedTest
,@ParameterizedTest
, or@TestFactory
之前執行。 -
@AfterEach
在每個@Test
,@RepeatedTest
,@ParameterizedTest
, or@TestFactory
之後執行。 -
@BeforeAll
在所有的@Test
,@RepeatedTest
,@ParameterizedTest
, and@TestFactory
之前執行。 -
@AfterAll
在所有的@Test
,@RepeatedTest
,@ParameterizedTest
, and@TestFactory
之後執行。 -
@Nested
嵌套測試,一個類套一個類,例子參考上面那個。 -
@Tag
打標籤,相當於分組,比如:import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag("fast") @Tag("model") class TaggingDemo { @Test @Tag("taxes") void testingTaxCalculation() { } }
-
@Disabled
禁用測試,比如:import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @Disabled("Disabled until bug #99 has been fixed") class DisabledClassDemo { @Test void testWillBeSkipped() { } }
-
@Timeout
對於test, test factory, test template, or lifecycle method,如果超時了就認為失敗了,比如:class TimeoutDemo { @BeforeEach @Timeout(5) void setUp() { // fails if execution time exceeds 5 seconds } @Test @Timeout(value = 100, unit = TimeUnit.MILLISECONDS) void failsIfExecutionTimeExceeds100Milliseconds() { // fails if execution time exceeds 100 milliseconds } }
-
@ExtendWith
註冊擴展,比如:@ExtendWith(RandomParametersExtension.class) @Test void test(@Random int i) { // ... }
JUnit5提供了標準的擴展機制來允許開發人員對JUnit5的功能進行增強。JUnit5提供了很多的標準擴展介面,第三方可以直接實現這些介面來提供自定義的行為。
-
@RegisterExtension
通過欄位註冊擴展,比如:class WebServerDemo { @RegisterExtension static WebServerExtension server = WebServerExtension.builder() .enableSecurity(false) .build(); @Test void getProductList() { WebClient webClient = new WebClient(); String serverUrl = server.getServerUrl(); // Use WebClient to connect to web server using serverUrl and verify response assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus()); } }
-
@TempDir
臨時目錄,比如:@Test void writeItemsToFile(@TempDir Path tempDir) throws IOException { Path file = tempDir.resolve("test.txt"); new ListWriter(file).write("a", "b", "c"); assertEquals(singletonList("a,b,c"), Files.readAllLines(file)); }
元註解和組合註解
JUnit Jupiter支援元註解,能繼承後實現自定義註解,比如自定義@Fast註解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Tag;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface Fast {
}
使用:
@Fast
@Test
void myFastTest() {
// ...
}
這個@Fast註解也是組合註解,甚至可以更進一步和@Test組合:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest {
}
只用@FastTest就可以了:
@FastTest
void myFastTest() {
// ...
}
小結
本文對JUnit20個主要的註解進行了介紹和示例演示,JUnit Jupiter支援元註解,可以自定義註解,也可以把多個註解組合起來。
參考資料:
//junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
//vitzhou.gitbooks.io/junit5/content/junit/extension_model.html#概述