Welcome to my GitHub

Github.com/zq2599/blog…

Content: all original article classification summary and supporting source code, involving Java, Docker, Kubernetes, DevOPS, etc.;

About the JUnit5 learning series

JUnit5 learning is a series of eight articles designed to improve unit testing skills in a SpringBoot environment. The links are as follows:

  1. Basic operation
  2. Assumptions class
  3. Assertions class
  4. Conditionality
  5. Tags and custom annotations
  6. The basis for Parameterized Tests
  7. Parameterized Tests are advanced
  8. Comprehensive Progression (Final)

This paper gives an overview of

  • JUnit5 Parameterized Tests can be used to control the performance of JUnit5 Parameterized Tests. You can use a variety of data sources to control the performance of JUnit5 Parameterized Tests. You can use a variety of data sources to control the performance of Tests.
  • This paper consists of the following chapters:
  1. Custom data source
  2. Parameter transformation
  3. Multi-field aggregation
  4. Multi-field to object
  5. Test execution name custom

Download the source code

  1. If you don’t want to code, you can download all the source code at GitHub, with the following address and link information:
The name of the link note
Project home page Github.com/zq2599/blog… The project’s home page on GitHub
Git repository address (HTTPS) Github.com/zq2599/blog… The project source warehouse address, HTTPS protocol
Git repository address (SSH) [email protected]:zq2599/blog_demos.git The project source warehouse address, SSH protocol
  1. The git project has multiple folders, and the application of this chapter is in the junitPractice folder, as shown in the red box below:

  1. Junitpractice is a parent-child project. This code is in the Parameterized sub-project, as shown below:

Custom data source

  1. The previous article used a variety of data sources. If you are not satisfied with their limitations and want to do a more thorough personalization, you can develop an implementation class for the ArgumentsProvider interface and specify it using @ArgumentsSource.
  2. For example, we will first develop the ArgumentsProvider implementation class myentsProvider.java:
package com.bolingcavalry.parameterized.service.impl;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import java.util.stream.Stream;

public class MyArgumentsProvider implements ArgumentsProvider {

    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
        return Stream.of("apple4"."banana4").map(Arguments::of); }}Copy the code
  1. Add @ArgumentsSource to the test method and specify MyArgumentsProvider:
    @Order(15)
    @DisplayName("ArgumentsProvider interface implementation class provides data as input arguments ")
    @ParameterizedTest
    @ArgumentsSource(MyArgumentsProvider.class)
    void argumentsSourceTest(String candidate) {
        log.info("argumentsSourceTest [{}]", candidate);
    }
Copy the code
  1. The result is as follows:

Parameter transformation

  1. Do parameterized test data sources and test method inputs have to be of the same data type? JUnit5 is not strictly required, and in fact JUnit5 can do some automatic or manual type conversions;
  2. The data source is an array of type int, but the test method takes an input parameter double:
    @Order(16)
    @displayName ("int automatically converted to double ")
    @ParameterizedTest
    @valuesource (ints = {1,2,3})
    void argumentConversionTest(double candidate) {
        log.info("argumentConversionTest [{}]", candidate);
    }
Copy the code
  1. For the design of int Conversion, make it double for the test method.

  1. Can also specify converters to converter the logic of the transformation, the example below is a string to a LocalDate type, the key is @ JavaTimeConversionPattern:
    @Order(17)
    @displayName (" String, specified converter, converted to LocalDate input ")
    @ParameterizedTest
    @valuesource (strings = {"01.01.2017", "31.12.2017"})
    void argumentConversionWithConverterTest(
            @JavaTimeConversionPattern("dd.MM.yyyy") LocalDate candidate) {
        log.info("argumentConversionWithConverterTest [{}]", candidate);
    }
Copy the code
  1. The result is as follows:

Argument Aggregation

  1. Consider this: If each record in the data source has multiple fields, how can a test method use these fields?
  2. Looking back at the @csvSource example, you can see that the test method uses two input parameters corresponding to two fields of each CSV record, as shown below:

3. This is fine for a small number of fields, but if the CSV record has many fields, wouldn’t the test method have to define a large number of input parameters? This is obviously not appropriate, so consider JUnit5’s Argument Aggregation feature, which puts all the fields from each CSV record into an object of type ArgumentsAccessor. ArgumentsAccessor = ArgumentsAccessor = ArgumentsAccessor = ArgumentsAccessor = ArgumentsAccessor = ArgumentsAccessor

ArgumentsAccessor provides a variety of methods to get data from ArgumentsAccessor instances.

  1. In the following example code, each record of the CSV data source has three fields, while the test method has only one input argument of type ArgumentsAccessor. Inside the test method, the different fields of the CSV record can be obtained using the getString, GET, and other methods of ArgumentsAccessor. For example, arguments.getString(0) gets the first field and returns the string Type, while arguments.get(2, types.class) gets the second field and converts it to Type.
    @Order(18)
    @displayName (" Multiple fields of CsvSource are aggregated into ArgumentsAccessor instances ")
    @ParameterizedTest
    @CsvSource({ "Jane1, Doe1, BIG", "John1, Doe1, SMALL" })
    void argumentsAccessorTest(ArgumentsAccessor arguments) {
        Person person = new Person();
        person.setFirstName(arguments.getString(0));
        person.setLastName(arguments.getString(1));
        person.setType(arguments.get(2, Types.class));

        log.info("argumentsAccessorTest [{}]", person);
    }
Copy the code
  1. ArgumentsAccessor can obtain all the fields of the CSV data:

More elegant aggregation

  1. The previous aggregation solves the problem of getting multiple fields of CSV data, but there are still flaws: The code to generate the Person instance from ArgumentsAccessor is written in the test method, as shown in the red box below. The test method should only have unit test logic, but the code to create the Person instance is clearly not appropriate here:

2. For the above problems, JUnit5 also provides solutions: Specify an annotation to a converter from ArgumentsAccessor to Person, as shown in the following example. You can see that the input to the test method has an annotation @aggregateWith, The value personAggregator. class is the converter from ArgumentsAccessor to Person, and the entry argument has been changed to Person from ArgumentsAccessor:

    @Order(19)
    @displayName (" Multiple fields of CsvSource converted to Person instance by specifying aggregate class ")
    @ParameterizedTest
    @CsvSource({ "Jane2, Doe2, SMALL", "John2, Doe2, UNKNOWN" })
    void customAggregatorTest(@AggregateWith(PersonAggregator.class) Person person) {
        log.info("customAggregatorTest [{}]", person);
    }
Copy the code
  1. PersonAggregator is a transformer class that implements the ArgumentsAggregator interface. The code is simple: Get the fields from the ArgumentsAccessor example to create a Person object:
package com.bolingcavalry.parameterized.service.impl;

import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
import org.junit.jupiter.params.aggregator.ArgumentsAggregationException;
import org.junit.jupiter.params.aggregator.ArgumentsAggregator;

public class PersonAggregator implements ArgumentsAggregator {

    @Override
    public Object aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) throws ArgumentsAggregationException {

        Person person = new Person();
        person.setFirstName(arguments.getString(0));
        person.setLastName(arguments.getString(1));
        person.setType(arguments.get(2, Types.class));

        returnperson; }}Copy the code
  1. The execution results of the above test methods are as follows:

Further simplification

  1. If you recall that JUnit5 supports custom annotations, let’s simplify the code in the red box:

Class csvtOperson.java. Add the @aggregateWith (PersonAggregator.class) from the red box.

package com.bolingcavalry.parameterized.service.impl;

import org.junit.jupiter.params.aggregator.AggregateWith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AggregateWith(PersonAggregator.class)
public @interface CsvToPerson {
}
Copy the code
  1. To see how the code in the red box above can be simplified, use @csvtoperson to turn ArgumentsAccessor into a Person object:
    @Order(20)
    @displayName (" Multiple fields of CsvSource, converted to Person instance by specifying aggregate class (custom annotation)")
    @ParameterizedTest
    @CsvSource({ "Jane3, Doe3, BIG", "John3, Doe3, UNKNOWN" })
    void customAggregatorAnnotationTest(@CsvToPerson Person person) {
        log.info("customAggregatorAnnotationTest [{}]", person);
    }
Copy the code
  1. The result is the same as @aggregateWith (personAggregate.class) :

Test execution name custom

  1. At the end of the article, let’s take a look at a light knowledge point, as shown in the red box below. Each time the test method is executed, IDEA will display the serial number and parameter values of this execution:

  1. ParameterizedTest (@parameterizedTest, @parameterizedTest, @parameterizedTest, @parameterizedTest, @parameterizedTest)
    @Order(21)
    @displayname ("CSV format multiple record input parameter (custom DisplayName)")
    @parameterizedTest (name = "fruit parameter [{0}], rank parameter [{1}]")
    @CsvSource({ "apple3, 31", "banana3, 32", "'lemon3, lime3', 0x3A" })
    void csvSourceWithCustomDisplayNameTest(String fruit, int rank) {
        log.info("csvSourceWithCustomDisplayNameTest, fruit [{}], rank [{}]", fruit, rank);
    }
Copy the code
  1. The result is as follows:

  • By now, we have learned and practiced the Parameterized knowledge of JUnit5. With such a powerful parameter input technology, we can further improve the code coverage and scene scope of unit test.

You are not alone, Xinchen original accompany all the way

  1. Java series
  2. Spring series
  3. The Docker series
  4. Kubernetes series
  5. Database + middleware series
  6. The conversation series

Welcome to pay attention to the public number: programmer Xin Chen

Wechat search “programmer Xin Chen”, I am Xin Chen, looking forward to enjoying the Java world with you…

Github.com/zq2599/blog…