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:
- Basic operation
- Assumptions class
- Assertions class
- Conditionality
- Tags and custom annotations
- The basis for Parameterized Tests
- Parameterized Tests are advanced
- Comprehensive Progression (Final)
This paper gives an overview of
- This article, the final installment of the JUnit5 learning series, puts some of the advanced features JUnit5 offers into action;
- There are so many features in JUnit5 that the JUnit5 learning series only covers the most common ones, not all of them.
- This paper consists of the following chapters:
- Version of the set
- The test method presents the name generator
- Repeat the test
- nested
- Dynamic Tests
- Multiple threads execute the test method concurrently
Download the source code
- 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 |
- The git project has multiple folders, and the application of this chapter is in the junitPractice folder, as shown in the red box below:
- Junitpractice is a parent-child project. This code is in the advanced sub-project, as shown below:
Version of the set
- The JUnit5 Learning code uses the SpringBoot: 2.3.4.RELEASE framework, which indirectly relies on JUnit version 5.6.2;
- Two features of this article that require JUnit version 5.7 or higher are the test method presentation name generator and dynamically generated test methods;
- For projects using the SpringBoot: 2.3.4.RELEASE framework, if you want to specify the JUnit version, you need to do the following three steps:
- DependencyManagement Add junit-BOM to the node and specify the version number:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.7.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Copy the code
- Exclude the indirect dependencies of spring-boot-starter-test and Junit-Jupiter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</exclusion>
</exclusions>
</dependency>
Copy the code
- Add the junit-Jupiter dependency using the version number specified in dependencyManagement:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
Copy the code
- Update to see version 5.7.0 already used:
- Version of the problem solved, then formally entered the advanced combat;
Display Name Generators Display Name Generators
- Translate the Display Name Generators into test method show Name generator, may refresh the reader to the writer’s cognitive level of English please include more…
- To review how to specify the DisplayName of the test method, if the test method uses @displayname, the string specified by @displayname will be displayed when the unit test execution results are displayed, as shown below:
3. In addition to specifying display names with @displayName, JUnit5 also provides a way to automatically generate display names: @DisplayNameGeneration, to see how it generates display names; 4. The demo code looks like this, which replaces all underscores of method names with Spaces when the value of @DisplayNameGeneration is set to ReplaceUnderscores:
package com.bolingcavalry.advanced.service.impl;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
public class ReplaceUnderscoresTest {
@Test
void if_it_is_zero(a) {}}Copy the code
- If_it_is_zero displays the name if it is zero:
6. In addition to the above substitution, JUnit5 provides another way to generate display names: Class name + connector + testing method name, and underline the name of the class and method name will be replaced with Spaces, the demo code is as follows, using the annotations @ IndicativeSentencesGeneration, its the separator attribute is the name of the class and method of the connection between the operator:
package com.bolingcavalry.advanced.service.impl;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.IndicativeSentencesGeneration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@ IndicativeSentencesGeneration (separator = ", the test method: ", the generator. = DisplayNameGenerator ReplaceUnderscores. Class)
public class IndicativeSentences_Test {
@Test
void if_it_is_one_of_the_following_years(a) {}}Copy the code
- The result is as follows:
Repeated Tests (Repeated Tests)
- RepeatedTest is to specify a Test method to be executed repeatedly for several times, the demo code is as follows, it can be seen that @test has been replaced by @REPEATedtest (5), the number 5 indicates the repeated execution for 5 times:
@Order(1)
@displayName (" Repeat test ")
@RepeatedTest(5)
void repeatTest(TestInfo testInfo) {
log.info("Test method [{}]", testInfo.getTestMethod().get().getName());
}
Copy the code
- The execution result is shown as follows:
3. In the implementation of the test method, if you want to know the current number of RepetitionInfo and the total number of RepetitionInfo, just add the inlet of the type of RepetitionInfo to the test method, the demonstration code is as follows, it can be seen that the API provided by RepetitionInfo can obtain the total number and the current number of RepetitionInfo:
@Order(2)
@displayName (" Repeat test, get execution from input parameter ")
@RepeatedTest(5)
void repeatWithParamTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {
log.info("Test method [{}], current [{}], total [{}] times",
testInfo.getTestMethod().get().getName(),
repetitionInfo.getCurrentRepetition(),
repetitionInfo.getTotalRepetitions());
}
Copy the code
- The execution results of the above code are as follows:
5. It can be seen from the lower left corner of the figure above that the result of repeated execution is displayed as “repetition X of X”, which can be customized, namely the name attribute of RepeatedTest annotation. The demo code is as follows: It can be seen that currentRepetition and totalRepetitions are placeholders and will be replaced with the current value and totalRepetitions respectively in the real presentation:
@Order(3)
@displayName (" Repeat test, use custom name ")
@repeatedtest (value = 5, name=" RepeatedTest: {currentRepetition}/{totalrepetition}")
void repeatWithCustomDisplayNameTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {
log.info("Test method [{}], current [{}], total [{}] times",
testInfo.getTestMethod().get().getName(),
repetitionInfo.getCurrentRepetition(),
repetitionInfo.getTotalRepetitions());
}
Copy the code
- The execution results of the above code are as follows:
Nested Tests
- If there are many test methods in a test class (e.g., add, delete, modify, query, and query), then both administration and presentation of results become complicated. Nested Tests can be used.
- Nested test (Nested Tests) function is to create some inner class in the test class, add and delete, for example, put all the Tests to find methods in an inner class, delete all test methods in another inner class, again add @ Nested comments to each of the inner class, class as the unit within this will execute the test and show the results, As shown below:
3. The nested test demo code is as follows:
package com.bolingcavalry.advanced.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Slf4j
@displayName (" Nested test demo ")
public class NestedTest {
@Nested
@displayName (" Find service-related tests ")
class FindService {
@Test
void findByIdTest(a) {}
@Test
void findByNameTest(a) {}}@Nested
@displayName (" Delete service-related tests ")
class DeleteService {
@Test
void deleteByIdTest(a) {}
@Test
void deleteByNameTest(a) {}}}Copy the code
- The result of the above code execution is as follows, which shows that the code management, execution and result presentation are all managed in groups:
Dynamic Tests
- The Test methods are defined at compile time and fixed at run time.
- JUnit5 offers another type of testing method: Dynamic Tests. First of all, test methods can be produced during run time. The methods modified by @TestFactory can be produced before they are executed and displayed like traditional test methods.
- The testFactoryTest method is decorated with @testFactory and returns an Iterable containing multiple DynamicTest instances. Each DynamicTest instance represents a test method. TestFactoryTest = testFactoryTest; testFactoryTest = testFactoryTest; testFactoryTest = testFactoryTest; testFactoryTest = testFactoryTest;
package com.bolingcavalry.advanced.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
@SpringBootTest
@Slf4j
class DynamicDemoTest {
@TestFactory
Iterable<org.junit.jupiter.api.DynamicTest> testFactoryTest() {
DynamicTest firstTest = dynamicTest(
"Dynamic Test Case 1",
() -> {
log.info("Use case number one, write the unit test logic here."); }); DynamicTest secondTest = dynamicTest("Dynamic Test Case No. 2",
() -> {
log.info("Use case number two, write the unit test logic here."); });returnArrays.asList(firstTest, secondTest); }}Copy the code
- DynamicTest = @test; DynamicTest = @test;
Introduction to Parallel Execution
- At the end of the JUnit5 learning series, let’s look at a feature that is both easy to understand and useful: Parallel Execution.
- Concurrent execution tests in JUnit5 can be divided into the following three scenarios:
- Multiple test classes whose respective test methods execute simultaneously;
- A test class in which multiple test methods are executed simultaneously;
- A test class in which a test method is executed by multiple threads when Repeated Tests or Parameterized Tests are performed.
Parallel Execution in multiple threads
- Front multithreading concurrent execution has three scenarios are introduced, and the limit of the space is not one coding of actual combat, the choice of the third scenario to practice, that is: a test a test method in a class in repeated testing multi-threaded concurrent execution, as for the other two scenarios how to set up, the next article will be clear, your own practice;
- To create the JUnit5 configuration file, right-click the Test folder and choose “New”->”Directory” from the popup menu:
- Click “Resources” in the red box to create a new resources directory:
- Create a new file, junit-platform.properties, in the newly added Resources directory, with the following contents and detailed instructions for each configuration item:
# Parallel switch true/false
junit.jupiter.execution.parallel.enabled=true
# method level multithreading switch same_thread/concurrent
junit.jupiter.execution.parallel.mode.default = same_thread
Same_thread /concurrent
junit.jupiter.execution.parallel.mode.classes.default = same_thread
There are three options for concurrent policies:
# fixed: the fixed number of threads, at this time also by junit. Jupiter. Execution. The parallel. Config. Fixed. The parallelism to specify the number of threads
# dynamic: indicates counting the number of threads based on the number of processors and cores
# custom: custom concurrency strategy, through this configuration to specify: junit. Jupiter. Execution. The parallel. Config. Custom. Class
junit.jupiter.execution.parallel.config.strategy = fixed
# number of concurrent threads. This configuration item is available only if the concurrency policy is fixed
junit.jupiter.execution.parallel.config.fixed.parallelism = 5
Copy the code
- Because the practice is the same type of concurrent execution of a method for many times, so the above configuration, class level multithreading switch and method level multithreading switch are selected “the same thread”, that is to say, do not need to execute multiple classes or multiple methods concurrently, please adjust according to your own needs;
- As for the concurrency strategy, dynamic adjustment is selected here. Here I have i5-8400 processor, which has six cores and six threads. Later we will see whether the execution effect is related to this hardware configuration.
- The SAME_THREAD value is SAME_THREAD, which limits the Execution of multiple tests within the same thread.
package com.bolingcavalry.advanced.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class ParallelExecutionTest {
@Order(1)
@Execution(ExecutionMode.SAME_THREAD)
@displayName (" Single thread 10 times ")
@repeatedtest (value = 10, name=" RepeatedTest: {currentRepetition}/{totalrepetition}")
void sameThreadTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {
log.info("Test method [{}], current [{}], total [{}] times", testInfo.getTestMethod().get().getName(), repetitionInfo.getCurrentRepetition(), repetitionInfo.getTotalRepetitions()); }}Copy the code
- The result is as follows:
- The code that executes concurrently under repeated tests is as follows, with the value @execution CONCURRENT:
@Order(2)
@Execution(ExecutionMode.CONCURRENT)
@displayName (" Multithreaded execution 10 times ")
@repeatedtest (value = 10, name=" RepeatedTest: {currentRepetition}/{totalrepetition}")
void concurrentTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {
log.info("Test method [{}], current [{}], total [{}] times",
testInfo.getTestMethod().get().getName(),
repetitionInfo.getCurrentRepetition(),
repetitionInfo.getTotalRepetitions());
}
Copy the code
- Red box 1 shows that the order is out of order, and red box 2 shows that the ten test methods are executed on five threads:
11. Finally, the demonstration of parameterized test, which can also be set to multi-threaded parallel execution:
@Order(3)
@Execution(ExecutionMode.CONCURRENT)
@displayName (" multiple ints ")
@ParameterizedTest
@valuesource (ints = {1,2,3,4,5,6,7,8,9,0})
void intsTest(int candidate) {
log.info("ints [{}]", candidate);
}
Copy the code
- The execution result is shown in the following figure, which shows that 5 threads are also executing in parallel:
conclusion
So far, “JUnit5 learning” series has been completed, thank you for your patience to read, I hope this original series can bring you some useful information, to provide some reference for your unit testing, if you find any mistakes in the article, look forward to your advice;
You are not alone, Xinchen original accompany all the way
- Java series
- Spring series
- The Docker series
- Kubernetes series
- Database + middleware series
- 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…