JUnit5依賴注入與測試介面
依賴注入
以前的JUnit的類構造方法和測試方法都是不能有引數的,JUnit Jupiter有一個顛覆性的改進,就是允許它們有入參,這樣就能做依賴注入了。
如果你對pytest的fixture有了解的話,就知道這個技術是多麼的強大。
ParameterResolver是一個介面類,類構造方法和測試方法在執行時,必須由被註冊的ParameterResolver進行解析。JUnit Jupiter有三個自動註冊的內建解析器:
- TestInfoParameterResolver 引數型別為TestInfo
- RepetitionInfoParameterResolver 引數型別為RepetitionInfo
- TestReporterParameterResolver 引數型別為TestReporter
TestInfo
TestInfo包含the display name, the test class, the test method, and associated tags等資訊。
示例:
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @DisplayName("TestInfo Demo") class TestInfoDemo { TestInfoDemo(TestInfo testInfo) { assertEquals("TestInfo Demo", testInfo.getDisplayName()); } @BeforeEach void init(TestInfo testInfo) { String displayName = testInfo.getDisplayName(); assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()")); } @Test @DisplayName("TEST 1") @Tag("my-tag") void test1(TestInfo testInfo) { assertEquals("TEST 1", testInfo.getDisplayName()); assertTrue(testInfo.getTags().contains("my-tag")); } @Test void test2() { } }
RepetitionInfo
主要是@RepeatedTest
會用到,包含當前重複以及總重複次數等資訊。
TestReporter
TestReporter能用來輸出額外的資訊。
示例:
class TestReporterDemo { @Test void reportSingleValue(TestReporter testReporter) { testReporter.publishEntry("a status message"); } @Test void reportKeyValuePair(TestReporter testReporter) { testReporter.publishEntry("a key", "a value"); } @Test void reportMultipleKeyValuePairs(TestReporter testReporter) { Map<String, String> values = new HashMap<>(); values.put("user name", "dk38"); values.put("award year", "1974"); testReporter.publishEntry(values); } }
傳自定義引數
除了內建解析器,如果想傳自定義引數,那麼需要使用@ExtendWith
註冊擴充套件,比如:
@ExtendWith(RandomParametersExtension.class)
class MyRandomParametersTest {
@Test
void injectsInteger(@Random int i, @Random int j) {
assertNotEquals(i, j);
}
@Test
void injectsDouble(@Random double d) {
assertEquals(0.0, d, 1.0);
}
}
有點外掛的意思,更常見的是MockitoExtension和SpringExtension。
測試介面
JUnit Jupiter除了測試類和測試方法,其實也有測試介面,比如:
@TestInstance(Lifecycle.PER_CLASS)
interface TestLifecycleLogger {
static final Logger logger = Logger.getLogger(TestLifecycleLogger.class.getName());
@BeforeAll
default void beforeAllTests() {
logger.info("Before all tests");
}
@AfterAll
default void afterAllTests() {
logger.info("After all tests");
}
@BeforeEach
default void beforeEachTest(TestInfo testInfo) {
logger.info(() -> String.format("About to execute [%s]",
testInfo.getDisplayName()));
}
@AfterEach
default void afterEachTest(TestInfo testInfo) {
logger.info(() -> String.format("Finished executing [%s]",
testInfo.getDisplayName()));
}
}
interface TestInterfaceDynamicTestsDemo {
@TestFactory
default Stream<DynamicTest> dynamicTestsForPalindromes() {
return Stream.of("racecar", "radar", "mom", "dad")
.map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))));
}
}
@Test
, @RepeatedTest
, @ParameterizedTest
, @TestFactory
, @TestTemplate
, @BeforeEach
, and @AfterEach
能作用到介面的default
方法上。
default
方法是介面已經實現好了的方法,介面的實現類不需要再編寫實現程式碼,就能直接使用。
如果測試類是@TestInstance(Lifecycle.PER_CLASS)
註解,那麼可以使用@BeforeAll
and @AfterAll
。
測試介面可以作為模版。如果測試介面有@ExtendWith
and @Tag
註解,那麼它的實現類也會繼承tags and extensions。比如:
@Tag("timed")
@ExtendWith(TimingExtension.class)
interface TimeExecutionLogger {
}
class TestInterfaceDemo implements TestLifecycleLogger,
TimeExecutionLogger, TestInterfaceDynamicTestsDemo {
@Test
void isEqualValue() {
assertEquals(1, "a".length(), "is always equal");
}
}
結果:
INFO example.TestLifecycleLogger - Before all tests
INFO example.TestLifecycleLogger - About to execute [dynamicTestsForPalindromes()]
INFO example.TimingExtension - Method [dynamicTestsForPalindromes] took 19 ms.
INFO example.TestLifecycleLogger - Finished executing [dynamicTestsForPalindromes()]
INFO example.TestLifecycleLogger - About to execute [isEqualValue()]
INFO example.TimingExtension - Method [isEqualValue] took 1 ms.
INFO example.TestLifecycleLogger - Finished executing [isEqualValue()]
INFO example.TestLifecycleLogger - After all tests
測試介面也可以作為契約。比如:
public interface Testable<T> {
T createValue();
}
public interface EqualsContract<T> extends Testable<T> {
T createNotEqualValue();
@Test
default void valueEqualsItself() {
T value = createValue();
assertEquals(value, value);
}
@Test
default void valueDoesNotEqualNull() {
T value = createValue();
assertFalse(value.equals(null));
}
@Test
default void valueDoesNotEqualDifferentValue() {
T value = createValue();
T differentValue = createNotEqualValue();
assertNotEquals(value, differentValue);
assertNotEquals(differentValue, value);
}
}
public interface ComparableContract<T extends Comparable<T>> extends Testable<T> {
T createSmallerValue();
@Test
default void returnsZeroWhenComparedToItself() {
T value = createValue();
assertEquals(0, value.compareTo(value));
}
@Test
default void returnsPositiveNumberWhenComparedToSmallerValue() {
T value = createValue();
T smallerValue = createSmallerValue();
assertTrue(value.compareTo(smallerValue) > 0);
}
@Test
default void returnsNegativeNumberWhenComparedToLargerValue() {
T value = createValue();
T smallerValue = createSmallerValue();
assertTrue(smallerValue.compareTo(value) < 0);
}
}
實現類:
class StringTests implements ComparableContract<String>, EqualsContract<String> {
@Override
public String createValue() {
return "banana";
}
@Override
public String createSmallerValue() {
return "apple"; // 'a' < 'b' in "banana"
}
@Override
public String createNotEqualValue() {
return "cherry";
}
}
小結
本文先介紹了JUnit Jupiter的顛覆性技術,允許傳參以實現依賴注入,然後介紹了除了測試類和測試方法以外的測試介面,它既可以作為測試模板,也可以作為測試契約。
參考資料:
https://junit.org/junit5/docs/current/user-guide/#writing-tests-dependency-injection
https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-interfaces-and-default-methods
所有文章公眾號首發!
如果你覺得這篇文章寫的還不錯的話,關注公眾號“dongfanger”,你的支援就是我寫文章的最大動力。
版權申明:本文為博主原創文章,轉載請保留原文連結及作者。