当前位置:网站首页>Parameterized tests guide in junit5
Parameterized tests guide in junit5
2022-06-24 15:38:00 【Exclusive rainy days】
As a new generation testing framework ,Junit5 There are a lot of favorite test programs in , I think the most outstanding thing is to be able to Parametric testing (Parameterized Tests).
brief introduction
Usually , That's what happens , Same test case , The only change is that the parameters entered during the test are different . Follow the previous practice , It may be that a test is written for each input parameter , Or you can wrap the test parameters into a collection and iterate through the execution of the test . In the new edition of Junit5 in , Has provided a more elegant way to .
This feature allows us to : This feature allows us to run a single test multiple times , And make every run just different parameters .
Installation dependency
In order to use JUnit 5 Parameterized testing of (parameterized tests). Need to be in Junit Platform On the basis of , Imported junit-jupiter-params Rack bag .
maven:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
Gradle:
testCompile("org.junit.jupiter:junit-jupiter-params:5.4.2")
A simple case
such as , You need to test a function to determine whether the input value is cardinal .
public class Numbers {
public static boolean isOdd(int number) {
return number % 2 != 0;
}
}
adopt Parameterized Test, It can be written in the following form :
@ParameterizedTest
@ValueSource(ints = {
1, 3, 5, -3, 15, Integer.MAX_VALUE}) // six numbers
void isOdd_ShouldReturnTrueForOddNumbers(int number) {
assertTrue(Numbers.isOdd(number));
}
JUnit5 The above tests will be performed 6 Time , They will be assigned every time *@ValueSource* Different from int Parameters .
How to define the source of different parameters
The above simply shows how to run the same test with different parameters . however Many times it's not just Int type .
Simple values Simple Value
@ValueSource You can pass a data or iterator to the test method . The simple parameters that can be supported are as follows :
- short (with the shorts attribute)
- byte (with the bytes attribute)
- int (with the ints attribute)
- long (with the longs attribute)
- float (with the floats attribute)
- double (with the doubles attribute)
- char (with the chars attribute)
- java.lang.String (with the strings attribute)
- java.lang.Class (with the classes attribute)
It is worth noting that ,@ValueSource Incoming... Is not allowed Null Values and Empty value . from JUnit 5.4 Start , We can use @NullSource、@EmptySource and @NullAndEmptySource Annotations can separate individual null value 、 Single Empty and Null and Empty Pass to the parameterized test method .
Enumeration class Enum
To run, pass all the values of an enumerated class into the test , have access to @EnumSource annotation . For example, use enumeration classes Month
@ParameterizedTest
@EnumSource(Month.class) // passing all 12 months
void getValueForAMonth_IsAlwaysBetweenOneAndTwelve(Month month) {
int monthNumber = month.getValue();
assertTrue(monthNumber >= 1 && monthNumber <= 12);
}
Or by names Some enumeration classes can be filtered out
@ParameterizedTest
@EnumSource(value = Month.class, names = {
"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"})
void someMonths_Are30DaysLong(Month month) {
final boolean isALeapYear = false;
assertEquals(30, month.length(isALeapYear));
}
usually , Will be considered names Is to operate on the enumeration class matching these names , But by mode=EXCLUDE Property can be set to negate .
@ParameterizedTest
@EnumSource(
value = Month.class,
names = {
"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER", "FEBRUARY"},
mode = EnumSource.Mode.EXCLUDE)
void exceptFourMonths_OthersAre31DaysLong(Month month) {
final boolean isALeapYear = false;
assertEquals(31, month.length(isALeapYear));
}
in addition , It can also be done through names Property to manipulate the string of these iterations .
@ParameterizedTest
@EnumSource(value = Month.class, names = ".+BER", mode = EnumSource.Mode.MATCH_ANY)
void fourMonths_AreEndingWithBer(Month month) {
EnumSet<Month> months =
EnumSet.of(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER, Month.DECEMBER);
assertTrue(months.contains(month));
}
CSV( The most commonly used )
A lot of times , At the same time Pass in the parameter and Expected results , To verify the test logic . such as , Need to test toUpperCase() Method ( Be able to bring the expected String Convert the string to the expected uppercase string ).
@ParameterizedTest
@CsvSource({
"test,TEST", "tEst,TEST", "Java,JAVA"})
void toUpperCase_ShouldGenerateTheExpectedUppercaseValue(String input, String expected) {
String actualValue = input.toUpperCase();
assertEquals(expected, actualValue);
}
among @CsvSource The annotation accepts a comma separated array , And each array item corresponds to CSV A line in a document . The annotation contains a delimiter attribute , Can be used to define the delimiter ( The default is comma ).
CSV Files
Same as the one in front CSV equally , Just write the parameters to the specific CSV Files are stored . adopt @CsvFileSource Note the file path .
@ParameterizedTest
@CsvFileSource(resources = "/data.csv", numLinesToSkip = 1)
void toUpperCase_ShouldGenerateTheExpectedUppercaseValueCSVFile(String input, String expected) {
String actualValue = input.toUpperCase();
assertEquals(expected, actualValue);
}
Usually ,@CsvFileSource Annotations go back and parse each line , But sometimes , The first row might be the column name , So in the above method, we add numLinesToSkip Property to skip the first line .
Method Method
adopt @MethodSource Annotations can pass some Complex iteration objects To test .
@ParameterizedTest
@MethodSource("provideStringsForIsBlank")
void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input, boolean expected) {
assertEquals(expected, Strings.isBlank(input));
}
among @MethodSource Annotations need to match existing existing methods , This method is usually queried in the same test class , If it is not under the same test class file , You need to add the fully qualified name of the above Dharma name . For example, the following example
class StringsUnitTest {
@ParameterizedTest
@MethodSource("com.baeldung.parameterized.StringParams#blankStrings")
void isBlank_ShouldReturnTrueForNullOrBlankStringsExternalSource(String input) {
assertTrue(Strings.isBlank(input));
}
}
public class StringParams {
static Stream<String> blankStrings() {
return Stream.of(null, "", " ");
}
}
Custom parameter providers Custom Argument Provider
By implementing ArgumentsProvider Interfaces can use some more advanced ways to pass parameters . such as
class BlankStringsArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of((String) null),
Arguments.of(""),
Arguments.of(" ")
);
}
}
Then reference the above custom parameter provider in the actual test class .
@ParameterizedTest
@ArgumentsSource(BlankStringsArgumentsProvider.class)
void isBlank_ShouldReturnTrueForNullOrBlankStringsArgProvider(String input) {
assertTrue(Strings.isBlank(input));
}
How to customize annotations
The front ones are Junit Annotations provided by the parameterized parsing framework , You can also customize some parameter annotations , Parameterized tests are implemented in a more elegant way . such as , You want to implement an annotation that loads test parameters from static variables . Similar to the following code .
static Stream<Arguments> arguments = Stream.of(
Arguments.of(null, true), // null strings should be considered blank
Arguments.of("", true),
Arguments.of(" ", true),
Arguments.of("not blank", false)
);
@ParameterizedTest
@VariableSource("arguments")
void isBlank_ShouldReturnTrueForNullOrBlankStringsVariableSource(String input, boolean expected) {
assertEquals(expected, Strings.isBlank(input));
}
JUnit5 The following two base classes are provided to help us implement . One is used to realize Annotation details , One is used to provide Test parameters .
- AnnotationConsumer Base classes provide annotation details
- ArgumentsProvider The base class provides parameters for the test
The actual implementation is as follows :
class VariableArgumentsProvider implements ArgumentsProvider, AnnotationConsumer<VariableSource> {
private String variableName;
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return context.getTestClass()
.map(this::getField)
.map(this::getValue)
.orElseThrow(() -> new IllegalArgumentException("Failed to load test arguments"));
}
@Override
public void accept(VariableSource variableSource) {
variableName = variableSource.value();
}
private Field getField(Class<?> clazz) {
try {
return clazz.getDeclaredField(variableName);
} catch (Exception e) {
return null;
}
}
@SuppressWarnings("unchecked")
private Stream<Arguments> getValue(Field field) {
Object value = null;
try {
value = field.get(null);
} catch (Exception ignored) {
}
return value == null ? null : (Stream<Arguments>) value;
}
}
Parameter type conversion
Implicit conversion
Suppose that by @CsvSource Annotation to rewrite the previous @EmumTests test . stay @CSVSource Pass through Incoming string , Instead of enumerating classes .
@ParameterizedTest
@CsvSource({
"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"}) // Pssing strings
void someMonths_Are30DaysLongCsv(Month month) {
final boolean isALeapYear = false;
assertEquals(30, month.length(isALeapYear));
}
It's supposed to fail , But in practice, you will find , It can run normally .
because Junit5 The string is implicitly converted by default .String By default, it can be converted to the following types
- UUID
- Locale
- LocalDate, LocalTime, LocalDateTime, Year, Month, etc.
- File and Path
- URL and URI
- Enum subclasses
Explicit conversion
Sometimes , You need to provide a custom explicit parameter type converter . for example , I want to yyyy/mm/dd Format string data is converted to LocalDate example .
The first step is to realize ArgumentConverter Interface
class SlashyDateConverter implements ArgumentConverter {
@Override
public Object convert(Object source, ParameterContext context)
throws ArgumentConversionException {
if (!(source instanceof String)) {
throw new IllegalArgumentException("The argument should be a string: " + source);
}
try {
String[] parts = ((String) source).split("/");
int year = Integer.parseInt(parts[0]);
int month = Integer.parseInt(parts[1]);
int day = Integer.parseInt(parts[2]);
return LocalDate.of(year, month, day);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to convert", e);
}
}
}
And then through @ConvertWith Annotation to reference the specified converter .
@ParameterizedTest
@CsvSource({
"2018/12/25,2018", "2019/02/11,2019"})
void getYear_ShouldWorkAsExpected(
@ConvertWith(SlashyDateConverter.class) LocalDate date, int expected) {
assertEquals(expected, date.getYear());
}
Parameter accessor
Usually , A test parameter , Will correspond to a formal parameter . But when passing multiple parameters through a parameter source , Sometimes it will appear very big and chaotic . Now , It can be accessed through a parameter accessor ArgumentsAccessor To aggregate these parameters , And then in use , Get... According to the index . such as , Want to test the following Person Class fullName Method
class Person {
String firstName;
String middleName;
String lastName;
// constructor
public String fullName() {
if (middleName == null || middleName.trim().isEmpty()) {
return String.format("%s %s", firstName, lastName);
}
return String.format("%s %s %s", firstName, middleName, lastName);
}
}
If you want to test fullName Method , You need to pass in firstName, middleName, lastName, and the expected fullName.. We do not define different test parameters , But through ArgumentsAccessor To parse these test parameters .
@ParameterizedTest
@CsvSource({
"Isaac,,Newton,Isaac Newton", "Charles,Robert,Darwin,Charles Robert Darwin"})
void fullName_ShouldGenerateTheExpectedFullName(ArgumentsAccessor argumentsAccessor) {
String firstName = argumentsAccessor.getString(0);
String middleName = (String) argumentsAccessor.get(1);
String lastName = argumentsAccessor.get(2, String.class);
String expectedFullName = argumentsAccessor.getString(3);
Person person = new Person(firstName, middleName, lastName);
assertEquals(expectedFullName, person.fullName());
}
( Be reasonable , Don't see too much advantage ) The advantage is that all the parameters are aggregated and stored together , And it can be resolved by several methods defined below .
- getString(index) The specific value is parsed directly through the index , String .( The return type is String)
- get(index) Simply parse the index element into Object object
- get(index, type) Resolves the specified index element to an object of the specified type type
Parameter aggregator
Use the previous parameter accessor ArgumentsAccessor It may make the code less readable and reusable . You can customize one aggregator To achieve .
The first is to realize ArgumentsAggregator Interface
class PersonAggregator implements ArgumentsAggregator {
@Override
public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context)
throws ArgumentsAggregationException {
return new Person(accessor.getString(1), accessor.getString(2), accessor.getString(3));
}
}
And then by specifying @AggregateWith Annotation to reference
@ParameterizedTest
@CsvSource({
"Isaac Newton,Isaac,,Newton", "Charles Robert Darwin,Charles,Robert,Darwin"})
void fullName_ShouldGenerateTheExpectedFullName(
String expectedFullName,
@AggregateWith(PersonAggregator.class) Person person) {
assertEquals(expectedFullName, person.fullName());
}
above PersonAggregator In the use case , Converged the last 3 Parameters , And an example is given Person object .
Custom explicit name Customizing Display Names
By default , The explicit test name after the test run is as follows
├─ someMonths_Are30DaysLongCsv(Month)
│ │ ├─ [1] APRIL
│ │ ├─ [2] JUNE
│ │ ├─ [3] SEPTEMBER
│ │ └─ [4] NOVEMBER
But we can pass @ParameterizedTest In the annotations name Attribute Custom display name . for example
@ParameterizedTest(name = "{index} {0} is 30 days long")
@EnumSource(value = Month.class, names = {
"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"})
void someMonths_Are30DaysLong(Month month) {
final boolean isALeapYear = false;
assertEquals(30, month.length(isALeapYear));
}
According to the ’ April is 30 days long’ May be more ideographic .
Under the custom display name , You can use the following placeholders .
- {index} Indicates that the index is called , from 1 Start , then 2,3 etc.
- {arguments} Represents the complete parameter list , Separated by commas .
- **{0}, {1}, …****.* Represents the name of a single parameter .
summary
Junit5 It will become more and more popular , Please refer to the above related source code tutorials/testing-modules/junit5-annotations at master · eugenp/tutorials.
Reference documents
边栏推荐
- 在Gradle 中对Junit5 测试框架引用
- Monitoring and warning | is the website attacked?
- Poor remote code execution in Alien Swarm
- SF express: please sign for MySQL soul ten
- Is it safe to open an account for flush stock on mobile phone!
- How about stock online account opening and account opening process? Is it safe to open an account online?
- Istio FAQ: 431 request header fields too large
- 【Prometheus】5. Alertmanager alarm (incomplete)
- "Industry foresight" future development trend of intelligent security monitoring industry
- Istio practical skill: enable accesslog locally
猜你喜欢

CVPR 2022 - Interpretation of selected papers of meituan technical team

刚刚阿里面软件测试回来,3+1面任职阿里P7,年薪28*15薪

国产芯片的赶超,让美国手机芯片龙头高通害怕了,出招应对竞争

Jenkins 镜像无法更新插件中心的3种解决方法

Do you really know the difference between H5 and applet?
Step by step introduction to sqlsugar based development framework (9) -- Realizing field permission control with WinForm control
Record the range of data that MySQL update will lock

FreeRTOS新建任务不执行问题解决办法
![clang: warning: argument unused during compilation: ‘-no-pie‘ [-Wunused-command-line-argument]](/img/f0/42f394dbc989d381387c7b953d2a39.jpg)
clang: warning: argument unused during compilation: ‘-no-pie‘ [-Wunused-command-line-argument]

MongoDB入门实战教程:学习总结目录
随机推荐
Use list
In 2021, big companies often ask IOS interview questions -- runloop
This website teaches you to imitate more than 100 well-known websites!
【Prometheus】6. Prometheus and kubernetes (incomplete)
国产芯片的赶超,让美国手机芯片龙头高通害怕了,出招应对竞争
【我的OpenGL学习进阶之旅】OpenGL的坐标系的学习笔记
推荐几款超级实用的数据分析利器
Is it safe to open an account for stock speculation in the top ten securities app rankings in China
Do you really know the difference between H5 and applet?
证券账户理财安全吗??
Allwinner a40i industrial Internet gateway design scheme, smart site, smart city core gateway
Vim编辑器的最常用的用法
Logstash introduction and simple case
Golang implements BigInteger large number calculation
Redis highly available
Two way combination of business and technology to build a bank data security management system
Network engineers must know the network essence knowledge!
How to resolve the 35 year old crisis? Sharing of 20 years' technical experience of chief architect of Huawei cloud database
A full set of tutorials for interviewers from Android manufacturers teach you: prepare for the interview and get the offer smoothly!
Record the range of data that MySQL update will lock