一次舌、概念
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform: Junit Platform是在JVM上啟動(dòng)測(cè)試框架的基礎(chǔ)壹若,不僅支持Junit自制的測(cè)試引擎,其他測(cè)試引擎也都可以接入仅孩。
JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的編程模型库北,是JUnit5新特性的核心铣揉。內(nèi)部 包含了一個(gè)測(cè)試引擎窜骄,用于在Junit Platform上運(yùn)行。
JUnit Vintage: 由于JUint已經(jīng)發(fā)展多年敬拓,為了照顧老的項(xiàng)目邻薯,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的測(cè)試引擎。
注意:
SpringBoot 2.4 以上版本移除了默認(rèn)對(duì) Vintage 的依賴(lài)乘凸。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test****)
JUnit 5’s Vintage Engine Removed from **spring-boot-starter-test,如果需要繼續(xù)兼容junit4需要自行引入vintage**
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
二厕诡、引入依賴(lài)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
SpringBoot整合Junit以后。
- 編寫(xiě)測(cè)試方法:@Test標(biāo)注(注意需要使用junit5版本的注解)
- Junit類(lèi)具有Spring的功能营勤,@Autowired灵嫌、比如 @Transactional 標(biāo)注測(cè)試方法,測(cè)試完成后自動(dòng)回滾
三葛作、常用注解:
JUnit5的注解與JUnit4的注解有所變化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
- @Test :表示方法是測(cè)試方法寿羞。但是與JUnit4的@Test不同,他的職責(zé)非常單一不能聲明任何屬性赂蠢,拓展的測(cè)試將會(huì)由Jupiter提供額外測(cè)試
- @ParameterizedTest :表示方法是參數(shù)化測(cè)試绪穆,下方會(huì)有詳細(xì)介紹
- @RepeatedTest :表示方法可重復(fù)執(zhí)行,下方會(huì)有詳細(xì)介紹
- @DisplayName :為測(cè)試類(lèi)或者測(cè)試方法設(shè)置展示名稱(chēng)
- @BeforeEach :表示在每個(gè)單元測(cè)試之前執(zhí)行
- @AfterEach :表示在每個(gè)單元測(cè)試之后執(zhí)行
- @BeforeAll :表示在所有單元測(cè)試之前執(zhí)行 【注意添加static關(guān)鍵字】
- @AfterAll :表示在所有單元測(cè)試之后執(zhí)行 【注意添加static關(guān)鍵字】
- @Tag :表示單元測(cè)試類(lèi)別虱岂,類(lèi)似于JUnit4中的@Categories
- @Disabled :表示測(cè)試類(lèi)或測(cè)試方法不執(zhí)行玖院,類(lèi)似于JUnit4中的@Ignore
- @Timeout :表示測(cè)試方法運(yùn)行如果超過(guò)了指定時(shí)間將會(huì)返回錯(cuò)誤
- @ExtendWith :為測(cè)試類(lèi)或測(cè)試方法提供擴(kuò)展類(lèi)引用
- @RepeatTest:重復(fù)幾次該方法
補(bǔ)充:如果要使用Springboot中的自動(dòng)注入,則要引入Springboot中的@SpringBootTest注解(由以下兩個(gè)注解組成)
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith({SpringExtension.class})
四第岖、斷言(assertions)
斷言方法都是 org.junit.jupiter.api.Assertions 的靜態(tài)方法
【若前面的斷言失敗难菌,后面的代碼不會(huì)執(zhí)行】
1、簡(jiǎn)單斷言
用來(lái)對(duì)單個(gè)值進(jìn)行簡(jiǎn)單的驗(yàn)證蔑滓。如:
方法 | 說(shuō)明 |
---|---|
assertEquals | 判斷兩個(gè)對(duì)象或兩個(gè)原始類(lèi)型是否相等 |
assertNotEquals | 判斷兩個(gè)對(duì)象或兩個(gè)原始類(lèi)型是否不相等 |
assertSame | 判斷兩個(gè)對(duì)象引用是否指向同一個(gè)對(duì)象 |
assertNotSame | 判斷兩個(gè)對(duì)象引用是否指向不同的對(duì)象 |
assertTrue | 判斷給定的布爾值是否為 true |
assertFalse | 判斷給定的布爾值是否為 false |
assertNull | 判斷給定的對(duì)象引用是否為 null |
assertNotNull | 判斷給定的對(duì)象引用是否不為 null |
@Test
@DisplayName("simple assertion")
public void simple() {
assertEquals(3, 1 + 2, "simple math");
assertNotEquals(3, 1 + 1);
assertNotSame(new Object(), new Object());
Object obj = new Object();
assertSame(obj, obj);
assertFalse(1 > 2);
assertTrue(1 < 2);
assertNull(null);
assertNotNull(new Object());
}
2郊酒、數(shù)組斷言
通過(guò) assertArrayEquals 方法來(lái)判斷兩個(gè)對(duì)象或原始類(lèi)型的數(shù)組是否相等
@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
3遇绞、組合斷言
assertAll 方法接受多個(gè) org.junit.jupiter.api.Executable 函數(shù)式接口的實(shí)例作為要驗(yàn)證的斷言,可以通過(guò) lambda 表達(dá)式很容易的提供這些斷言
@Test
@DisplayName("assert all")
public void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
4燎窘、異常斷言
在JUnit4時(shí)期摹闽,想要測(cè)試方法的異常情況時(shí),需要用@Rule注解的ExpectedException變量還是比較麻煩的褐健。而JUnit5提供了一種新的斷言方式Assertions.assertThrows() ,配合函數(shù)式編程就可以進(jìn)行使用钩骇。
@Test
@DisplayName("異常測(cè)試")
public void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
//扔出斷言異常
ArithmeticException.class, () -> System.out.println(1 % 0));
}
5、超時(shí)斷言
Junit5還提供了Assertions.assertTimeout() 為測(cè)試方法設(shè)置了超時(shí)時(shí)間
@Test
@DisplayName("超時(shí)測(cè)試")
public void timeoutTest() {
//如果測(cè)試方法時(shí)間超過(guò)1s將會(huì)異常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
6铝量、快速失敗
通過(guò) fail 方法直接使得測(cè)試失敗
@Test
@DisplayName("fail")
public void shouldFail() {
fail("This should fail");
}
五、前置條件(assumptions)
類(lèi)似于斷言银亲,不同之處在于不滿(mǎn)足的斷言會(huì)使得測(cè)試方法失敗慢叨,而不滿(mǎn)足的前置條件只會(huì)使得測(cè)試方法的執(zhí)行終止(即當(dāng)前方法被跳過(guò),在當(dāng)前類(lèi)的測(cè)試報(bào)告中可以看到跳過(guò)的方法數(shù)量)务蝠。前置條件可以看成是測(cè)試方法執(zhí)行的前提拍谐,當(dāng)該前提不滿(mǎn)足時(shí),就沒(méi)有繼續(xù)執(zhí)行的必要馏段。
@Test
@DisplayName("前置條件測(cè)試")
void testAssumption(){
Assumptions.assumeTrue(true,"結(jié)果不是true");//若假設(shè)失敗轩拨,則直接跳過(guò)后面的代碼
System.out.println("執(zhí)行后面的代碼……");
}
六、嵌套測(cè)試(Nested)
JUnit 5 可以通過(guò) Java 中的內(nèi)部類(lèi)和@Nested 注解實(shí)現(xiàn)嵌套測(cè)試院喜,從而可以更好的把相關(guān)的測(cè)試方法組織在一起亡蓉。在內(nèi)部類(lèi)中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的層次沒(méi)有限制喷舀。
package com.laj.admin;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.util.EmptyStackException;
import java.util.Stack;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("嵌套測(cè)試")
public class AStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
//嵌套測(cè)試下砍濒,外層的Test不能驅(qū)動(dòng)內(nèi)層的Before(After)Each/All之類(lèi)的方法提前或之后運(yùn)行
assertNull(stack);//null
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);//彈出所有棧中元素
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);//彈出棧中 第一個(gè)元素
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
總結(jié):內(nèi)層Test可以驅(qū)動(dòng)外層Test,然而外層Test不可以驅(qū)動(dòng)內(nèi)層Test硫麻。
七爸邢、參數(shù)化測(cè)試(ParameterizedTest)
利用@ValueSource等注解,指定入?yún)⒛美ⅲ覀儗⒖梢允褂貌煌膮?shù)進(jìn)行多次單元測(cè)試杠河,而不需要每新增一個(gè)參數(shù)就新增一個(gè)單元測(cè)試,省去了很多冗余代碼浇辜。
@ValueSource: 為參數(shù)化測(cè)試指定入?yún)?lái)源券敌,支持八大基礎(chǔ)類(lèi)以及String類(lèi)型,Class類(lèi)型
@NullSource: 表示為參數(shù)化測(cè)試提供一個(gè)null的入?yún)?/p>
@EnumSource: 表示為參數(shù)化測(cè)試提供一個(gè)枚舉入?yún)?/p>
@CsvFileSource:表示讀取指定CSV文件內(nèi)容作為參數(shù)化測(cè)試入?yún)?/p>
@MethodSource:表示讀取指定方法的返回值作為參數(shù)化測(cè)試入?yún)?注意方法返回需要是一個(gè)流)
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.stream.Stream;
@DisplayName("參數(shù)化測(cè)試")
public class ParameterizedTest {
@org.junit.jupiter.params.ParameterizedTest
@ValueSource(ints = {1,2,3,4,5})
void test(int i){
System.out.println(i);
}
@org.junit.jupiter.params.ParameterizedTest
@MethodSource("stream_m")
void test2(String s){
System.out.println(s);
}
static Stream<String> stream_m(){
return Stream.of("123","abc");
}
}
補(bǔ)充:當(dāng)然如果參數(shù)化測(cè)試僅僅只能做到指定普通的入?yún)⑦€達(dá)不到讓我覺(jué)得驚艷的地步。讓我真正感到他的強(qiáng)大之處的地方在于他可以支持外部的各類(lèi)入?yún)⑸萋浮H?CSV,YML,JSON 文件甚至方法的返回值也可以作為入?yún)⑴惆住V恍枰?shí)現(xiàn)ArgumentsProvider接口,任何外部文件都可以作為它的入?yún)ⅰ?/p>