著名的Java單元測(cè)試框架Junit 4已經(jīng)出來(lái)很長(zhǎng)時(shí)間了,當(dāng)時(shí)我發(fā)現(xiàn)JUnit 5已經(jīng)處于測(cè)試版,就準(zhǔn)備寫文章來(lái)介紹JUnit 5.不過(guò)因?yàn)檫€是測(cè)試版,所以有些地方還不太完善,我也有點(diǎn)懶沒(méi)有好好寫。這幾天突然想起這事了,在到官網(wǎng)上查看,發(fā)現(xiàn)就在9月10日,JUnit 5的正式版終于出來(lái)了!那么我就正好把文章重新好好寫寫,為大家介紹這個(gè)最新的JUnit框架。
10年積累的成都網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問(wèn)題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有三元免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
框架結(jié)構(gòu)
和JUnit 4相比,JUnit 5的結(jié)構(gòu)非常清晰,為自定義插件、IDE測(cè)試執(zhí)行等擴(kuò)展功能做了很好的支持。這一點(diǎn)從項(xiàng)目結(jié)構(gòu)就可以看出來(lái)。
JUnit Platform
這一組的包名是org.junit.platform,從名字就可以看到,這一組的主要功能就是作為測(cè)試框架的基礎(chǔ)平臺(tái)。這個(gè)包下的模塊包含基礎(chǔ)API、執(zhí)行引擎及執(zhí)行器、基本的命令行執(zhí)行功能、命令行界面、Maven及Gradle的測(cè)試插件等最基本的功能。
JUnit Jupiter
Jupiter 是JUnit 5的代號(hào),這個(gè)包下的模塊包含JUnit 5的主要功能。如果我們要使用JUnit 5,那么必然要包含這一組模塊。
JUnit Vintage
Vintage 是舊版本JUnit 的代號(hào),這個(gè)包下的模塊可以讓我們?cè)谛碌腏Unit平臺(tái)上運(yùn)行舊的JUnit 3 和 4 的測(cè)試。
導(dǎo)入類庫(kù)
在JUnit 5還在測(cè)試階段的時(shí)候,官方文檔上還有在Maven和Gradle中集成JUnit 5的例子。但是到了正式版,這一部分的內(nèi)容消失了,僅僅留下兩個(gè)示例項(xiàng)目的鏈接,讓我們自己參考(復(fù)制粘貼)。
使用Maven
junit5-maven-consumer 是官方的Maven例子。本來(lái)我準(zhǔn)備把相關(guān)的POM配置貼到這里,但是一看Maven的配置太長(zhǎng)了,所以還是算了。如果有需求的話請(qǐng)自己查看這個(gè)項(xiàng)目的POM配置。
使用Gradle
如果用Gradle的話,那么這個(gè)問(wèn)題就簡(jiǎn)單多了。在junit5-gradle-consumer 示例項(xiàng)目中也有比較詳細(xì)的說(shuō)明。
首先,Gradle默認(rèn)不支持JUnit 5,,所以需要啟用JUnit Platform Gradle 插件來(lái)支持。
buildscript { repositories { mavenCentral() } dependencies { classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0' } } apply plugin: 'org.junit.platform.gradle.plugin'
然后是關(guān)于這個(gè)Gradle插件的配置。默認(rèn)情況下所有的引擎和標(biāo)簽都會(huì)被執(zhí)行。如果你想選擇只執(zhí)行某些引擎和標(biāo)簽的測(cè)試,可以取消下面的注釋并按照你自己的需求進(jìn)行修改。當(dāng)然假如你沒(méi)有這些高級(jí)需求,可以把這一部分刪掉。
junitPlatform { // platformVersion '1.0.0' filters { engines { // include 'junit-jupiter', 'junit-vintage' // exclude 'custom-engine' } tags { // include 'fast' exclude 'slow' } // includeClassNamePattern '.*Test' } // enableStandardTestTask true // reportsDir file('build/test-results/junit-platform') // this is the default // logManager 'org.apache.logging.log4j.jul.LogManager' }
如果你只需要運(yùn)行JUnit 5測(cè)試,只需要導(dǎo)入下面兩個(gè)依賴項(xiàng)。JUnit Platform的依賴會(huì)自動(dòng)導(dǎo)入。
dependencies { testCompile("org.junit.jupiter:junit-jupiter-api:5.0.0") testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0") }
如果你想在新平臺(tái)下運(yùn)行舊的JUnit 3和4測(cè)試,需要導(dǎo)入下面的依賴項(xiàng)。
dependencies { testCompile("junit:junit:4.12") testRuntime("org.junit.vintage:junit-vintage-engine:4.12.0") }
編寫測(cè)試
JUnit 4測(cè)試
如果前面都配置好了,現(xiàn)在就可以開始編寫測(cè)試了。首先先來(lái)復(fù)習(xí)一下舊的JUnit 4測(cè)試。
public class JUnit4Test { @BeforeClass public static void init() { System.out.println("Before Class"); } @AfterClass public static void clean() { System.out.println("After class"); } @Before public void before() { System.out.println("Before"); } @After public void after() { System.out.println("After"); } @Test public void test1() { System.out.println("Test 1"); } @Test public void test2() { System.out.println("Test 2"); } }
使用gradle test等命令執(zhí)行一下,就會(huì)執(zhí)行這個(gè)測(cè)試。結(jié)果類似于這樣。
Before Class Before Test 1 Test 2 After After class
JUnit 5測(cè)試
讓我們來(lái)看看等效的JUnit 5測(cè)試怎么寫??梢钥吹阶蠲黠@的變化:首先幾個(gè)注解被重新命名成更見名知義的名稱;另外一點(diǎn)是測(cè)試方法不必是公有方法,這樣我們可以少敲幾下鍵盤。
public class JUnit5Test { @BeforeAll static void beforeAll() { System.out.println("Before All"); } @AfterAll static void afterAll() { System.out.println("After All"); } @BeforeEach void before() { System.out.println("Before"); } @AfterEach void after() { System.out.println("After"); } @Test void test1() { System.out.println("Test 1"); } @Test void test2() { System.out.println("Test 2"); } }
編寫斷言
為了驗(yàn)證測(cè)試用例是否正確,我們需要編寫一些斷言。JUnit 5自帶了很多斷言,可以幫助我們編寫測(cè)試用例。而且這些斷言都帶有可以接受lambda表達(dá)式的重載版本,非常適合Java 8使用。當(dāng)然我個(gè)人認(rèn)為斷言還是AssertJ更方便一點(diǎn)。
import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.*; public class AssertionDemo { @Test void testAssertion() { assertEquals(10, 10); assertTrue(true); assertEquals(100, 100, "兩個(gè)數(shù)相等"); assertAll("數(shù)字" , () -> assertEquals("name", "name") , () -> assertEquals(500, 500)); assertThrows(InvalidParameterException.class , () -> { throw new InvalidParameterException(); } ); int result = assertTimeout(Duration.ofSeconds(5) , () -> { int i = 0, j = 0; while (i <= 100) { for (; j <= 100000; j++) j++; i++; } return i; }); assertEquals(100, result); } }
依賴注入
現(xiàn)在測(cè)試類的構(gòu)造方法和測(cè)試方法都可以接受參數(shù)了。ParameterResolver接口定義了如何在運(yùn)行時(shí)注入?yún)?shù)的方法。內(nèi)置的幾個(gè)可以讓我們獲取測(cè)試用例運(yùn)行時(shí)的信息。
首先是TestInfoParameterResolver。如果方法上有TestInfo類型的實(shí)例,JUnit 5框架就會(huì)自動(dòng)注入該實(shí)例,這個(gè)實(shí)例的幾個(gè)方法可以讓我們獲取測(cè)試類和測(cè)試方法的名稱、顯示名稱、標(biāo)簽等信息。
public class DependencyInjectionDemo { @Test @DisplayName("依賴注入") @Tag("test") void testDisplayName(TestInfo testInfo) { assertEquals("依賴注入", testInfo.getDisplayName()); assertEquals(Collections.singleton("test"), testInfo.getTags()); } }
還有RepetitionInfoParameterResolver等內(nèi)置參數(shù)解析器,將在后面介紹。
常用注解
顯示名稱
我們可以為測(cè)試類和測(cè)試方法添加自定義的名稱,這些名貴會(huì)由測(cè)試運(yùn)行器和測(cè)試報(bào)告所顯示。顯示名稱沒(méi)有變量名那樣的顯示,可以是一段包含空格的長(zhǎng)字符串,甚至還可以是Emoji表情。
@DisplayName("測(cè)試類可以指定顯示名稱") public class DisplayNameDemo { @Test @DisplayName("測(cè)試方法也可以指定顯示名稱") void testWithLongDisplayName() { } @Test @DisplayName("顯示名稱還可以包含表情��") void testWithDisplayNameWithEmoji() { } }
禁用測(cè)試
@Disabled注解可以用到測(cè)試類或測(cè)試方法上,可以禁用對(duì)應(yīng)的測(cè)試。
@Disabled public class DisabledTestDemo { @Test //@Disabled void testDisabled() { } }
重復(fù)測(cè)試
如果需要讓某個(gè)測(cè)試方法運(yùn)行多次,使用@RepeatedTest注解。
public class RepeatedTestDemo { @RepeatedTest(10) void testRepeated10Times() { } }
還可以注入一個(gè)實(shí)例RepetitionInfo,檢查當(dāng)前重復(fù)次數(shù)和總的重復(fù)次數(shù)。
public class RepeatedTestDemo { @BeforeEach void beforeEach(RepetitionInfo info) { System.out.printf("%d - %d\n" , info.getCurrentRepetition() , info.getTotalRepetitions()); } @RepeatedTest(10) void testRepeated10Times() { } }
附帶標(biāo)簽
在前面介紹配置Gradle的時(shí)候就說(shuō)了,在配置中可以選擇過(guò)濾某些標(biāo)簽的測(cè)試。要在代碼中給標(biāo)簽也很簡(jiǎn)單,直接用@Tag注解即可。
@Tag("taggedTest") public class TagDemo { @Test @Tag("taggedTest1") void testWithTag1() { } @Test @Tag("taggedTest2") void testWithTag2() { } }
嵌套測(cè)試
有時(shí)候可能需要嵌套測(cè)試來(lái)表明某些測(cè)試之間的包含關(guān)系。嵌套測(cè)試使用@Nested注解。
@DisplayName("外層測(cè)試") public class NestedDemo { @Test void testOuter() { } @Nested @DisplayName("內(nèi)層測(cè)試") class InnerTestDemo { @Test void testInner() { } } }
需要注意只有費(fèi)靜態(tài)內(nèi)部類才能使用Nested注解。另外,由于Java不允許內(nèi)部類有靜態(tài)方法,所以也不能有@BeforeAll和@AfterAll注解。如果想要突破這個(gè)限制,需要在嵌套內(nèi)部類上添加@TestInstance(Lifecycle.PER_CLASS)注解,詳情參見Test Instance Lifecycle。
IDE支持
雖然現(xiàn)在JUnit 5已經(jīng)出來(lái)了。但是各種工具鏈的支持還沒(méi)有跟上。目前只有Intellij IDEA和Eclipse 4.7 (Oxygen)添加了對(duì)JUnit 5的支持。所以如果在正式場(chǎng)合的話,使用JUnit 4還是更穩(wěn)妥一點(diǎn)。
常見問(wèn)題
區(qū)分不同版本間的@Test注解
就在我寫這篇文章的時(shí)候, 我的測(cè)試小例子就遇到了一個(gè)問(wèn)題,測(cè)試通不過(guò),顯示如下的錯(cuò)誤信息。
Failures (1): JUnit Vintage:yitian.study.test.AssertionDemo:initializationError ClassSource [className = 'yitian.study.test.AssertionDemo', filePosition = null] => java.lang.Exception: Method testAssertion() should be public
英文好的同學(xué)應(yīng)該可以認(rèn)出來(lái),這個(gè)錯(cuò)誤信息說(shuō)的是測(cè)試方法必須是公開的。但是前面明明說(shuō)了,JUnit 5取消了這個(gè)限制,那么為什么還會(huì)出現(xiàn)這個(gè)錯(cuò)誤呢?我仔細(xì)一看,發(fā)現(xiàn)了錯(cuò)誤所在??赡苁怯捎谝郧癑Unit 4用的比較多,所以IDE默認(rèn)對(duì)于@Test這個(gè)注解,自動(dòng)補(bǔ)全的是這個(gè)。
import org.junit.Test;
這個(gè)包是JUnit 4下的@Test注解。如果我們要使用JUnit 5的話,需要的是以下這個(gè)@Test注解。
import org.junit.jupiter.api.Test;
修改之后,再次運(yùn)行測(cè)試,果然沒(méi)有問(wèn)題了。當(dāng)然這里為了學(xué)習(xí)和使用,我同時(shí)引用了JUnit 4的包,所以才會(huì)出現(xiàn)這個(gè)沖突。如果沒(méi)有什么特殊需求的話,建議只導(dǎo)入JUnit 5的jar包,防止出現(xiàn)混淆。當(dāng)然都導(dǎo)入也可以,只不過(guò)你就需要小心區(qū)分,不要把JUnit 4的注解寫到JUnit 5的測(cè)試上。最后附上我的測(cè)試小例子,有興趣的同學(xué)可以看看。
總結(jié)
以上就是本文關(guān)于JUnit5相關(guān)內(nèi)容簡(jiǎn)介的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!