sponsored links

阿里P9眼中的Junit5手冊如何用?

阿里P9眼中的Junit5手冊如何用?

開始看內容之前,咱先說點有趣的事,就是小黑晚上剛剛寫完這篇文章,這不是不知道該起什麼標題嘛,於是請教了一下掘友兄弟們,接著就出現了下面這一幕

阿里P9眼中的Junit5手冊如何用?

我直接麻了呀掘友們真的是個人是人才,說話又好聽呀!!嗯,都有責任,雪崩的時候,每一片雪花都在勇闖天涯。

什麼是Junit5

Junit是Java語言中的一個流行測試框架,是由Kent Beck和Erich Gamma開發的。它的第一個版本於1997年釋出。由於其易用性,它成為Java社群中最流行的測試框架之一。它是一個輕量級測試框架,允許Java開發人員用Java語言編寫單元測試用例。最新發布的版本是5.8.2,被稱為JUnit5。

JUnit 5由許多不同的模組組成。主要包括以下三個子模組:

  • Junit Platform
  • Junit Jupiter
  • Junit Vintage

以上三個模組構成了Junit5的核心功能。

Junit 5的架構

阿里P9眼中的Junit5手冊如何用?

Junit5包含以下三部分核心元件:

Junit Platform

該模組提供了在JVM上啟動測試框架的核心基礎功能,充當JUnit與其客戶端(如構建工具[Maven、Gradle]和IDE[Eclipse、IntelliJ])之間的介面。它引入了Launcher(啟動器)的概念,外部工具可以使用它來發現、過濾和執行測試用例。

它還提供了TestEngine API,用於開發在JUnit上執行的測試框架;使用TestEngine API,第三方測試庫(如Spock、Cucumber和FitNesse)可以直接j整合它們的自定義TestEngine。

Junit Jupiter

該模組為在Junit 5中編寫測試和擴充套件提供了一個新的程式設計模型和擴充套件模型。

它有一套全新的註解來編寫Junit5中的測試用例,其中包括@BeforeEach、@AfterEach、@AfterAll、@BeforeAll等。可以理解為是對Junit Platform的TestEngine API的實現,以便Junit5測試可以執行。

Junit Vintage

阿里P9眼中的Junit5手冊如何用?

Vintage從字面意思理解是“古老的,經典的”。

該模組就是為了對Junit4和JUnit3編寫的測試用例提供支援。因此,Junit5具備向後相容能力。

快速入門

在我們開始編寫Junit5測試之前,需要先具備以下條件。

Java 8+

Junit 5要求JDK版本最低是Java 8,所以我們需要先安裝Java 8或更高版本的JDK。

IDE

我們肯定不能直接在記事本里編寫程式碼,所以需要使用順手的IDE,我因為習慣於使用IntelliJ IDEA,所以接下來的示例都是在IDEA中進行。

如果你更喜歡使用Eclipse,可以使用Eclipse Oxygen版本或者更高階的版本。

因為在Junit5的學習過程中不會用到特別多的Jar包依賴,所以這裡先不使用Maven或Gradle構建工具。

接下來我們開始在IDEA中編寫測試程式碼。

首先,在IDE中建立一個Java專案,JDK選擇1.8版本。

阿里P9眼中的Junit5手冊如何用?

然後一直點選Next,給我們的工程起一個名字,就叫Junit5吧。

阿里P9眼中的Junit5手冊如何用?

接下來,在我們的專案中建一個test資料夾,並新建一個測試類FirstJunit5Test.java

阿里P9眼中的Junit5手冊如何用?

現在就可以編寫第一個測試用例啦。

package test;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.fail;

/**
 * @author 小黑說Java
 * @ClassName FirstJunit5Test
 * @Description
 * @date 2022/1/6
 **/
public class FirstJunit5Test {

    @Test
    public void test() {
        fail("還沒有實現的測試用例");
    }
}
複製程式碼

正常情況下,你在寫這段程式碼時,會編譯失敗,因為我們的工程中現在還沒有新增Junit5的Jar包。

阿里P9眼中的Junit5手冊如何用?

透過程式碼提示,將Junit5的Jar包新增到classpath就可以了。

在我們上面的程式碼中,有一個test()方法,該方法上有一個@Test註解,表示這是一個測試方法,我們在這個方法中編寫程式碼進行測試。

最後直接右鍵執行該測試方法。測試用例失敗,如下面的圖所示。它給出“AssertionFailedError:還沒有實現的測試用例”。

阿里P9眼中的Junit5手冊如何用?

這是因為在test()方法中,是使用fail("還沒有實現的測試用例")斷言。該斷言未透過測試用例。

@Test註解

該註解是我們在編寫測試用例時最常使用的一個註解。

接下來,我們先定義一個具有功能的類,然後透過測試用例來對功能進行不同場景的測試。

package test;

/**
 * @author 小黑說Java
 * @ClassName OddEven
 **/
public class OddEven {
    /**
     * 判斷一個數是否為偶數
     */
    public boolean isNumberEven(int number) {
        return number % 2 == 0;
    }

}
複製程式碼

很簡單的一個功能,在OddEven類中有一個isEvenNumber()方法,用來判斷一個數字是奇數還是偶數。如果為偶數返回true,反之返回false。

接下來我們編寫測試程式碼。 為了測試isEvenNumber()方法,我們需要編寫覆蓋其功能的測試用例。

  • 傳入偶數,它應該返回true;
  • 傳入奇數,應該返回false。

為了將測試類中建立的方法識別為測試方法,我們需要使用@Test註釋對其進行標記。讓我們來看看測試類和測試方法:

package test;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * @author 小黑說Java
 * @ClassName OddEvenTest
 * @Description
 * @date 2022/1/6
 **/
public class OddEvenTest {
    @Test
    void evenNumberTrue() {
        OddEven oddEven = new OddEven();
        assertTrue(oddEven.isNumberEven(10));
    }

    @Test
    void oddNumberFale() {
        OddEven oddEven = new OddEven();
        assertFalse(oddEven.isNumberEven(11));
    }
}
複製程式碼

在以上程式碼中,我們使用到了Junit5的斷言assertTrue()和assertFasle();

assertTrue()方法接受布林值並確保該值為true。如果傳遞的是false,則測試用例將失敗。

assertFalse()方法接受布林值並確保該值為false。如果傳遞的值為true,則測試用例將失敗。

執行上面的測試用例,會得到如下結果,表示測試用例透過。

阿里P9眼中的Junit5手冊如何用?

什麼是斷言?

阿里P9眼中的Junit5手冊如何用?

如果你不理解斷言的字面意思,可以看一下翻譯,asserts表示明確肯定。

沒錯,小黑哥不光講技術,還教英語~

在JUnit5中斷言的作用就是幫助我們用測試用例的實際輸出驗證預期輸出。

簡而言之,斷言是我們在測試中用來驗證預期行為的靜態方法。

所有JUnit5的斷言都在org.juit.jupiter.Assertions類中。這些方法支援Java8 lambda表示式,並被大量過載以支援不同型別,如基本資料型別、物件、stream、陣列等。

斷言方法


斷言方法


作用


assertNull()


斷言實際輸出為null.


assertNotNull()


斷言實際輸出不為null.


fail()


讓測試用例不透過


assertSame()


斷言期望值和實際值是用一個物件


assertNotSame()


斷言期望值和實際值不是用一個物件


assertTrue()


斷言實際值為true


assertFalse()


斷言實際值為false


assertEquals()


斷言期望值和實際值相等


assertNotEquals()


斷言期望值和實際值不相等


assertArrayEquals()


斷言期望陣列和實際陣列相等


assertIterableEquals()


斷言期望可迭代容器和實際可迭代容器相等


assertThrows()


斷言可執行程式碼中會丟擲期望的異常型別


assertAll()


斷言一組中的多個


assertTimeout()


斷言一段可執行程式碼的會在指定時間執行結束


assertTimeoutPreemptively()


斷言可執行程式碼如果超過指定時間會被搶佔中止

在上一節內容中我們初步使用了fail(),assertTrue(), assertFalse(),接下來我們重點介紹一下其他幾個斷言。

assertNull()

該斷言方法幫助我們驗證特定物件是否為空。

  • 如果實際值為空,則測試用例將透過
  • 如果實際值不為空,則測試用例將失敗

assertNull()有三種過載方法,如下所述:

public static void assertNull(Object actual)

public static void assertNull(Object actual, String message)

public static void assertNull(Object actual, Supplier<String> messageSupplier)
複製程式碼
  • AssertNull(Object Actual)-它斷言實際值是否為空。
  • AssertNull(Object Actual,String Message)-它斷言實際值是否為空。在這種情況下,如果實際值不為空,則測試用例將失敗,並顯示一條提供的訊息。
  • AssertNull(Object Actual,Supplier messageSupplier)-它斷言實際值是否為空。在這種情況下,如果實際值不為空,則測試用例將失敗,並透過供應商功能提供一條訊息。使用Supplier函式的主要優點是,只有在測試用例失敗時,它才懶惰地計算為字串。

接下來我們編寫一個程式碼案例。假設我們現在有一個字串工具類StringUtils,該類中提供一個reverse(String)方法,可實現字串反轉功能。

package com.heiz123.junit5;

/**
 * @author 小黑說Java
 * @ClassName StringUtils
 * @Description
 * @date 2022/1/6
 **/

public class StringUtils {

    public static String reverse(String input) {
        if (input == null) {
            return null;
        }

        if (input.length() == 0) {
            return "";
        }

        char[] charArray = input.toCharArray();
        int start = 0;
        int end = input.length() - 1;

        while (start < end) {
            char temp = charArray[start];
            charArray[start] = charArray[end];
            charArray[end] = temp;
            start++;
            end--;
        }

        return new String(charArray);
    }
}
複製程式碼

我們使用assertNull()斷言來對reverse()方法編寫如下測試用例。

  • 如果我們以“ABC”的形式提供輸入字串,它將返回“CBA”
  • 如果我們提供的輸入字串為null,則返回null
  • 如果我們將輸入字串作為“”提供,它將返回“”字串

編寫一個測試類StringUtilsTest,程式碼如下:

package test;

import static org.junit.jupiter.api.Assertions.*;

import java.util.function.Supplier;

import com.heiz123.junit5.StringUtils;
import org.junit.jupiter.api.Test;

/**
 * @author 小黑說Java
 * @ClassName StringUtilsTest
 * @Description
 * @date 2022/1/6
 **/

class StringUtilsTest {

    @Test
    void nullStringRevered() {
        String actual = StringUtils.reverse((null));
        assertNull(actual);
    }

    @Test
    void emptyStringReversed() {
        String actual = StringUtils.reverse((""));
        String message = "Actual String should be null !!! ";
        assertNull(actual, message);
    }

    @Test
    void NonNullStringReversed() {
        String actual = StringUtils.reverse(("ABC"));
        Supplier<String> messageSupplier = () -> "Actual String should be null !!! ";
        // assertNull使用Java 8的MessageSupplier
        assertNull(actual, messageSupplier);
    }

}
複製程式碼

執行測試用例結果我們發現,只有nullStringRevered()測試用例透過。

阿里P9眼中的Junit5手冊如何用?

如果你多次執行測試用例會發現執行順序並不固定。

  • 在StringUtilsTest類中有3個@Test方法: nullStringRevered():當向reverse()方法傳入null時,則返回null。因此,assertNull()會斷言實際返回的值為空。它通過了Junit測試用例。
  • emptyStringReversed():當向reverse()方法傳入""時,則返回""。這裡返回值為空字串,不為空。因此,它無法透過Junit測試用例。在此測試用例中,我們使用過載的assertNull()方法,該方法將字串訊息作為第二個引數。因為這個測試用例不滿足斷言條件,所以它失敗,並給出“AssertionFailedError: Actual String should be null !!! ==> Expected :null Actual :”。
  • NonNullStringReversed:當向reverse()方法傳入"ABC"時,返回"CBA"。這裡,返回值不為空。因此,它無法透過Junit測試用例。在此測試用例中,使用過載的assertNull()方法,該方法將Supplier<String>messageSupplier作為第二個引數。因為此測試用例不滿足斷言條件,所以它失敗,並丟擲“AssertionFailedError: Actual String should be null !!! ==> Expected :null Actual :CBA”。

assertThrows()

該斷言方法有助於斷言用於驗證一段程式碼中是否丟擲期望的異常。

  • 如果沒有引發異常,或者引發了不同型別的異常,則此方法將失敗;
  • 它遵循繼承層次結構,因此如果期望的型別是Exception,而實際是RuntimeException,則斷言也會透過。

assertThrow()也有三種有用的過載方法:

public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable)

public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable, String message)

public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable, Supplier<String> messageSupplier)
複製程式碼

我們透過以下測試程式碼來看一下這個斷言:

package test;

import org.junit.jupiter.api.Test;

import java.io.IOException;

import static org.junit.jupiter.api.Assertions.assertThrows;

/**
 * @author 小黑說Java
 * @ClassName AssertThrowTest
 * @Description
 * @date 2022/1/6
 **/
public class AssertThrowTest {

    @Test
    public void testAssertThrows() {
        assertThrows(ArithmeticException.class, () -> divide(1, 0));
    }

    @Test
    public void testAssertThrowsWithMessage() {
        assertThrows(IOException.class, () -> divide(1, 0), "除以0啦!!!");
    }

    @Test
    public void testAssertThrowsWithMessageSupplier() {
        assertThrows(Exception.class, () -> divide(1, 0), () -> "除以0啦!!!");
    }

    private int divide(int a, int b) {
        return a / b;
    }
}

複製程式碼

在以上測試程式碼中,三個測試用例都使用assertThrows(),分別期望不同的異常型別,對應的可執行程式碼為呼叫divide方法,用1除以0。

執行該測試程式碼結果如下:

阿里P9眼中的Junit5手冊如何用?

  • testAssertThrows() : 該用例期望丟擲ArithmeticException,因為1除以0丟擲的異常就是ArithmeticException,所以該用例透過;
  • testAssertThrowsWithMessage():該用例期望丟擲IOException,ArithmeticException並不是IOException的子類,所以測試未透過;
  • testAssertThrowsWithMessageSupplier():該用例期望丟擲Exception,ArithmeticException是Exception的自雷,所以測試透過。

使用Supplier<String> messageSupplier引數的斷言相比使用String message引數斷言有一個優點,就是隻有在斷言不透過的時候,才會構造字串物件。

assertTimeout()

該斷言方法它用於測試長時間執行的任務。

如果測試用例中的給定任務花費的時間超過指定時間,則測試用例將失敗。

提供給測試用例的任務將與呼叫程式碼在同一個執行緒中執行。此外,如果超時,並不會搶先中止任務的執行。

該方法同樣有三個過載實現:

public static void assertTimeout(Duration timeout, Executable executable)

public static void assertTimeout(Duration timeout, Executable executable, String message)

public static void assertTimeout(Duration timeout, Executable executable, Supplier<String> messageSupplier)
複製程式碼

透過以下測試程式碼來看一下該斷言方法如何執行:

package test;

import org.junit.jupiter.api.Test;

import java.time.Duration;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTimeout;

/**
 * @author 小黑說Java
 * @ClassName AssertTimeoutTest
 * @Description
 * @date 2022/1/6
 **/
public class AssertTimeoutTest {
    @Test
    void timeoutNotExceeded() {
        // 該斷言成功
        assertTimeout(Duration.ofMinutes(3), () -> {
            // 執行不到3分鐘的任務
        });
    }

    @Test
    void timeoutNotExceededWithResult() {
        // 該斷言執行成功並返回物件
        String actualResult = assertTimeout(Duration.ofMinutes(3), () -> {
            return "result";
        });
        assertEquals("result", actualResult);
    }

    @Test
    void timeoutNotExceededWithMethod() {
        // 該斷言呼叫一個方法引用並返回一個物件
        String actualGreeting = assertTimeout(Duration.ofMinutes(3), AssertTimeoutTest::greeting);
        assertEquals("Hello, World!", actualGreeting);
    }

    @Test
    void timeoutExceeded() {
        // 以下斷言失敗,並顯示類似於以下內容的錯誤訊息:
        // execution exceeded timeout of 10 ms by 91 ms
        assertTimeout(Duration.ofMillis(10), () -> {
            // 模擬耗時超過10毫秒的任務
            Thread.sleep(100);
            System.out.println("結束");
        });
    }

    private static String greeting() {
        return "Hello, World!";
    }

}
複製程式碼

執行以上測試用例結果如下:

阿里P9眼中的Junit5手冊如何用?

除了timeoutExceeded()其他測試用例都透過。並且從日誌中可以看到,有打印出“結束”,說明並未將任務執行執行緒中斷。

assertTimeoutPreemptively()

該斷言方法和assertTimeout()作用基本相同,但是有一個主要的區別,使用該斷言提供給測試用例的任務將在與呼叫程式碼不同的執行緒中執行。並且,如果執行超時,任務的執行將被搶先中止。

package test;

import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import java.time.Duration;
import org.junit.jupiter.api.Test;

/**
 * @author 小黑說Java
 * @ClassName AssertTimeoutPreemptivelyTest
 * @Description
 * @date 2022/1/6
 **/
public class AssertTimeoutPreemptivelyTest {
    @Test
    void timeoutExceededWithPreemptiveTermination() {
        // 以下斷言失敗,並顯示類似於以下內容的錯誤訊息:
        // execution timed out after 10 ms
        assertTimeoutPreemptively(Duration.ofMillis(10), () -> {
            // 模擬耗時超過10毫秒的任務
            Thread.sleep(100);
            System.out.println("結束");
        });
    }
}
複製程式碼

執行該測試案例結果如下,可以看到,並沒有打印出“結束”。

阿里P9眼中的Junit5手冊如何用?

斷言是org.juit.jupiter.Assertions類中的一系列靜態方法,其作用就是在測試中用來驗證預期行為。

什麼是“假設”?

阿里P9眼中的Junit5手冊如何用?

在JUnit5中的org.junit.jupiter.api.Assumptions類中定義了一系列的支援基於假設的條件執行的測試方法。

與斷言相比,如果假設方法失敗並不會導致測試用例的失敗,只會讓測試用例中止。

假設通常在中斷沒有意義的測試方法時使用。例如,如果測試依賴於當前執行時環境中並不存在的內容,那麼這個測試案例就沒有測試意義。

如果假設不滿足,則會丟擲TestAbortedException。

Junit 5中有3種類型的假設:

assumeTrue() : 假設為真。

assumeFalse(): 假設為假。

assumeThat(): 假設是某種特定情況。

assumeTrue()

該方法假設給定的引數是true,如果傳入的引數為true,則測試繼續執行;如果假設為false,則該測試方法中止。

assumeTrue()有三類型別的過載方法:

// 使用boolean作為假設驗證
public static void assumeTrue(boolean assumption) throws TestAbortedException
public static void assumeTrue(boolean assumption, Supplier<String> messageSupplier) throws TestAbortedException
public static void assumeTrue(boolean assumption, String message) throws TestAbortedException

// 使用BooleanSupplier作為假設驗證
public static void assumeTrue(BooleanSupplier assumptionSupplier) throws TestAbortedException
public static void assumeTrue(BooleanSupplier assumptionSupplier, String message) throws TestAbortedException
public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier<String> messageSupplier) throws TestAbortedException
複製程式碼

接下來我們看一下上面的方法如何使用:

package test;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assumptions.assumeTrue;

/**
 * @author 小黑說Java
 * @ClassName AssumeTrueTest
 * @Description
 * @date 2022/1/6
 **/
public class AssumeTrueTest {
    @Test
    void testOnDevelopmentEnvironment() {
        System.setProperty("ENV", "DEV");
        assumeTrue("DEV".equals(System.getProperty("ENV")));
        //後續程式碼會繼續執行
        System.out.println("開發環境測試");
    }

    @Test
    void testOnProductionEnvironment() {
        System.setProperty("ENV", "PROD");
        assumeTrue("DEV".equals(System.getProperty("ENV")), "假設失敗");
        // 後續程式碼不會執行
        System.out.println("生產環境測試");
    }
}
複製程式碼

執行結果如下,testOnProductionEnvironment()因為假設結果為false,所以中斷,並沒有列印輸出語句。

阿里P9眼中的Junit5手冊如何用?

assumeFalse()

assumeFalse()和assumeTrue()同理,驗證給定的假設是否為false,如果為false,則測試繼續,如果為true,測試中止。

assumingThat()

假設JUnit5中的API有一個靜態實用程式方法,名為AssergingThat()。 該假設方法接收一個Boolean或BooleanSupplier作為假設,對該假設進行驗證,如果驗證透過,則執行第二個引數傳入的Executable;如果假設驗證不透過,則不執行;不管是否驗證透過,都不會影響測試用例後續的執行。

public static void assumingThat(boolean assumption, Executable executable)
public static void assumingThat(BooleanSupplier assumptionSupplier, Executable executable)
複製程式碼

我們透過下面程式碼來驗證一下該假設方法:

package test;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumingThat;

/**
 * @author 小黑說Java
 * @ClassName AssumingThatTest
 * @Description
 * @date 2022/1/6
 **/
public class AssumingThatTest {

    @Test
    void testInAllEnvironments() {
        System.setProperty("ENV", "DEV");
        assumingThat("DEV".equals(System.getProperty("ENV")),
                () -> {
                    System.out.println("testInAllEnvironments - 只在開發環境執行!!!");
                    assertEquals(2, 1 + 1);
                });
        assertEquals(42, 40 + 2);
    }

    @Test
    void testInAllEnvironments2() {
        System.setProperty("ENV", "DEV");
        assumingThat("PROD".equals(System.getProperty("ENV")),
                () -> {
                    System.out.println("testInAllEnvironments2 - 只在生產環境執行 !!!");
                    assertEquals(2, 1 + 1);
                });
        assertEquals(42, 40 + 2);
    }
}
複製程式碼

執行結果如下,可以看到只有testInAllEnvironments()中的Executable執行了:

阿里P9眼中的Junit5手冊如何用?

生命週期相關注解

在Junit5中,對於每個@Test,都會建立一個測試類的新例項。例如,如果一個類有兩個@Test方法,那麼將建立兩個測試類例項,每個@Test一個。因此,測試類的建構函式被呼叫的次數與@Test方法的數量一樣多。

我們可以透過如下程式碼來證明這一點:

package test;

import org.junit.jupiter.api.Test;

/**
 * @author 小黑說Java
 * @ClassName LifecycleTest
 * @Description
 * @date 2022/1/6
 **/
public class LifecycleTest {

    public LifecycleTest() {
        System.out.println("LifecycleTest - 建立例項 !!!");
    }

    @Test
    public void testOne() {
        System.out.println("LifecycleTest - testOne()執行!!!");
    }

    @Test
    public void testTwo() {
        System.out.println("LifecycleTest - testTwo()執行!!!");
    }

}
複製程式碼

從以下執行結果我們可以看出,確實執行了兩次構造方法。這表明每個測試方法都是在各自的測試物件例項中執行。

阿里P9眼中的Junit5手冊如何用?

@BeforeEach和@AfterEach

顧名思義,使用@BeforeEach和@AfterEach註釋的方法在每個@Test方法之前和之後呼叫。因此,如果測試類中有兩個@Test方法,則@BeforeEach方法將在測試方法之前被呼叫兩次,@AfterEach方法同理。

package test;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
 * @author 小黑說Java
 * @ClassName LifecycleTest
 * @Description
 * @date 2022/1/6
 **/
public class LifecycleTest {

    public LifecycleTest() {
        System.out.println("LifecycleTest - 建立例項 !!!");
    }

    @BeforeEach
    public void beforeEach() {
        System.out.println("LifecycleTest - beforeEach()執行!!!");
    }

    @Test
    public void testOne() {
        System.out.println("LifecycleTest - testOne()執行!!!");
    }

    @Test
    public void testTwo() {
        System.out.println("LifecycleTest - testTwo()執行!!!");
    }

    @AfterEach
    public void afterEach() {
        System.out.println("LifecycleTest - afterEach()執行 !!!");
    }

}
複製程式碼

執行結果如下:

阿里P9眼中的Junit5手冊如何用?

因為Junit測試類有兩個@Test方法,所以它在測試類的單獨例項中執行每個測試方法。隨機選擇一個@Test方法,按照如下順序執行:

  1. 執行構造方法建立例項;
  2. 執行@BeforeEach註解方法;
  3. 執行@Test方法;
  4. 執行@AfterEach註解方法。

然後,再按照相同的步驟執行下一個@Test方法。

通常,當我們擁有跨各種測試用例的公共設定邏輯時,公共初始化程式碼放在@BeforeEach方法中,並在@AfterEach方法中進行清理。

@BeforeAll和@AfterAll

顧名思義,@BeforeAll是在所有測試用例執行之前執行,@AfterAll是在所有測試用例執行之後執行。

這兩個註解可@BeforeEach和@AfterEach還有一點不同,就是隻能修改在靜態方法上。這裡也很好理解,因為每個@Test方法都是單獨的測試物件,要在所有用例前執行的方法必然不能是某一個物件的方法。

package test;

import org.junit.jupiter.api.*;

/**
 * @author 小黑說Java
 * @ClassName LifecycleTest
 * @Description
 * @date 2022/1/6
 **/
public class LifecycleTest {

    @BeforeAll
    public static void beforeAll() {
        System.out.println("LifecycleTest - beforeAll()執行!!!");
    }

    public LifecycleTest() {
        System.out.println("LifecycleTest - 建立例項 !!!");
    }

    @BeforeEach
    public void beforeEach() {
        System.out.println("LifecycleTest - beforeEach()執行!!!");
    }

    @Test
    public void testOne() {
        System.out.println("LifecycleTest - testOne()執行!!!");
    }

    @Test
    public void testTwo() {
        System.out.println("LifecycleTest - testTwo()執行!!!");
    }

    @AfterEach
    public void afterEach() {
        System.out.println("LifecycleTest - afterEach()執行 !!!");
    }

    @AfterAll
    public static void afterAll() {
        System.out.println("LifecycleTest - afterAll()執行!!!");
    }

}
複製程式碼

執行以上程式碼結果如下:

阿里P9眼中的Junit5手冊如何用?

@BeforeAll和@Afterall具有以下特點:

  • 這兩種方法都是靜態方法。
  • 這兩種方法在測試生命週期中都只會呼叫一次;
  • @BeforeAll方法是類級方法,會在建構函式之前被呼叫;
  • @AfterAll方法也是類級別的方法,它在所有方法執行後被呼叫。
  • @BeforeAll方法常用於需要進行資源初始化的地方,比如資料庫連線、伺服器啟動等。這些資源可以被測試方法使用。
  • @AfterAll方法常用於需要進行昂貴的資源清理的地方,比如資料庫連線關閉、伺服器停止等。

自定義名稱@DisplayName

JUnit5中的@DisplayName註解用於為測試類自定義名稱。預設情況下,JUnit5測試報告會在IDE測試報告中和在執行測試用例時列印測試類的類名。我們可以使用@DisplayName註解為測試類提供自定義名稱,這使其更易於閱讀。

@DisplayName不僅可以放在類上,我們還可以加在測試方法上,給每個測試用例自定義名稱。

@DisplayName註解可以接受具有以下內容的字串:

  • 一串單詞;
  • 特殊字元;
  • 甚至可以使用表情符號。
@DisplayName("$測試 DisplayName  %^&")
public class DisplayNameTest {

    @Test
    @DisplayName("$用例1%^&")
    public void test() {
        System.out.println("test method執行!!!");
    }
}
複製程式碼

阿里P9眼中的Junit5手冊如何用?

引數解析器ParameterResolver

在Junit5之前的版本中,對在測試類的構造方法或測試方法中使用引數的支援比較有限。

JUnit5的Jupiter中的一個主要變化是現在允許測試建構函式和測試方法都有引數。這些引數為建構函式和測試方法提供元資料。

因此,可以更靈活的支援測試方法和建構函式的依賴項注入。

在JUnit 5中,org.juit.jupiter.api.tension包中有一個名為ParameterResolver的介面。該介面定義了希望在執行時動態解析引數的測試擴充套件的API。

Junit 5中有多種型別的ParameterResolver。通常,如果測試建構函式或@Test、@BeforeEach、@AfterEach、@BeforeAll、@AfterAll、@TestFactory方法接受引數,則必須在執行時由註冊的ParameterResolver解析該引數。

阿里P9眼中的Junit5手冊如何用?

TestInfoParameterResolver

TestInfoParameterResolver是一個內建的ParameterResolver,如果方法引數的型別為TestInfo,則表示TestInfoParameterResolver將提供與當前測試對應的TestInfo例項作為該引數的值。然後,可以使用作為引數的TestInfo來檢索有關當前測試的資訊或元資料,例如測試的顯示名稱、測試類、測試方法或關聯的標記。 如果在測試方法上使用@DisplayName註釋,則檢索與測試方法關聯的自定義名稱,否則檢索技術名稱,即測試類或測試方法的實際名稱。

package test;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;

/**
 * @author 小黑說Java
 * @ClassName TestInfoParameterTest
 * @Description
 * @date 2022/1/6
 **/
@DisplayName("測試Parameter")
public class TestInfoParameterTest {

    @BeforeAll
    public static void beforeAll(TestInfo testInfo) {
        System.out.println("beforeAll() 執行- ");
        System.out.println("Display name - " + testInfo.getDisplayName());
        System.out.println("Test Class - " + testInfo.getTestClass());
        System.out.println("Test Method - " + testInfo.getTestMethod());
        System.out.println("*******************************************");
    }

    public TestInfoParameterTest(TestInfo testInfo) {
        System.out.println("Constructor 執行 - ");
        System.out.println("Display name - " + testInfo.getDisplayName());
        System.out.println("Test Class - " + testInfo.getTestClass());
        System.out.println("Test Method - " + testInfo.getTestMethod());
        System.out.println("*******************************************");
    }

    @BeforeEach
    public void beforeEach(TestInfo testInfo) {
        System.out.println("beforeEach()執行 - ");
        System.out.println("Display name - " + testInfo.getDisplayName());
        System.out.println("Test Class - " + testInfo.getTestClass());
        System.out.println("Test Method - " + testInfo.getTestMethod());
        System.out.println("*******************************************");
    }

    @Test
    @DisplayName("測試用例1")
    public void testOne(TestInfo testInfo) {
        System.out.println("testOne() got executed with test info as - ");
        System.out.println("Display name - " + testInfo.getDisplayName());
        System.out.println("Test Class - " + testInfo.getTestClass());
        System.out.println("Test Method - " + testInfo.getTestMethod());
        System.out.println("*******************************************");
    }

}
複製程式碼

以上程式碼執行結果如下:

阿里P9眼中的Junit5手冊如何用?

從結果可以看出,傳入TestInfo引數,可以在方法中獲取到對應的測試用例方法名,以及對應的DisplayName等資訊。

禁用測試用例

我們可能需要在某些情況下禁用我們的測試用例,在Junit5中提供了一禁用或啟用測試方法或測試類的能力。

@Disabled

該註解可以作用在測試類或測試方法上,如果在測試類上則表示該類下的所有測試方法都關閉;如果作用在方法上,表示該測試方法被關閉。

同時,該註解有一個可選引數,可以作為關閉測試用例的原因。

package test;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * @author 小黑說Java
 * @ClassName DisableTest
 * @Description
 * @date 2022/1/6
 **/
@Disabled
public class DisableTest {
    @Test
    void evenNumberTrue() {
        OddEven oddEven = new OddEven();
        assertTrue(oddEven.isNumberEven(10));
    }

    @Test
    @Disabled("因為XXX關閉該用例")
    void oddNumberFale() {
        OddEven oddEven = new OddEven();
        assertFalse(oddEven.isNumberEven(11));
    }

}
複製程式碼

Junit5還提供了其他各種可以對測試用例進行禁用和啟用的註解,這裡不在重複講解。如果你有興趣可以透過Junit 5中jupiter官方API瞭解更多.

巢狀測試

@Nested註解可以支援對多個測試進行分組,來建立各個測試用例之間的關係。通常是在主測試類中新增內部類的方式來實現。

但是,預設情況下,內部類並不參與測試執行,只有使用@Nested註解修飾的內部類才能具備測試功能。

package test;

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.LinkedList;
import java.util.Queue;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author 小黑說Java
 * @ClassName TestingAQueueTest
 * @Description
 * @date 2022/1/6
 **/
public class TestingAQueueTest {
    // 字串佇列
    Queue<String> queue;

    @Test
    @DisplayName("is null")
    void isNotInstantiated() {
        assertNull(queue);
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            queue = new LinkedList<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(queue.isEmpty());
        }

        @Test
        @DisplayName("return null element when polled")
        void returnNullWhenPolled() {
            assertNull(queue.poll());
        }

        @Test
        @DisplayName("return null element when peeked")
        void returnNullWhenPeeked() {
            assertNull(queue.peek());
        }

        @Nested
        @DisplayName("after offering an element")
        class AfterOffering {

            String anElement = "an element";

            @BeforeEach
            void offerAnElement() {
                queue.offer(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(queue.isEmpty());
            }

            @Test
            @DisplayName("returns the element when polled and is empty")
            void returnElementWhenPolled() {
                assertEquals(anElement, queue.poll());
                assertTrue(queue.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, queue.peek());
                assertFalse(queue.isEmpty());
            }
        }
    }
}
複製程式碼

執行結果如下:

阿里P9眼中的Junit5手冊如何用?

透過@Nested註解,可以讓我們的測試用例更有結構,從邏輯上我們可以將測試用例進行分組,更有可讀性。

重複測試

Junit5的jupiter中提供了重複測試指定次數的能力。 使用@repeatedtest註解的方法來完成。 也可透過@DisplayName自定義重複測試的名稱。

package test;

import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;

/**
 * @author 小黑說Java
 * @ClassName RepeatedTest
 * @Description
 * @date 2022/1/6
 **/
public class RepeateTest {

    @RepeatedTest(5)
    public void RepeatedTest() {
        assertTrue(0 < 5);
    }

    @RepeatedTest(name = "{displayName} - {currentRepetition}/{totalRepetitions}", value = 5)
    @DisplayName("Repeated test")
    public void repeatedTestWithDisplayName() {
        assertTrue(0 < 5);
    }
}
複製程式碼

執行結果如下:

阿里P9眼中的Junit5手冊如何用?

總結

Junit是Java中非常流行的測試框架,在我們日常開發中編寫單元測試會經常用到,新版本的Junit5包含Platform、Jupiter、Vintage三個模組。

在Junit5中新出現了很多創新,支援Java8+的新功能,以及一些不同的測試風格,讓我們在單元測試時能更靈活,擁抱變化,持續學習。

原文連結:https://juejin.cn/post/7050111986360811557

分類: 歷史
時間: 2022-01-07

相關文章

2006年,75歲的劉思齊,來到毛岸英犧牲地,了結毛澤東多年心願

2006年,75歲的劉思齊,來到毛岸英犧牲地,了結毛澤東多年心願
毛岸英與劉思齊早年間合影 1938年乍暖還寒,延安黨校禮堂里正規矩地坐著幾行人,正屏氣凝神地觀看話劇<棄兒>. "媽媽,媽媽,嗚嗚,媽媽",舞臺上,一位8歲的小女孩,身 ...

84歲毛岸青去世,劉思齊、李敏李訥前來追悼送別,邵華哭成淚人

84歲毛岸青去世,劉思齊、李敏李訥前來追悼送別,邵華哭成淚人
2007年,人民領袖毛澤東主席的兒子毛岸青在北京解放軍醫院治療無效,離開了人世. 作為毛主席的兒子,毛岸青一生默默無聞,但是他一生都在為國為民而奔波忙碌,他的一生是平凡而偉大的一生. 4月2日上午,毛 ...

62年,劉思齊和楊茂之結婚,毛澤東囑咐:以後不要疏遠父女之情喲

62年,劉思齊和楊茂之結婚,毛澤東囑咐:以後不要疏遠父女之情喲
前言 1927年春天,武昌都府堤41號來了兩位客人,楊開慧才生下第三個孩子不久,她被保姆攙扶著走到客廳,看到毛澤東正在同他們交談.到訪的是劉謙初和張文秋,他們即將結婚,毛澤東笑著說:"別人會 ...

劉思齊為毛岸英掃墓,回來大病一場,毛主席說:意識為主醫藥為輔

劉思齊為毛岸英掃墓,回來大病一場,毛主席說:意識為主醫藥為輔
1950年10月7日,中央決定出兵抗美援朝,確定彭德懷掛帥出征.毛主席親自在家設宴餞行. 彭老總來到院子裡,主席迎上前,兩雙大手緊緊地握在一起.幾十年的革命友誼,一切盡在不言中. 彭德懷說:" ...

生娃後的吉娜不太自信,穿膝上波點裙走機場,挽著郎朗胳膊不撒手

生娃後的吉娜不太自信,穿膝上波點裙走機場,挽著郎朗胳膊不撒手
在復古流行的今天,很多人會為經典元素著迷,它們擁有被多個時代所接受的通用美感.而波點元素,在經典中又帶有浪漫慵懶的氣息,憑藉獨特的質感一直被人津津樂道.而且它的風格也不是單一的,年輕女孩喜歡它的清新文 ...

法鏈銜接:區塊鏈數字資產追贓挽損新方法

法鏈銜接:區塊鏈數字資產追贓挽損新方法
蔡欣 "法鏈銜接"數字資產追贓挽損方法邏輯示意圖 區塊鏈數字資產,是指基於區塊鏈技術產生的.可被持有和轉移所有權的特定計算機編碼.它不僅包括所謂的"數字貨幣", ...

刑場上抗聯戰士無奈槍殺12名同志後自殺,用槍聲發出最後絕密情報

刑場上抗聯戰士無奈槍殺12名同志後自殺,用槍聲發出最後絕密情報
一.抗聯的槍聲響徹白山黑水 1931年9月18日,日本關東軍自導自演了破壞南滿鐵路柳條湖段的鬧劇,並栽贓東北軍破壞,隨後悍然炮轟瀋陽東北軍駐地北大營,悍然進攻瀋陽城,史稱"九一八事變&quo ...

切爾西1-0澤尼特:樹立以盧卡庫同志為核心的進攻集體

切爾西1-0澤尼特:樹立以盧卡庫同志為核心的進攻集體
歐冠,衛冕冠軍切爾西1-0擊敗對手贏得開門紅.其中,盧卡庫在第69分鐘頭球破門斬獲全場唯一進球. 此役,盧卡庫出任單箭頭,齊耶赫.芒特埋伏身後,若日尼奧領銜中場. 比賽過程: 第33分鐘,芒特斜傳,盧 ...

陳喬年同志犧牲後,有一獨女遺失,為何直到1994年才被家人找到?

陳喬年同志犧牲後,有一獨女遺失,為何直到1994年才被家人找到?
1928年,繼大哥陳延年之後,陳喬年再次被國民黨反動派逮捕,而後歷經煉獄般的生活,最終壯烈犧牲,離別之際,他還慷慨激昂的講道:"人生總有一死,血不會白流!讓子孫後代享受前人披荊斬棘的幸福吧. ...

80年代面對“走私狂潮”,陳雲上報後,小平同志如何批示?

80年代面對“走私狂潮”,陳雲上報後,小平同志如何批示?
1983年1月17日,一場鬧得沸沸揚揚的"改革開放腐敗第一案"在廣東省汕頭市正式宣判.這一天,汕頭人民廣場人潮湧動,一萬七千多人在這裡成為了歷史見證者.當聽到"死刑&qu ...

1978年,有人在紐約報紙上勸他戒菸,鄧小平的回覆很“小平同志”

1978年,有人在紐約報紙上勸他戒菸,鄧小平的回覆很“小平同志”
古今中外,有不少領導人都有抽菸的習慣,也有不少人都有著相似的戒菸故事,而我們熟知的鄧小平同志就是其中的一位. 常年吸菸的人都或多或少有過被勸誡"戒菸"的經歷,鄧小平同志也不例外.由 ...

1939年鄧小平結婚,彭德懷初見卓琳:小平同志,你可真會找老婆呀

1939年鄧小平結婚,彭德懷初見卓琳:小平同志,你可真會找老婆呀
1939年9月的一天,入秋後的延安已經能感受到一絲絲的涼意了.在這天晚上,夜色很美,夜光灑在了一座座窯洞上,灑在了寶塔山,灑在了延河. 突然間,一聲軍號響了起來,這是在催促勞作了一天的人們儘快休息. ...

上海剛剛解放,李克農就急電陳毅:務必要找到一位叫李靜安的同志

上海剛剛解放,李克農就急電陳毅:務必要找到一位叫李靜安的同志
1949年5月27日上海解放,次日,中共中央決定由第三野戰軍司令員陳毅擔任上海市市長,負責儘快"醫治"戰爭創傷,實現上海的重建. 陳毅上任後馬不停蹄,立刻就為戰後的恢復工作開始了奔 ...

劉思齊回憶:毛岸英赴戰場前特意交待了四件事,犧牲後才明白寓意

劉思齊回憶:毛岸英赴戰場前特意交待了四件事,犧牲後才明白寓意
作為毛主席和楊開慧的長子,英年早逝的毛岸英可以說是毛主席內心的一大痛楚.1922年出生的毛岸英由於年代動盪,不得已跟著父親四處奔波,曾經被毛主席驕傲中不失感慨地評價道:"這孩子從小就吃百家飯 ...

劉思齊改嫁後,給長子取名“小英”紀念毛岸英,40年後收到撫卹金

劉思齊改嫁後,給長子取名“小英”紀念毛岸英,40年後收到撫卹金
1950年11月25日,這是一個劉思齊永遠走不出來的日子,這一天,在遙遠的朝鮮戰場上,美軍的炮彈瘋狂傾瀉在志願軍陣地上,毛岸英所在的作戰室遭到轟炸,年輕的戰士毛岸英把自己的生命留在了朝鮮的土地上. 過 ...

毛澤東穿著毛褲趕到陳毅追悼會,握著張茜的手說:陳毅是個好同志

毛澤東穿著毛褲趕到陳毅追悼會,握著張茜的手說:陳毅是個好同志
1972年1月10日,午飯過後,身穿淡黃色睡衣的毛主席,在堆滿書的臥床上輾轉反側,按照毛主席當時的生活習慣,吃完午飯是要睡一覺的,但是在這一天,他無論如何也睡不著. 因為他知道在下午三點,首都各界群眾 ...

美國南方司令部給美空軍慶生海報卻用了俄戰機照片,俄媒超諷刺:謝謝,同志們

美國南方司令部給美空軍慶生海報卻用了俄戰機照片,俄媒超諷刺:謝謝,同志們
來源:環球網 [環球網報道]尷尬場面又出現了-- "今日俄羅斯"(RT)訊息,美國南方司令部當時間18日發推,給美國空軍送上生日祝福.結果尷尬的是,生日海報上卻用了一張俄羅斯蘇-2 ...

美國南方司令部用俄戰機慶祝美空軍生日,俄媒笑翻:謝謝同志們

美國南方司令部用俄戰機慶祝美空軍生日,俄媒笑翻:謝謝同志們
[南方+9月19日訊]據今日俄羅斯(RT)報道,當地時間9月18日,美軍南方司令部的官方推特賬號釋出了一張以俄羅斯蘇霍伊型戰鬥機剪影為主題的海報圖片來紀念美國空軍生日後,面臨網友.媒體的嘲笑.俄羅斯媒 ...

傳奇臥底師!鄧小平指示軍紀不要嚴,同志起初不理解,後茅塞頓開

傳奇臥底師!鄧小平指示軍紀不要嚴,同志起初不理解,後茅塞頓開
作者:太史小生 內線工作,尋機起義,就是以敵人的身份,打入敵人內部,在長時間的潛伏中,秘密地進行策反工作,使敵內部的一些人員,不斷接受進步思想的影響,逐步認識到敵人的反動本質以及必然失敗的命運,深入瞭 ...

【公安紅色印記】護送劉少奇同志過封鎖線

【公安紅色印記】護送劉少奇同志過封鎖線
平遙縣抗日民主政府紀念館. 山西平遙是晉冀魯豫根據地通往延安的交通要道之一,這裡也是日寇重點佔領區.中共平遙縣委領導抗日軍民建立了一條50多公里長的地下交通線,劉少奇.羅瑞卿.陳賡等很多領導幹部都曾透 ...