五年了,你还在用junit4吗?
junit5
JUnit5在2017年就發(fā)布了,你還在用junit4嗎?
什么是junit5
與以前的JUnit版本不同,JUnit 5由三個(gè)不同子項(xiàng)目的多個(gè)不同模塊組成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform為在JVM上啟動(dòng)測試框架提供基礎(chǔ)。它還定義了TestEngine API, 用來開發(fā)在平臺(tái)上運(yùn)行的測試框架。此外,平臺(tái)提供了一個(gè)控制臺(tái)啟動(dòng)器],用于從命令行啟動(dòng)平臺(tái),并為Gradle和Maven提供構(gòu)建插件以[基于JUnit 4的Runner,用于在平臺(tái)上運(yùn)行任意TestEngine。
JUnit Jupiter是在JUnit 5中編寫測試和擴(kuò)展的新型編程模型和[擴(kuò)展模型][]的組合.Jupiter子項(xiàng)目提供了TestEngine,用于在平臺(tái)上運(yùn)行基于Jupiter的測試。
JUnit Vintage提供TestEngine,用于在平臺(tái)上運(yùn)行基于JUnit 3和JUnit 4的測試。
為什么需要 JUnit 5
自從有了類似 JUnit 之類的測試框架,Java 單元測試領(lǐng)域逐漸成熟,開發(fā)人員對(duì)單元測試框架也有了更高的要求:更多的測試方式,更少的其他庫的依賴。
因此,大家期待著一個(gè)更強(qiáng)大的測試框架誕生,JUnit 作為Java測試領(lǐng)域的領(lǐng)頭羊,推出了 JUnit 5 這個(gè)版本,主要特性:
提供全新的斷言和測試注解,支持測試類內(nèi)嵌
更豐富的測試方式:支持動(dòng)態(tài)測試,重復(fù)測試,參數(shù)化測試等
實(shí)現(xiàn)了模塊化,讓測試執(zhí)行和測試發(fā)現(xiàn)等不同模塊解耦,減少依賴
提供對(duì) Java 8 的支持,如 Lambda 表達(dá)式,Sream API等。
基本注解
@Test:?表示方法是測試方法。但是與JUnit4的@Test不同,他的職責(zé)非常單一不能聲明任何屬性,拓展的測試將會(huì)由Jupiter提供額外測試
@ParameterizedTest: 表示方法是參數(shù)化測試
@RepeatedTest: 表示方法可重復(fù)執(zhí)行
@DisplayName: 為測試類或者測試方法設(shè)置展示名稱
@BeforeEach: 表示在每個(gè)單元測試之前執(zhí)行
@AfterEach: 表示在每個(gè)單元測試之后執(zhí)行
@BeforeAll: 表示在所有單元測試之前執(zhí)行
@AfterAll: 表示在所有單元測試之后執(zhí)行
@Tag: 表示單元測試類別,類似于JUnit4中的@Categories
@Disabled: 表示測試類或測試方法不執(zhí)行,類似于JUnit4中的@Ignore
@Timeout: 表示測試方法運(yùn)行如果超過了指定時(shí)間將會(huì)返回錯(cuò)誤
@ExtendWith: 為測試類或測試方法提供擴(kuò)展類引用
常用注解格式:
class?StandardTests?{//與junit4的@beforeClass類似,每個(gè)測試類運(yùn)行一次@BeforeAllstatic?void?initAll()?{}//與junit4中@before類似,每個(gè)測試用例都運(yùn)行一次@BeforeEachvoid?init()?{}@Test@DisplayName("成功測試")void?succeedingTest()?{}@Test@DisplayName("失敗測試")void?failingTest()?{fail("a?failing?test");}//禁用測試用例@Test@Disabled("for?demonstration?purposes")void?skippedTest()?{//?not?executed}@Testvoid?abortedTest()?{assumeTrue("abc".contains("Z"));fail("test?should?have?been?aborted");}//與@BeforeEach對(duì)應(yīng),每個(gè)測試類執(zhí)行一次,一般用于恢復(fù)環(huán)境@AfterEachvoid?tearDown()?{}//與@BeforeAll對(duì)應(yīng),每個(gè)測試類執(zhí)行一次,一般用于恢復(fù)環(huán)境@AfterAllstatic?void?tearDownAll()?{} }?
新特性
顯示名稱
@DisplayName("顯示名稱測試") class?DisplayNameDemo?{@Test@DisplayName("我的?第一個(gè)?測試?用例")void?testWithDisplayNameContainingSpaces()?{}@Test@DisplayName("╯°□°)╯")void?testWithDisplayNameContainingSpecialCharacters()?{}@Test@DisplayName("????")void?testWithDisplayNameContainingEmoji()?{} }IDE運(yùn)行測試結(jié)果顯示:
image-20210416232329161**優(yōu)點(diǎn):**通過這種方式,可以在方法名是英文特別長或者很難用英文描述清楚的場景下,增加中文解釋
更強(qiáng)大的斷言
JUnit Jupiter提供了許多JUnit4已有的斷言方法,并增加了一些適合與Java 8 lambda一起使用的斷言方法。所有JUnit Jupiter斷言都是[org.junit.jupiter.Assertions]類中的靜態(tài)方法。
分組斷言:
多個(gè)條件同時(shí)滿足時(shí)才斷言成功
@Test void?groupedAssertions()?{Person?person?=?new?Person();Assertions.assertAll("person",()?->?assertEquals("niu",?person.getName()),()?->?assertEquals(18,?person.getAge())); }異常斷言:
Junit4時(shí)需要使用rule方式,junit5提供了assertThrows更優(yōu)雅的異常斷言
@Test void?exceptionTesting()?{Throwable?exception?=?assertThrows(IllegalArgumentException.class,?()?->?{throw?new?IllegalArgumentException("a?message");});assertEquals("a?message",?exception.getMessage()); }超時(shí)斷言:
@Test @DisplayName("超時(shí)測試") public?void?timeoutTest()?{Assertions.assertTimeout(Duration.ofMillis(100),?()?->?Thread.sleep(50)); }標(biāo)簽和過濾
通過標(biāo)簽把測試分組,在不同階段執(zhí)行不同的邏輯測試,比如劃分為快速冒煙測試和執(zhí)行慢但也重要的測試
@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>嵌套測試
當(dāng)我們編寫的類和代碼逐漸增多,隨之而來的需要測試的對(duì)應(yīng)測試類也會(huì)越來越多。
為了解決測試類數(shù)量爆炸的問題,JUnit 5提供了@Nested 注解,能夠以靜態(tài)內(nèi)部成員類的形式對(duì)測試用例類進(jìn)行邏輯分組。
并且每個(gè)靜態(tài)內(nèi)部類都可以有自己的生命周期方法, 這些方法將按從外到內(nèi)層次順序執(zhí)行。
此外,嵌套的類也可以用@DisplayName 標(biāo)記,這樣我們就可以使用正確的測試名稱。下面看下簡單的用法:
@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?{@BeforeEachvoid?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";@BeforeEachvoid?pushAnElement()?{stack.push(anElement);}@Test@DisplayName("it?is?no?longer?empty")void?isNotEmpty()?{assertFalse(stack.isEmpty());}}} }junit沒有限制嵌套層數(shù),除非必要一般不建議使用超過3層,過于復(fù)雜的層次結(jié)構(gòu)會(huì)增加開發(fā)者理解用例關(guān)系的難度
構(gòu)造函數(shù)和方法的依賴注入
在之前的所有JUnit版本中,測試構(gòu)造函數(shù)或方法都不允許有參數(shù)(至少不能使用標(biāo)準(zhǔn)的Runner實(shí)現(xiàn))。作為JUnit Jupiter的主要變化之一,測試構(gòu)造函數(shù)和方法現(xiàn)在都允許有參數(shù)。這帶來了更大的靈活性,并為構(gòu)造函數(shù)和方法啟用依賴注入
TestInfo可獲取測試信息
TestReporter可以向控制臺(tái)輸出信息
重復(fù)測試
多次調(diào)用同一個(gè)測試用例
@RepeatedTest(10) @DisplayName("重復(fù)測試") public?void?testRepeated()?{//... } image-20210416232512919動(dòng)態(tài)測試
動(dòng)態(tài)測試只需要編寫一處代碼,就能一次性對(duì)各種類型的輸入和輸出結(jié)果進(jìn)行驗(yàn)證
@TestFactory @DisplayName("動(dòng)態(tài)測試") Stream<DynamicTest>?dynamicTests()?{List<Person>?persons?=?getAllPerson();return?persons.stream().map(person?->?DynamicTest.dynamicTest(person.getName()?+?"-test",?()?->?assertTrue(person.getName().contains("niu")))); }超時(shí)測試
通過時(shí)間來驗(yàn)證用例是否超時(shí),一般要求單個(gè)單元測試不應(yīng)該超過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));} }參數(shù)測試
參數(shù)測試我覺得是最好用的特性,可以大量減少重復(fù)模板式代碼,也是junit5最驚艷的提升,強(qiáng)烈推薦使用
@ValueSource: 為參數(shù)化測試指定入?yún)碓?#xff0c;支持八大基礎(chǔ)類以及String類型,Class類型
@NullSource: 表示為參數(shù)化測試提供一個(gè)null的入?yún)?/p>
@EnumSource: 表示為參數(shù)化測試提供一個(gè)枚舉入?yún)?/p>
@CsvSource:表示讀取CSV格式內(nèi)容作為參數(shù)化測試入?yún)?/p>
@CsvFileSource:表示讀取指定CSV文件內(nèi)容作為參數(shù)化測試入?yún)?/p>
@MethodSource:表示讀取指定方法的返回值作為參數(shù)化測試入?yún)?注意方法返回需要是一個(gè)流)
@ArgumentsSource:指定一個(gè)自定義的,可重用的ArgumentsProvider。
看完用法描述,簡直太喜歡了
一個(gè)頂三個(gè)基礎(chǔ)測試用例
@ParameterizedTest @ValueSource(strings?=?{"one",?"two",?"three"}) @DisplayName("參數(shù)化測試1") public?void?parameterizedTest1(String?string)?{assertTrue(StringUtils.isNotBlank(string)); } image-20210416233807174如果不是基礎(chǔ)的類型,可以使用方法構(gòu)造,只要返回值為Stream類型就可以,多個(gè)參數(shù)使用Arguments實(shí)例流
@ParameterizedTest @MethodSource("method") @DisplayName("方法來源參數(shù)") public?void?testWithExplicitLocalMethodSource(String?name)?{Assertions.assertNotNull(name); }private?static?Stream<String>?method()?{return?Stream.of("apple",?"banana"); }@CsvSource允許您將參數(shù)列表表示為以逗號(hào)分隔的值(例如,字符串文字)
@ParameterizedTest @CsvSource({"steven,18",?"jack,24"}) @DisplayName("參數(shù)化測試-csv格式") public?void?parameterizedTest3(String?name,?Integer?age)?{System.out.println("name:"?+?name?+?",age:"?+?age);Assertions.assertNotNull(name);Assertions.assertTrue(age?>?0); } image-20210416232702304@CsvFileSource使用classpath中的CSV文件,CSV文件中的每一行都會(huì)導(dǎo)致參數(shù)化測試的一次調(diào)用
這種就完全把測試數(shù)據(jù)與測試方法隔離,達(dá)到更好解耦效果
@ParameterizedTest @CsvFileSource(resources?=?"/persons.csv")??//指定csv文件位置 @DisplayName("參數(shù)化測試-csv文件") public?void?parameterizedTest2(String?name,?Integer?age)?{System.out.println("name:"?+?name?+?",age:"?+?age);Assertions.assertNotNull(name);Assertions.assertTrue(age?>?0); }其他方式不在贅述,如果還是滿足不了需求,可以通過@ArgumentsSource自定義自己的數(shù)據(jù)來源,必須封裝成去取JSON或者XMl等數(shù)據(jù)
AssertJ
當(dāng)定義好需要運(yùn)行的測試方法后,下一步則是需要關(guān)注測試方法的細(xì)節(jié),這就離不開斷言和假設(shè)
斷言:封裝好了常用判斷邏輯,當(dāng)不滿足條件時(shí),該測試用例會(huì)被認(rèn)為測試失敗
假設(shè):與斷言類似,當(dāng)條件不滿足時(shí),測試會(huì)直接退出而不是判定為失敗
因?yàn)椴粫?huì)影響到后續(xù)的測試用例,最常用的還是斷言
除了Junit5自帶的斷言,AssertJ是非常好用的一個(gè)斷言工具,最大特點(diǎn)是提供了流式斷言,與Java8使用方法非常類似
@Test void?testString()?{//?斷言null或?yàn)榭兆址產(chǎn)ssertThat("").isNullOrEmpty();//?斷言空字符串a(chǎn)ssertThat("").isEmpty();//?斷言字符串相等?斷言忽略大小寫判斷字符串相等assertThat("niu").isEqualTo("niu").isEqualToIgnoringCase("NIu");//?斷言開始字符串?結(jié)束字符穿?字符串長度assertThat("niu").startsWith("ni").endsWith("u").hasSize(3);//?斷言包含字符串?不包含字符串a(chǎn)ssertThat("niu").contains("iu").doesNotContain("love");//?斷言字符串只出現(xiàn)過一次assertThat("niu").containsOnlyOnce("iu"); }@Test void?testNumber()?{//?斷言相等assertThat(42).isEqualTo(42);//?斷言大于?大于等于assertThat(42).isGreaterThan(38).isGreaterThanOrEqualTo(38);//?斷言小于?小于等于assertThat(42).isLessThan(58).isLessThanOrEqualTo(58);//?斷言0assertThat(0).isZero();//?斷言正數(shù)?非負(fù)數(shù)assertThat(1).isPositive().isNotNegative();//?斷言負(fù)數(shù)?非正數(shù)assertThat(-1).isNegative().isNotPositive(); }@Test void?testCollection()?{//?斷言?列表是空的assertThat(newArrayList()).isEmpty();//?斷言?列表的開始?結(jié)束元素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?不為空?sizeassertThat(foo).isNotEmpty().hasSize(3);//?斷言?map?包含元素assertThat(foo).contains(entry("A",?1),?entry("B",?2));//?斷言?map?包含keyassertThat(foo).containsKeys("A",?"B",?"C");//?斷言?map?包含valueassertThat(foo).containsValue(3); } //?其他斷言,請(qǐng)自行探索......想想如果沒有使用AssertJ時(shí)我們是如何寫斷言的,是不是需要多個(gè)assert,很繁瑣
AssertJ的斷言代碼清爽很多,流式斷言充分利用了java8之后的匿名方法和stream類型的特點(diǎn),很好的對(duì)Junit斷言方法做了補(bǔ)充。
參考
https://junit.org/junit5/docs/current/user-guide/#overview
https://assertj.github.io/doc/
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的五年了,你还在用junit4吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OS之内存管理 ---基本的内存管理策略
- 下一篇: 枚举方式的线程安全的单例