Junit5中實現參數化測試


從Junit5開始,對參數化測試支持進行了大幅度的改進和提升。下面我們就一起來詳細看看Junit5參數化測試的方法。

部署和依賴

和Junit4相比,Junit5框架更多在向測試平台演進。其核心組成也從以前的一個Junit的jar包更換成由多個模塊組成。本文所需要依賴模塊如下:

  • junit-jupiter-engine: Junit的核心測試引擎
  • junit-jupiter-params: 編寫參數化測試所需要的依賴包
  • junit-platform-launcher: 從IDE(InteliJ/Eclipses)等運行時所需要的啟動器

另外,為了從Maven命令行工具中運行Juint,還需要junit-platform-surefire-provider包的依賴。

maven的pom.xml文件中添加如下來進行安裝

<dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.2.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>5.2.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-launcher</artifactId> <version>1.2.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22</version> </plugin> </plugins> </build> 

Junit5 參數源詳解

value source

value source是最簡單的參數源,通過注解可以直接指定攜帶的運行參數。

  • String values: @ValueSource(strings = {“foo”, “bar”, “baz”})
  • Double values: @ValueSource(doubles = {1.5D, 2.2D, 3.0D})
  • Long values: @ValueSource(longs = {2L, 4L, 8L})
  • Integer values: @ValueSource(ints = {2, 4, 8})

示例代碼如下:

import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; public class ValueSourcesExampleTest { @ParameterizedTest @ValueSource(ints = {2, 4, 8}) void testNumberShouldBeEven(int num) { assertEquals(0, num % 2); } @ParameterizedTest @ValueSource(strings = {"Radar", "Rotor", "Tenet", "Madam", "Racecar"}) void testStringShouldBePalindrome(String word) { assertEquals(isPalindrome(word), true); } @ParameterizedTest @ValueSource(doubles = {2.D, 4.D, 8.D}) void testDoubleNumberBeEven(double num) { assertEquals(0, num % 2); } boolean isPalindrome(String word) { return word.toLowerCase().equals(new StringBuffer(word.toLowerCase()).reverse().toString()); } } 

輸出

[INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running qiucao.learning.ParaTest [INFO] Tests run: 11, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.155 s - in qiucao.learning.ParaTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 11, Failures: 0, Errors: 0, Skipped: 0 

Enum Source

枚舉參數源,允許我們通過將參數值由給定Enum枚舉類型傳入。並可以通過制定約束條件或正則匹配來篩選傳入參數

import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.EnumSet; import java.util.concurrent.TimeUnit; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.EnumSource.Mode; public class EnumSourcesExampleTest { @ParameterizedTest(name = "[{index}] TimeUnit: {arguments}") @EnumSource(TimeUnit.class) void testTimeUnitMinimumNanos(TimeUnit unit) { assertTrue(unit.toMillis(2000000L) > 1); } @ParameterizedTest @EnumSource(value = TimeUnit.class, names = {"SECONDS", "MINUTES"}) void testTimeUnitJustSecondsAndMinutes(TimeUnit unit) { assertTrue(EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES).contains(unit)); assertFalse(EnumSet .of(TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MILLISECONDS, TimeUnit.NANOSECONDS, TimeUnit.MICROSECONDS).contains(unit)); } @ParameterizedTest @EnumSource(value = TimeUnit.class, mode = Mode.EXCLUDE, names = {"SECONDS", "MINUTES"}) void testTimeUnitExcludingSecondsAndMinutes(TimeUnit unit) { assertFalse(EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES).contains(unit)); assertTrue(EnumSet .of(TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MILLISECONDS, TimeUnit.NANOSECONDS, TimeUnit.MICROSECONDS).contains(unit)); } @ParameterizedTest @EnumSource(value = TimeUnit.class, mode = Mode.MATCH_ALL, names = ".*SECONDS") void testTimeUnitIncludingAllTypesOfSecond(TimeUnit unit) { assertFalse(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MINUTES).contains(unit)); assertTrue(EnumSet .of(TimeUnit.SECONDS, TimeUnit.MILLISECONDS, TimeUnit.NANOSECONDS, TimeUnit.MICROSECONDS).contains(unit)); } } 

輸出:

[INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running qiucao.learning.ParaTest [INFO] Tests run: 18, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.206 s - in qiucao.learning.ParaTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 18, Failures: 0, Errors: 0, Skipped: 0 

Method Source

通過其他的Java方法函數來作為參數源。引用的方法返回值必須是Stream, Iterator 或者Iterable.

import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; public class MethodSourceExampleTest { @ParameterizedTest @MethodSource("stringGenerator") void shouldNotBeNullString(String arg){ assertNotNull(arg); } @ParameterizedTest @MethodSource("intGenerator") void shouldBeNumberWithinRange(int arg){ assertAll( () -> assertTrue(arg > 0), () -> assertTrue(arg <= 10) ); } @ParameterizedTest(name = "[{index}] user with id: {0} and name: {1}") @MethodSource("userGenerator") void shouldUserWithIdAndName(long id, String name){ assertNotNull(id); assertNotNull(name); } static Stream<String> stringGenerator(){ return Stream.of("hello", "world", "let's", "test"); } static IntStream intGenerator() { return IntStream.range(1,10); } static Stream<Arguments> userGenerator(){ return Stream.of(Arguments.of(1L, "Sally"), Arguments.of(2L, "Terry"), Arguments.of(3L, "Fred")); } } 

輸出:

[INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running qiucao.learning.ParaTest [INFO] Tests run: 16, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.191 s - in qiucao.learning.ParaTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 16, Failures: 0, Errors: 0, Skipped: 0 

Argument Source

通過參數類來作為參數源。這里引用的類必須實現ArgumentsProvider接口。示例如下:

import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; public class ArgumentsSourceExampleTest { @ParameterizedTest @ArgumentsSource(CustomArgumentsGenerator.class) void testGeneratedArguments(double number) throws Exception { assertFalse(number == 0.D); assertTrue(number > 0); assertTrue(number < 1); } static class CustomArgumentsGenerator implements ArgumentsProvider { @Override public Stream<? extends Arguments> provideArguments(ExtensionContext context) { return Stream.of(Math.random(), Math.random(), Math.random(), Math.random(), Math.random()) .map(Arguments::of); } } } 

CSV Source

通過指定csv格式(comma-separated-values)的注解作為參數源

import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; public class CsvSourceExampleTest { Map<Long, String> idToUsername = new HashMap<>(); { idToUsername.put(1L, "Selma"); idToUsername.put(2L, "Lisa"); idToUsername.put(3L, "Tim"); } @ParameterizedTest @CsvSource({"1,Selma", "2,Lisa", "3,Tim"}) void testUsersFromCsv(long id, String name) { assertTrue(idToUsername.containsKey(id)); assertTrue(idToUsername.get(id).equals(name)); } } 

輸出:

[INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running qiucao.learning.ParaTest [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.164 s - in qiucao.learning.ParaTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 

CSV File Source

除了使用csv參數源,這里也支持使用csv文件作為參數源

假設users.csv 文件包含如下csv格式的數據

1,Selma
2,Lisa
3,Tim

代碼示例如下

import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvFileSource; import org.junit.jupiter.params.provider.CsvSource; public class CsvFileSourceExampleTest { Map<Long, String> idToUsername = new HashMap<>(); { idToUsername.put(1L, "Selma"); idToUsername.put(2L, "Lisa"); idToUsername.put(3L, "Tim"); } @ParameterizedTest @CsvFileSource(resources = "/users.csv") void testUsersFromCsv(long id, String name) { assertTrue(idToUsername.containsKey(id)); assertTrue(idToUsername.get(id).equals(name)); } } 

輸出:

[INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running qiucao.learning.ParaTest [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.199 s - in qiucao.learning.ParaTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 

參數轉換

JUnit allows us to convert arguments to the target format we need in our tests.

There are two possible conversion types:

隱式轉換

JUnit提供了很對內建的格式轉化支持,特別是string和常用的數據類型

以下是支持和string型進行轉換的類型

Boolean
Byte
Character
Short
Integer
Long
Float
Double
Enum subclass
Instant
LocalDate
LocalDateTime
LocalTime
OffsetTime
OffsetDateTime
Year
YearMonth
ZonedDateTime

顯式轉換

Junit5中可以使用@ConvertWith(MyConverter.class) 注解來實現 SimpleArgumentConverter.

代碼示例:


 
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDate; import java.time.Month; import java.util.UUID; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.SimpleArgumentConverter; import org.junit.jupiter.params.provider.ValueSource; public class ArgumentsConversionExampleTest { @ParameterizedTest @ValueSource(strings = "2017-07-11") void testImplicitArgumentConversion(LocalDate date) throws Exception { assertTrue(date.getYear() == 2017); assertTrue(date.getMonth().equals(Month.JULY)); assertTrue(date.getDayOfMonth() == 11); } @ParameterizedTest @ValueSource(strings = "B4627B3B-ACC4-44F6-A2EB-FCC94DAB79A5") void testImplicitArgumentConversion(@ConvertWith(ToUUIDArgumentConverter.class) UUID uuid) throws Exception { assertNotNull(uuid); assertTrue(uuid.getLeastSignificantBits() == -6706989278516512347L); } static class ToUUIDArgumentConverter extends SimpleArgumentConverter { @Override protected Object convert(Object source, Class<?> targetType) { assertEquals(UUID.class, targetType, "may only convert to UUID"); return UUID.fromString(String.valueOf(source)); } } } 

輸出:

[INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running qiucao.learning.ParaTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.487 s - in qiucao.learning.ParaTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 

補充對照: JUnit 4中參數化測試方法

代碼如下:

@RunWith(Parameterized.class) public class ParameterizedTest { @Parameters(name = "Run #{index}: {0}^2={1}") public static Iterable<Object[]> data() { return Arrays.asList(new Object[][] { { 1, 1 }, { 2, 4 }, { 3, 9 }, { 4, 16 }, { 5, 25 } }); } private final int input; private final int resultExpected; public ParameterizedTest(final int input, final int result) { this.input = input; this.resultExpected = result; } @Test public void testUserMapping() { Calculator calc = new Calculator(); assertEquals(resultExpected, calc.square(input)); } } 


作者:城下秋草
鏈接:https://www.jianshu.com/p/477f2ded7ccc
來源:簡書


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM