Junit5單元測(cè)試

一次舌、概念

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>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末膳灶,一起剝皮案震驚了整個(gè)濱河市咱士,隨后出現(xiàn)的幾起案子立由,更是在濱河造成了極大的恐慌,老刑警劉巖序厉,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锐膜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡弛房,警方通過(guò)查閱死者的電腦和手機(jī)道盏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)文捶,“玉大人荷逞,你說(shuō)我怎么就攤上這事〈馀牛” “怎么了种远?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)顽耳。 經(jīng)常有香客問(wèn)我坠敷,道長(zhǎng),這世上最難降的妖魔是什么射富? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任膝迎,我火速辦了婚禮,結(jié)果婚禮上胰耗,老公的妹妹穿的比我還像新娘限次。我一直安慰自己,他們只是感情好柴灯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布掂恕。 她就那樣靜靜地躺著,像睡著了一般弛槐。 火紅的嫁衣襯著肌膚如雪懊亡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天乎串,我揣著相機(jī)與錄音店枣,去河邊找鬼。 笑死叹誉,一個(gè)胖子當(dāng)著我的面吹牛鸯两,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播长豁,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钧唐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了匠襟?” 一聲冷哼從身側(cè)響起钝侠,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤该园,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后帅韧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體里初,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年忽舟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了双妨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叮阅,死狀恐怖刁品,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浩姥,我是刑警寧澤哑诊,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站及刻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏竞阐。R本人自食惡果不足惜缴饭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骆莹。 院中可真熱鬧颗搂,春花似錦、人聲如沸幕垦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)先改。三九已至疚察,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仇奶,已是汗流浹背貌嫡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留该溯,地道東北人岛抄。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像狈茉,于是被迫代替她去往敵國(guó)和親夫椭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容