关于JUnit5 你必须知道的(二)JUnit 5的新特性
之前介紹了下JUnit 5的架構(gòu)和如何在maven下運(yùn)行JUnit 5測(cè)試。這篇博客主要介紹下JUnit 5的新特性。
- 包可見性
- 常用注解
- 測(cè)試命名
- 斷言
- tag標(biāo)記
- 擴(kuò)展機(jī)制
包可見性
在JUnit 4里我們的測(cè)試方法必須定義為public的訪問級(jí)別,如果沒有定義成public,雖然編譯的時(shí)候不會(huì)提示異常,但是在運(yùn)行時(shí)會(huì)提示 “java.lang.Exception: Method testIsBlank() should be public” 如下錯(cuò)誤信息。
而在JUnit 5里,我們不再需要將測(cè)試類與測(cè)試方法定義為public了,默認(rèn)的包可見的訪問級(jí)別就可以了。
那就能偷懶少敲幾個(gè)單詞了哈哈!
常用注解
對(duì)于在JUnit 4中的常用注解,你都可以在JUnit 5中找到對(duì)應(yīng)的注解,關(guān)系如下:
| @Test | @Test | 指明被注解的方法是一個(gè)測(cè)試方法,注意JUnit 5的@Test在jupiter-api里 |
| @BeforeAll | @BeforeClass | 被注解的靜態(tài)方法會(huì)在當(dāng)前類的所有@Test方法執(zhí)行前執(zhí)行一次 |
| @BeforeEach | @Before | 被注解的方法會(huì)在當(dāng)前類的每個(gè)@Test方法執(zhí)行前執(zhí)行一次 |
| @AfterAll | @AfterClass | 被注解的靜態(tài)方法會(huì)在當(dāng)前類的所有@Test方法執(zhí)行后執(zhí)行一次 |
| @AfterEach | @After | 被注解的方法會(huì)在當(dāng)前類的每個(gè)@Test方法執(zhí)行后執(zhí)行一次 |
| @Disabled | @Ignore | 被注解的方法不會(huì)被執(zhí)行,但是在測(cè)試報(bào)告里會(huì)記錄為已執(zhí)行 |
測(cè)試命名
寫測(cè)試用例的時(shí)候,為了更好的可讀性,我們往往會(huì)給測(cè)試方法定義一個(gè)有意義的名字,eg.testXXXWhenXXXThenReturnXXX。JUnit 5還提供了一個(gè)@DisplayName 注解,方便我們?yōu)槊總€(gè)測(cè)試用例添加更具體的名字,更容易表述用例所要測(cè)試的內(nèi)容(可以是字符串,特殊符號(hào),甚至是表情符號(hào))。
public class DisplayNameTest {@Test@DisplayName("執(zhí)行aTest方法")void aTest() {assertEquals(4, (2 + 2));} }運(yùn)行結(jié)果 :
斷言
測(cè)試用例中斷言可以算是最重要的一部分了,沒有斷言的測(cè)試是不完整的。
常規(guī)斷言
junit 框架中最常用的斷言就是檢查一個(gè)對(duì)象或者屬性是否為null.或者判斷兩個(gè)屬性是否一致。JUnit 4和JUnit 5中的斷言方法都可以接受字符串作為一個(gè)可選參數(shù),如果斷言失敗,則會(huì)在控制臺(tái)輸出對(duì)應(yīng)的描述信息。JUnit 5中還可以使用 lambda 表達(dá)式 來構(gòu)建這個(gè)描述信息。
注意一下,在JUnit 4和JUnit 5中描述信息的參數(shù)位置是不一樣的。
@Test@DisplayName("assert all test")void assertTest() {String expected = "chenpp";String actual = "chen"+ "pp";String nullValue = null;assertEquals(expected, actual, "incorrect!");assertNull(nullValue);}擴(kuò)展斷言
assertAll
判斷一組斷言是否滿足,包含的所有斷言都會(huì)執(zhí)行,即使其中一個(gè)或多個(gè)斷言失敗
如下代碼中第一個(gè)和第四個(gè)斷言都是錯(cuò)誤的,在執(zhí)行的時(shí)候會(huì)把5個(gè)斷言都執(zhí)行一遍,而不像常規(guī)斷言一樣,遇到錯(cuò)誤的斷言就停止運(yùn)行
@Testvoid assertAllTestWhenOneIsError() {String expected = "Hi,chenpp";String actual = "Hi," + "chenpp";String nullValue = null;assertAll("Assert All of these",() -> assertEquals(expected, "", "incorrect!"),() -> assertFalse(nullValue != null),() -> assertNull(nullValue),() -> assertNull("not null", "incorrect!"),() -> assertTrue(nullValue == null));}
這樣我們?cè)陂_發(fā)單元測(cè)試的時(shí)候就可以一次把所有的錯(cuò)誤信息都打印出來,而不會(huì)在測(cè)試第一個(gè)斷言失敗時(shí)就結(jié)束執(zhí)行,這樣只能得到第一個(gè)出錯(cuò)地方的得到提示,也無法得知其他斷言是否成功,只能再跑一遍測(cè)試。
assertThrows和 expectThrows
都用于判斷是否拋出期望的異常類型,在JUnit 4 中是通過 expected = 方法參數(shù)或者 @Rule 提供此能力。JUnit 5的expectThrows還會(huì)返回拋出的異常實(shí)例,用于進(jìn)一步的驗(yàn)證
@Testpublic void exception() {assertThrows(ArithmeticException.class, () -> System.out.println(1 / 0));}Tag標(biāo)記
JUnit 5新增了@Tag注解,可以為測(cè)試類或方法添加標(biāo)簽,并在執(zhí)行時(shí)快速地根據(jù)標(biāo)簽來針對(duì)性地運(yùn)行測(cè)試。
使用maven運(yùn)行測(cè)試用例時(shí),使用如下參數(shù)指定tag名稱即可
-Dgroups=“tagName”
通過@Tag可以為測(cè)試類或方法添加標(biāo)簽,但是不同的標(biāo)簽只是通過字符串來進(jìn)行區(qū)分,并不是類型安全的。開發(fā)過程中如果拼寫錯(cuò)誤就可能導(dǎo)致標(biāo)簽沒有被正確應(yīng)用。更好的做法是使用類型安全的元注解(meta annotation),如下
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Tag("history") public @interface History{ }使用@History的作用等同于@Tag(“history”),這樣不僅提高了代碼的可讀性,也可以避免拼寫錯(cuò)誤可能帶來的問題。
擴(kuò)展機(jī)制
在JUnit 5出來之前,我們?nèi)绻雽?duì)JUnit 4 的核心功能進(jìn)行擴(kuò)展,往往都會(huì)使用自定義Runner 和 @Rule。
自定義Runner 通常是 BlockJUnit4ClassRunner 的子類,用于實(shí)現(xiàn) JUnit 中沒有直接提供的某種功能。 eg.spring-test框架的SpringJUnit4ClassRunner, 和mock框架的MockitoJUnitRunner
局限性:
JUnit 5擴(kuò)展機(jī)制的核心準(zhǔn)則:
Prefer extension points over features
基于這一準(zhǔn)則,JUnit 5 中定義了許多擴(kuò)展點(diǎn),每個(gè)擴(kuò)展點(diǎn)都對(duì)應(yīng)一個(gè)接口。我們可以定義自己的擴(kuò)展可以實(shí)現(xiàn)其中的某些接口,然后通過 @ExtendWith 注解注冊(cè)給 JUnit,后者會(huì)在特定的時(shí)間點(diǎn)調(diào)用注冊(cè)的接口實(shí)現(xiàn)。
JUnit 5定義的部分?jǐn)U展點(diǎn):
| BeforeAllCallback | 在所有測(cè)試方法執(zhí)行前定義測(cè)試容器執(zhí)行的行為,也就是在 @BeforeAll 注解的方法之前執(zhí)行 |
| AfterAllCallback | 在@AfterAll 注解的方法之后執(zhí)行 |
| BeforeEachCallback | 在@BeforeEach 注解的方法之前執(zhí)行 |
| AfterEachCallback | 在@AfterEach 注解的方法之后執(zhí)行 |
| BeforeTestExecutionCallback | 在測(cè)試方法運(yùn)行之前立刻執(zhí)行 |
| AfterTestExecutionCallback | 在測(cè)試方法運(yùn)行之后立刻執(zhí)行 |
| ParameterResolver | 用于在運(yùn)行時(shí)動(dòng)態(tài)解析參數(shù) |
Extension Context
Extension Context包含了測(cè)試方法的上下文信息,所有的擴(kuò)展接口都定義了一個(gè)包含該參數(shù)的接口方法,開發(fā)者可以根據(jù)Extension Context拿到跟測(cè)試方法相關(guān)的幾乎所有的信息,包括方法注解,測(cè)試實(shí)例,測(cè)試標(biāo)簽等。
Store
Extension Context 提供了一個(gè)getStore(Namespace namespace)的接口方法,返回了一個(gè)Store的對(duì)象,用于存儲(chǔ)一些數(shù)據(jù),方便不同的回調(diào)接口之間共享數(shù)據(jù)。
Namespace
如果你需要使用Extension Context里的Store功能,那么你就需要先申請(qǐng)一個(gè)Namespace,它可以避免不同的擴(kuò)展實(shí)現(xiàn)誤操作同一份數(shù)據(jù)
比方說,現(xiàn)在我們想實(shí)現(xiàn)一個(gè)功能–計(jì)算每個(gè)測(cè)試方法的執(zhí)行時(shí)間。為了方便使用,我們定義一個(gè)注解@CalTime,如果想計(jì)算測(cè)試方法的執(zhí)行時(shí)間,就在該方法上加上@CalTime注解即可
@Target({ TYPE, METHOD, ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(CalTestTimeExtension.class) public @interface CalTime { }根據(jù)JUnit 5的擴(kuò)展點(diǎn),我們的CalTestTimeExtension需要實(shí)現(xiàn)BeforeTestExecutionCallback和AfterTestExecutionCallback 兩個(gè)擴(kuò)展API
public class CalTestTimeExtensionimplements BeforeTestExecutionCallback, AfterTestExecutionCallback {private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create("CalTestTimeExtension");private static final String STORE_KEY = "calTime";@Overridepublic void beforeTestExecution(ExtensionContext context) {//判斷是否有添加對(duì)應(yīng)的注解if (!isSupport(context)) {return;}context.getStore(NAMESPACE).put(STORE_KEY, System.currentTimeMillis());}@Overridepublic void afterTestExecution(ExtensionContext context) {if (!isSupport(context))return;long beginTime = (long) context.getStore(NAMESPACE).get(STORE_KEY);long executeTime = System.currentTimeMillis() - beginTime;System.out.println("Test " + context.getDisplayName() +" execute cost " + executeTime + " ms");}private static boolean isSupport(ExtensionContext context) {return context.getElement().map(el -> el.isAnnotationPresent(CalTime.class)).orElse(false);} }參考資料:
「譯」JUnit 5 系列:基礎(chǔ)入門
Junit5 用戶指南
總結(jié)
以上是生活随笔為你收集整理的关于JUnit5 你必须知道的(二)JUnit 5的新特性的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于JUnit5 你必须知道的(一) J
- 下一篇: 面试官让我说出2种@Transactio