最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

JUnit 5 參數(shù)化測試

2023-04-25 10:25 作者:信碼由韁  | 我要投稿


JUnit 5參數(shù)化測試

目錄

  • 設(shè)置

  • 我們的第一個參數(shù)化測試

  • 參數(shù)來源

    • @ValueSource

    • @NullSource & @EmptySource

    • @MethodSource

    • @CsvSource

    • @CsvFileSource

    • @EnumSource

    • @ArgumentsSource

    • 參數(shù)轉(zhuǎn)換

    • 參數(shù)聚合

  • 獎勵

  • 總結(jié)

如果您正在閱讀這篇文章,說明您已經(jīng)熟悉了JUnit。

讓我為您概括一下JUnit——在軟件開發(fā)中,我們開發(fā)人員編寫的代碼可能是設(shè)計一個人的個人資料這樣簡單,也可能是在銀行系統(tǒng)中進行付款這樣復(fù)雜。在開發(fā)這些功能時,我們傾向于編寫單元測試。顧名思義,單元測試的主要目的是確保代碼的小、單獨部分按預(yù)期功能工作。如果單元測試執(zhí)行失敗,這意味著該功能無法按預(yù)期工作。編寫單元測試的一種工具是JUnit。這些單元測試程序很小,但是非常強大,并且可以快速執(zhí)行。如果您想了解更多關(guān)于JUnit 5(也稱為JUnit Jupiter)的信息,請查看這篇JUnit5的文章。

現(xiàn)在我們已經(jīng)了解了JUnit,接下來讓我們聚焦于JUnit 5中的參數(shù)化測試。參數(shù)化測試可以解決在為任何新/舊功能開發(fā)測試框架時遇到的最常見問題。

  • 編寫針對每個可能輸入的測試用例變得更加容易。

  • 單個測試用例可以接受多個輸入來測試源代碼,有助于減少代碼重復(fù)。

  • ?通過使用多個輸入運行單個測試用例,我們可以確信已涵蓋所有可能的場景,并維護更好的代碼覆蓋率。

開發(fā)團隊通過利用方法和類來創(chuàng)建可重用且松散耦合的源代碼。傳遞給代碼的參數(shù)會影響其功能。例如,計算器類中的sum方法可以處理整數(shù)和浮點數(shù)值。JUnit 5引入了執(zhí)行參數(shù)化測試的能力,可以使用單個測試用例測試源代碼,該測試用例可以接受不同的輸入。這樣可以更有效地進行測試,因為在舊版本的JUnit中,必須為每種輸入類型創(chuàng)建單獨的測試用例,從而導(dǎo)致大量的代碼重復(fù)。

? 示例代碼

本文附帶有在 GitHub上?的一個可工作的示例代碼。

設(shè)置

就像瘋狂泰坦滅霸喜歡訪問力量一樣,您可以使用以下Maven依賴項來訪問JUnit5中參數(shù)化測試的力量:

<dependency>

????<groupId>org.junit.jupiter</groupId>

????<artifactId>junit-jupiter-params</artifactId>

????<version>5.9.2</version>

????<scope>test</scope>

</dependency>

讓我們來寫些代碼,好嗎?

我們的第一個參數(shù)化測試

?現(xiàn)在,我想向您介紹一個新的注解 @ParameterizedTest。顧名思義,它告訴JUnit引擎使用不同的輸入值運行此測試。

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

import org.junit.jupiter.params.ParameterizedTest;

import org.junit.jupiter.params.provider.ValueSource;

public class ValueSourceTest {

????@ParameterizedTest

????@ValueSource(ints = { 2, 4 })

????void checkEvenNumber(int number) {

????????assertEquals(0, number % 2,

???????? "Supplied number is not an even number");

????}

}

在上面的示例中,注解@ValueSource為?checkEvenNumber() 方法提供了多個輸入。假設(shè)我們使用JUnit4編寫相同的代碼,即使它們的結(jié)果(斷言)完全相同,我們也必須編寫2個測試用例來覆蓋輸入2和4。

當(dāng)我們執(zhí)行 ValueSourceTest 時,我們會看到什么:

ValueSourceTest

|_ checkEvenNumber

|_ [1] 2

|_ [2] 4

這意味著?checkEvenNumber() 方法將使用2個輸入值執(zhí)行。

在下一節(jié)中,讓我們學(xué)習(xí)一下JUnit5框架提供的各種參數(shù)來源。

參數(shù)來源

JUnit5提供了許多參數(shù)來源注釋。下面的章節(jié)將簡要概述其中一些注釋并提供示例。

@ValueSource

?@ValueSource是一個簡單的參數(shù)源,可以接受單個字面值數(shù)組。@ValueSource支持的字面值類型有shortbyte、int、long、float、double、charboolean、StringClass。

@ParameterizedTest

@ValueSource(strings = { "a1", "b2" })

void checkAlphanumeric(String word) {

????assertTrue(StringUtils.isAlphanumeric(word),

???????????? "Supplied word is not alpha-numeric");

}

@NullSource & @EmptySource

假設(shè)我們需要驗證用戶是否已經(jīng)提供了所有必填字段(例如在登錄函數(shù)中需要提供用戶名和密碼)。我們使用注解來檢查提供的字段是否為 null,空字符串或空格。

  • 在單元測試中使用 @NullSource 和 @EmptySource 可以幫助我們提供帶有 null、空字符串和空格的數(shù)據(jù)源,并驗證源代碼的行為。

@ParameterizedTest

@NullSource

void checkNull(String value) {

????assertEquals(null, value);

}

@ParameterizedTest

@EmptySource

void checkEmpty(String value) {

????assertEquals("", value);

}

  • 我們還可以使用 @NullAndEmptySource 注解來組合傳遞 null 和空輸入。

@ParameterizedTest

@NullAndEmptySource

void checkNullAndEmpty(String value) {

????assertTrue(value == null || value.isEmpty());

}

  • 另一個傳遞 null、空字符串和空格輸入值的技巧是結(jié)合使用 @NullAndEmptySource 注解,以覆蓋所有可能的負面情況。該注解允許我們從一個或多個測試類的工廠方法中加載輸入,并生成一個參數(shù)流。

@ParameterizedTest

@NullAndEmptySource

@ValueSource(strings = { " ", " " })

void checkNullEmptyAndBlank(String value) {

????assertTrue(value == null || value.isBlank());

}

@MethodSource

該注解允許我們從一個或多個測試類的工廠方法中加載輸入,并生成一個參數(shù)流。

  • 顯式方法源 - 測試將嘗試加載提供的方法。

// Note: The test will try to load the supplied method

@ParameterizedTest

@MethodSource("checkExplicitMethodSourceArgs")

void checkExplicitMethodSource(String word) {

assertTrue(StringUtils.isAlphanumeric(word),

"Supplied word is not alpha-numeric");

}

static Stream<String> checkExplicitMethodSourceArgs() {

return Stream.of("a1",

"b2");

}

  • 隱式方法源 - 測試將搜索與測試類匹配的源方法。

// Note: The test will search for the source method

// that matches the test-case method name

@ParameterizedTest

@MethodSource

void checkImplicitMethodSource(String word) {

????assertTrue(StringUtils.isAlphanumeric(word),

"Supplied word is not alpha-numeric");

}

static Stream<String> checkImplicitMethodSource() {

return Stream.of("a1",

"b2");

}

  • 多參數(shù)方法源 - 我們必須將輸入作為參數(shù)流傳遞。測試將按照索引順序加載參數(shù)。

// Note: The test will automatically map arguments based on the index

@ParameterizedTest

@MethodSource

void checkMultiArgumentsMethodSource(int number, String expected) {

????assertEquals(StringUtils.equals(expected, "even") ? 0 : 1, number % 2);

}

static Stream<Arguments> checkMultiArgumentsMethodSource() {

????return Stream.of(Arguments.of(2, "even"),

???? Arguments.of(3, "odd"));

}

  • 外部方法源 - 測試將嘗試加載外部方法。

// Note: The test will try to load the external method

@ParameterizedTest

@MethodSource(

"source.method.ExternalMethodSource#checkExternalMethodSourceArgs")

void checkExternalMethodSource(String word) {

????assertTrue(StringUtils.isAlphanumeric(word),

"Supplied word is not alpha-numeric");

}

package source.method;

import java.util.stream.Stream;

public class ExternalMethodSource {

????static Stream<String> checkExternalMethodSourceArgs() {

????????return Stream.of("a1",

???????? "b2");

????}

}

@CsvSource

該注解允許我們將參數(shù)列表作為逗號分隔的值(即 CSV 字符串字面量)傳遞,每個 CSV 記錄都會導(dǎo)致執(zhí)行一次參數(shù)化測試。它還支持使用 useHeadersInDisplayName屬性跳過 CSV 標(biāo)頭。

@ParameterizedTest

@CsvSource({ "2, even",

"3, odd"})

void checkCsvSource(int number, String expected) {

????assertEquals(StringUtils.equals(expected, "even")

???? ? 0 : 1, number % 2);

}

@CsvFileSource

該注解允許我們使用類路徑或本地文件系統(tǒng)中的逗號分隔值(CSV)文件。與 @CsvSource 類似,每個 CSV 記錄都會導(dǎo)致執(zhí)行一次參數(shù)化測試。它還支持各種其他屬性 -numLinesToSkip、useHeadersInDisplayNamelineSeparator、delimiterString等。

示例 1: 基本實現(xiàn)

@ParameterizedTest

@CsvFileSource(

files = "src/test/resources/csv-file-source.csv",

numLinesToSkip = 1)

void checkCsvFileSource(int number, String expected) {

????assertEquals(StringUtils.equals(expected, "even")

???????????????? ? 0 : 1, number % 2);

}

src/test/resources/csv-file-source.csv

NUMBER, ODD_EVEN

2, even

3, odd

示例2:使用屬性

@ParameterizedTest

@CsvFileSource(

????files = "src/test/resources/csv-file-source_attributes.csv",

????delimiterString = "|",

????lineSeparator = "||",

????numLinesToSkip = 1)

void checkCsvFileSourceAttributes(int number, String expected) {

????assertEquals(StringUtils.equals(expected, "even")

? 0 : 1, number % 2);

}

src/test/resources/csv-file-source_attributes.csv

|| NUMBER | ODD_EVEN ||

|| 2 ???? | even ||

|| 3 ???? | odd???? ||

@EnumSource

該注解提供了一種方便的方法來使用枚舉常量作為測試用例參數(shù)。支持的屬性包括:

  • value - 枚舉類類型,例如 ChronoUnit.class

package java.time.temporal;

public enum ChronoUnit implements TemporalUnit {

????SECONDS("Seconds", Duration.ofSeconds(1)),

????MINUTES("Minutes", Duration.ofSeconds(60)),

HOURS("Hours", Duration.ofSeconds(3600)),

????DAYS("Days", Duration.ofSeconds(86400)),

????//12 other units

}

?ChronoUnit 是一個包含標(biāo)準(zhǔn)日期周期單位的枚舉類型。

@ParameterizedTest

@EnumSource(ChronoUnit.class)

void checkEnumSourceValue(ChronoUnit unit) {

assertNotNull(unit);

}

在此示例中,@EnumSource 將傳遞所有16個 ChronoUnit 枚舉值作為參數(shù)。

  • names - 枚舉常量的名稱或選擇名稱的正則表達式,例如 DAYS 或 ^.*DAYS$

@ParameterizedTest

@EnumSource(names = { "DAYS", "HOURS" })

void checkEnumSourceNames(ChronoUnit unit) {

????assertNotNull(unit);

}

@ArgumentsSource

該注解提供了一個自定義的可重用ArgumentsProvider。ArgumentsProvider的實現(xiàn)必須是外部類或靜態(tài)嵌套類。

  • 外部參數(shù)提供程序

public class ArgumentsSourceTest {

????@ParameterizedTest

????@ArgumentsSource(ExternalArgumentsProvider.class)

????void checkExternalArgumentsSource(int number, String expected) {

????????assertEquals(StringUtils.equals(expected, "even")

????????????????????? 0 : 1, number % 2,

????????????????????"Supplied number " + number +

????????????????????" is not an " + expected + " number");

????}

}

public class ExternalArgumentsProvider implements ArgumentsProvider {

????@Override

????public Stream<? extends Arguments> provideArguments(

????????ExtensionContext context) throws Exception {

????????return Stream.of(Arguments.of(2, "even"),

???????????? Arguments.of(3, "odd"));

????}

}

  • 靜態(tài)嵌套參數(shù)提供程序

public class ArgumentsSourceTest {

????@ParameterizedTest

????@ArgumentsSource(NestedArgumentsProvider.class)

????void checkNestedArgumentsSource(int number, String expected) {

????????assertEquals(StringUtils.equals(expected, "even")

? 0 : 1, number % 2,

???????????? ????"Supplied number " + number +

????????????????????" is not an " + expected + " number");

????}

????static class NestedArgumentsProvider implements ArgumentsProvider {

????????@Override

????????public Stream<? extends Arguments> provideArguments(

????????????ExtensionContext context) throws Exception {

????????????return Stream.of(Arguments.of(2, "even"),

???? Arguments.of(3, "odd"));

????????}

????}

}

參數(shù)轉(zhuǎn)換

首先,想象一下如果沒有參數(shù)轉(zhuǎn)換,我們將不得不自己處理參數(shù)數(shù)據(jù)類型的問題。?

源方法: Calculator?

public int sum(int a, int b) {

????return a + b;

}

測試用例:

@ParameterizedTest

@CsvSource({ "10, 5, 15" })

void calculateSum(String num1, String num2, String expected) {

????int actual = calculator.sum(Integer.parseInt(num1),

????????????????????????????????Integer.parseInt(num2));

????assertEquals(Integer.parseInt(expected), actual);

}

如果我們有String參數(shù),而我們正在測試的源方法接受Integers,則在調(diào)用源方法之前,我們需要負責(zé)進行此轉(zhuǎn)換。

JUnit5 提供了不同的參數(shù)轉(zhuǎn)換方式

  • 擴展原始類型轉(zhuǎn)換

@ParameterizedTest

@ValueSource(ints = { 2, 4 })

void checkWideningArgumentConversion(long number) {

????assertEquals(0, number % 2);

}

使用?@ValueSource(ints = { 1, 2, 3 }) 進行參數(shù)化測試時,可以聲明接受 int、long、float 或 double 類型的參數(shù)。

  • 隱式轉(zhuǎn)換

@ParameterizedTest

@ValueSource(strings = "DAYS")

void checkImplicitArgumentConversion(ChronoUnit argument) {

????assertNotNull(argument.name());

}

JUnit5提供了幾個內(nèi)置的隱式類型轉(zhuǎn)換器。轉(zhuǎn)換取決于聲明的方法參數(shù)類型。例如,用@ValueSource(strings = "DAYS")注釋的參數(shù)化測試會隱式轉(zhuǎn)換為類型ChronoUnit的參數(shù)。

  • 回退字符串到對象的轉(zhuǎn)換

@ParameterizedTest

@ValueSource(strings = { "Name1", "Name2" })

void checkImplicitFallbackArgumentConversion(Person person) {

????assertNotNull(person.getName());

}

public class Person {

????private String name;

????public Person(String name) {

????????this.name = name;

????}

????//Getters & Setters

}

JUnit5提供了一個回退機制,用于自動將字符串轉(zhuǎn)換為給定目標(biāo)類型,如果目標(biāo)類型聲明了一個適用的工廠方法或工廠構(gòu)造函數(shù)。例如,用@ValueSource(strings = { "Name1", "Name2" })注釋的參數(shù)化測試可以聲明接受一個類型為Person的參數(shù),其中包含一個名為name且類型為string的單個字段。

  • 顯式轉(zhuǎn)換

@ParameterizedTest

@ValueSource(ints = { 100 })

void checkExplicitArgumentConversion(

????@ConvertWith(StringSimpleArgumentConverter.class) String argument) {

????assertEquals("100", argument);

}

public class StringSimpleArgumentConverter extends SimpleArgumentConverter {

????@Override

????protected Object convert(Object source, Class<?> targetType)

????????throws ArgumentConversionException {

????????return String.valueOf(source);

????}

}

如果由于某種原因,您不想使用隱式參數(shù)轉(zhuǎn)換,則可以使用@ConvertWith注釋來定義自己的參數(shù)轉(zhuǎn)換器。例如,用@ValueSource(ints = { 100 })注釋的參數(shù)化測試可以聲明接受一個類型為String的參數(shù),使用StringSimpleArgumentConverter.class將整數(shù)轉(zhuǎn)換為字符串類型。

參數(shù)聚合

@ArgumentsAccessor

默認(rèn)情況下,提供給@ParameterizedTest方法的每個參數(shù)對應(yīng)于一個方法參數(shù)。因此,當(dāng)提供大量參數(shù)的參數(shù)源可以導(dǎo)致大型方法簽名時,我們可以使用ArgumentsAccessor而不是聲明多個參數(shù)。類型轉(zhuǎn)換支持如上面的隱式轉(zhuǎn)換所述。

@ParameterizedTest

@CsvSource({ "John, 20",

???????? "Harry, 30" })

void checkArgumentsAccessor(ArgumentsAccessor arguments) {

????Person person = new Person(arguments.getString(0),

???????????????????????????? arguments.getInteger(1));

????assertTrue(person.getAge() > 19, person.getName() + " is a teenager");

}

自定義聚合器

我們看到ArgumentsAccessor可以直接訪問@ParameterizedTest方法的參數(shù)。如果我們想在多個測試中聲明相同的ArgumentsAccessor怎么辦?JUnit5通過提供自定義可重用的聚合器來解決此問題。

  • @AggregateWith

@ParameterizedTest

@CsvSource({ "John, 20",

???????????? "Harry, 30" })

void checkArgumentsAggregator(

????@AggregateWith(PersonArgumentsAggregator.class) Person person) {

????assertTrue(person.getAge() > 19, person.getName() + " is a teenager");

}

public class PersonArgumentsAggregator implements ArgumentsAggregator {

????@Override

????public Object aggregateArguments(ArgumentsAccessor arguments,

????????ParameterContext context) throws ArgumentsAggregationException {

????????return new Person(arguments.getString(0),

arguments.getInteger(1));

????}

}

實現(xiàn)ArgumentsAggregator接口并通過@AggregateWith注釋在@ParameterizedTest方法中注冊它。當(dāng)我們執(zhí)行測試時,它會將聚合結(jié)果作為對應(yīng)測試的參數(shù)提供。ArgumentsAggregator的實現(xiàn)可以是外部類或靜態(tài)嵌套類。

額外福利

由于您已經(jīng)閱讀完文章,我想給您一個額外的福利 - 如果您正在使用像Fluent assertions for java這樣的斷言框架,則可以將java.util.function.Consumer作為參數(shù)傳遞,其中包含斷言本身。

@ParameterizedTest

@MethodSource("checkNumberArgs")

void checkNumber(int number, Consumer<Integer> consumer) {

????consumer.accept(number);????

}

static Stream<Arguments> checkNumberArgs() {????

????Consumer<Integer> evenConsumer =

????????????i -> Assertions.assertThat(i % 2).isZero();

????Consumer<Integer> oddConsumer =

????????????i -> Assertions.assertThat(i % 2).isEqualTo(1);

????return Stream.of(Arguments.of(2, evenConsumer),

???????? Arguments.of(3, oddConsumer));

}

總結(jié)

JUnit5的參數(shù)化測試功能通過消除重復(fù)測試用例的需要,提供多次使用不同輸入運行相同測試的能力,實現(xiàn)了高效的測試。這不僅為開發(fā)團隊節(jié)省了時間和精力,而且還增加了測試過程的覆蓋范圍和有效性。此外,該功能允許對源代碼進行更全面的測試,因為可以使用更廣泛的輸入進行測試,從而增加了識別任何潛在的錯誤或問題的機會??傮w而言,JUnit5的參數(shù)化測試是提高代碼質(zhì)量和可靠性的有價值的工具。

【注】本文譯自: JUnit 5 Parameterized Tests (reflectoring.io)


JUnit 5 參數(shù)化測試的評論 (共 條)

分享到微博請遵守國家法律
民县| 温宿县| 津市市| 高安市| 南乐县| 海门市| 清徐县| 阿拉善右旗| 山西省| 安丘市| 翼城县| 诏安县| 临汾市| 从江县| 舟山市| 手机| 上蔡县| 乌兰察布市| 微山县| 新闻| 扶沟县| 盐亭县| 呼玛县| 长武县| 玉环县| 嘉善县| 梁河县| 东宁县| 德钦县| 西乌珠穆沁旗| 商洛市| 辽宁省| 石楼县| 新泰市| 丽江市| 沙河市| 桐乡市| 大同市| 正宁县| 顺平县| 繁峙县|