Junit单元测试框架

2024/5/11 单元测试JUnitMockitoPowerMockSpring

# 基本概念

# JUnit

JUnit is a simple framework to write repeatable tests.

测试运行器:决定了用什么方式偏好去运行这些测试集/类/方法。

测试集:可能包含多个测试类。

测试类:包含一个或多个测试方法的文件。

测试方法/测试用例:用@Test注解标记的方法。

测试类及测试用例命名规范:

  • 测试类命名规范: 被测试类+Test,例如:UserServiceTest
  • 测试用例命名规范: test+被测试方法,例如:testGet

常见的测试运行器:

JUnit 4 JUnit 5 Description
@RunWith(JUnit38ClassRunner.class) 用于兼容JUnit3.8的运行器
@RunWith(JUnit4.class) JUnit 4的默认运行器,用JUnit4测试工具来运行测试
@RunWith(MockitoJUnitRunner.class) @ExtendWith(MockitoExtension.class) 运行Mockito的运行环境
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@ExtendWith(SpringExtension.class) 让类运行在Spring的测试环境,以便测试开始时自动创建Spring应用上下文,并使用JUnit4测试工具运行测试。
@RunWith(SpringRunner.class)继承了 @RunWith(SpringJUnit4ClassRunner.class) ,用法相同。
@RunWith(Parameterized.class) 参数化运行器,配合@Parameters使用JUnit的参数化功能
@RunWith(Suite.class)
@SuiteClasses({ATest.class,BTest.class,CTest.class})
测试集运行器,配合@SuiteClasses使用JUnit的测试集功能

JUnit3中测试用例需要继承TestCase类

JUnit4中测试用例无需继承TestCase类,只需标记@Test注解即可。

JUnit 3 JUnit 4 JUnit 5
所有测试用例前仅执行一次 N/A @BeforeClass @BeforeAll
每个测试用例前执行 setUp() @Before @BeforeEach
执行测试用例 N/A @Test @Test
每个测试用例后执行 tearDown() @After @AfterEach
所有测试用例后仅执行一次 N/A @AfterClass @AfterAll
JUnit 4 Annotation JUnit 5 Annotation Description
@Test @Test Denotes that a method is a test method. Unlike JUnit 4’s @Test annotation, this annotation does not declare any attributes, since test extensions in JUnit Jupiter operate based on their own dedicated annotations. Such methods are inherited unless they are overridden.
@ParameterizedTest Denotes that a method is a parameterized test (opens new window). Such methods are inherited unless they are overridden.
@RepeatedTest Denotes that a method is a test template for a repeated test (opens new window). Such methods are inherited unless they are overridden.
@TestFactory Denotes that a method is a test factory for dynamic tests (opens new window). Such methods are inherited unless they are overridden.
@TestTemplate Denotes that a method is a template for test cases (opens new window) designed to be invoked multiple times depending on the number of invocation contexts returned by the registered providers (opens new window). Such methods are inherited unless they are overridden.
@TestClassOrder Used to configure the test class execution order (opens new window) for @Nested test classes in the annotated test class. Such annotations are inherited.
@TestMethodOrder Used to configure the test method execution order (opens new window) for the annotated test class; similar to JUnit 4’s @FixMethodOrder. Such annotations are inherited.
@TestInstance Used to configure the test instance lifecycle (opens new window) for the annotated test class. Such annotations are inherited.
@DisplayName Declares a custom display name (opens new window) for the test class or test method. Such annotations are not inherited.
@DisplayNameGeneration Declares a custom display name generator (opens new window) for the test class. Such annotations are inherited.
@Before @BeforeEach Denotes that the annotated method should be executed before each @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory method in the current class; analogous to JUnit 4’s @Before. Such methods are inherited – unless they are overridden or superseded (i.e., replaced based on signature only, irrespective of Java’s visibility rules).
@After @AfterEach Denotes that the annotated method should be executed after each @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory method in the current class; analogous to JUnit 4’s @After. Such methods are inherited – unless they are overridden or superseded (i.e., replaced based on signature only, irrespective of Java’s visibility rules).
@BeforeClass @BeforeAll Denotes that the annotated method should be executed before all @Test, @RepeatedTest, @ParameterizedTest, and @TestFactory methods in the current class; analogous to JUnit 4’s @BeforeClass. Such methods are inherited – unless they are hidden, overridden, or superseded, (i.e., replaced based on signature only, irrespective of Java’s visibility rules) – and must be static unless the "per-class" test instance lifecycle (opens new window) is used.
@AfterClass @AfterAll Denotes that the annotated method should be executed after all @Test, @RepeatedTest, @ParameterizedTest, and @TestFactory methods in the current class; analogous to JUnit 4’s @AfterClass. Such methods are inherited – unless they are hidden, overridden, or superseded, (i.e., replaced based on signature only, irrespective of Java’s visibility rules) – and must be static unless the "per-class" test instance lifecycle (opens new window) is used.
@Nested Denotes that the annotated class is a non-static nested test class (opens new window). On Java 8 through Java 15, @BeforeAll and @AfterAll methods cannot be used directly in a @Nested test class unless the "per-class" test instance lifecycle (opens new window) is used. Beginning with Java 16, @BeforeAll and @AfterAll methods can be declared as static in a @Nested test class with either test instance lifecycle mode. Such annotations are not inherited.
@Tag Used to declare tags for filtering tests (opens new window), either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are inherited at the class level but not at the method level.
@Ignore @Disabled Used to disable (opens new window) a test class or test method; analogous to JUnit 4’s @Ignore. Such annotations are not inherited.
@Timeout Used to fail a test, test factory, test template, or lifecycle method if its execution exceeds a given duration. Such annotations are inherited.
@RunWith @ExtendWith Used to register extensions declaratively (opens new window). Such annotations are inherited.
指定测试用例运行环境
@RegisterExtension Used to register extensions programmatically (opens new window) via fields. Such fields are inherited unless they are shadowed.
@TempDir Used to supply a temporary directory (opens new window) via field injection or parameter injection in a lifecycle method or test method; located in the org.junit.jupiter.api.io package.

# JUnit 4

https://junit.org/junit4/

# JUnit 5

Unlike previous versions of JUnit, JUnit 5 is composed of several different modules from three different sub-projects.

JUnit 5 = JUnit Platform + JUnit Jupiter+ JUnit Vintage

JUnit Platform:用于在 JVM 上运行测试的基础设施,无论是编程模型还是命令行工具。

JUnit Jupiter:JUnit 5 的新模块,它提供了编写测试的新的程序化模型。

JUnit Vintage:用于在 JUnit 平台上运行旧的 JUnit 3 和 JUnit 4 测试的引擎。

JUnit Platform是JUnit5的核心组成部分,它定义了一个抽象的TestEngine API,用于在平台上运行测试框架。这意味着其他自动化测试引擎或开发人员自己定制的引擎都可以接入JUnit5实现对接和执行。JUnit Platform还支持通过命令行、Gradle和Maven来运行测试。

JUnit Jupiter是JUnit5的扩展部分,它提供了新的编程模型和扩展模型,使编写测试用例更加方便。这个部分可以看作是承载JUnit4原有功能的演进,包含了JUnit5最新的编程模型和扩展机制。通过这些新特性,JUnit Jupiter使自动化测试更加方便、功能更加强大。

JUnit Vintage提供了一个在平台上运行JUnit3和JUnit4的TestEngine。这意味着,如果你在使用JUnit3或JUnit4编写的旧测试用例,你可以将这些用例迁移到JUnit5上,并从中受益。

IntelliJ IDEA supports running tests on the JUnit Platform since version 2016.2. For details please see the post on the IntelliJ IDEA blog (opens new window). Note, however, that it is recommended to use IDEA 2017.3 or newer since these newer versions of IDEA will download the following JARs automatically based on the API version used in the project: junit-platform-launcher, junit-jupiter-engine, and junit-vintage-engine.

https://junit.org/junit5/

https://junit.org/junit5/docs/current/user-guide/ (opens new window)

https://github.com/junit-team/junit5-samples (opens new window)

# 参数化测试/数据驱动测试

参数化测试必须将 @Test 注解替换为 @ParameterizedTest 注解。

  • @ValueSource 注解可提供单个数组作为测试数据。
  • @NullSource 注解用于传递null值。
  • @EmptySource 注解用于传递空值。
  • @NullAndEmptySource 注解是 @NullSource 和 @EmptySource 的组合注解,可同时提供null值和空值。
  • @EnumSource 注解可提供单个枚举作为测试数据。
  • @MethodSource 注解读取指定方法的返回值作为参数化测试的入参(注意方法返回类型必须是一个流)。
  • @CsvSource 注解可提供单组或多组参数。
  • @CsvFileSource 注解通过读取CSV文件作为参数化测试的入参。
  • 实现 ArgumentsProvider 接口,并配合 @ArgumentsSource 注解来给测试方法提供测试数据。

# Spring常用注解

注解 作用
@WebMvcTest 启动一个应用程序上下文,该上下文只包含测试 Web 控制器所需的 bean
@AutoConfigureMockMvc 可以应用于测试类以启用和配置MockMvc的自动配置的注释。
@PropertySource 加载配置文件
@TestPropertySource 加载配置文件。@TestPropertySource可以用来覆盖掉来自于系统环境变量,Java的系统属性,@PropertySource的属性。
@ActiveProfiles 激活指定环境
@ActiveProfiles(profiles = {"default", "local", "test"})
如果有相同配置,后面同名配置会覆盖前面的。
@ContextConfiguration 用来加载ApplicationContext

@WebMvcTest 示例:

@WebMvcTest(controllers = UserController.class)
@ExtendWith(SpringExtension.class)
1
2

@AutoConfigureMockMvc 示例:

@AutoConfigureMockMvc
@SpringBootTest
1
2

@ContextConfiguration 示例:

@ActiveProfiles("local")
@PropertySource("/application-local.yml")
@ContextConfiguration(
    classes = {SftpOutboundFactoryTest.ContextConfig.class}, // 用来加载配置类
    initializers = ConfigDataApplicationContextInitializer.class // 用来加载配置文件
)
@ExtendWith(SpringExtension.class)
1
2
3
4
5
6
7

# 常见的Mock框架

# Mockito

https://site.mockito.org/ (opens new window)

mockito-core:Mockito 核心依赖,提供了 Mockito 框架的核心功能和 API。

mockito-junit-jupiter:Mockito 与 JUnit Jupiter 的集成依赖,用于在 JUnit 5 环境下使用 Mockito 进行单元测试。

mockito-inline:Mockito 的内联依赖,用于支持内联(inline)Mocks 的创建和使用。

Note that you must use @RunWith(MockitoJUnitRunner.class) or MockitoAnnotations.initMocks(this) to initialize these mocks and inject them.

What are the limitations of Mockito: https://github.com/mockito/mockito/wiki/FAQ (opens new window)

常见注解使用:

Non-Spring Env Spring Env Description
@InjectMocks @Autowired 创建类实例并注入依赖的模拟,以便在单元测试中更好地控制测试环境,提高测试的准确性。
@InjectMocks creates an instance of the class and injects the mocks that are created with the @Mock (or @Spy ) annotations into this instance.
@InjectMocks will only inject mocks/spies created using the @Spy or @Mock annotation.
@Autowired will only inject mocks/spies created using the @SpyBean or @MockBean annotation.
@Mock @MockBean 创建模拟
@Mock 相当于 Mockito.mock(Class classToMock)
打桩的方法均使用模拟,不会真实调用
@Spy @SpyBean 创建模拟
@Spy 相当于 Mockito.spy(Class classToSpy)
如果某方法没有打桩,则真实调用,否则使用模拟。

# 打桩(stubbing)

// 有返回值
when(mockObject.someMethod()).thenReturn(someReturnValue);
doReturn(someReturnValue).when(mockObject).someMethod();

// 无返回值
doNothing().when(mockObject).someMethod();

// 抛出异常
when(mockObject.someMethod()).thenThrow(someException);
doThrow(someException).when(mockObject).someMethod();
1
2
3
4
5
6
7
8
9
10

# 断言(assertion)

// org.junit.jupiter.api.Assertions
// 方法是否被调用
verify(mockObject, never()).someMethod("never called");
verify(mockObject, atLeastOnce()).someMethod("called at least once");
verify(mockObject, atLeast(2)).someMethod("called at least twice");
verify(mockObject, atMost(3)).someMethod("called at most three times");
verify(mockObject, times(4)).someMethod("called four times");
1
2
3
4
5
6
7

# PowerMock

当所测逻辑里有静态工具类方法或私有方法时,我们希望它返回特定值时(极值边界、异常测试场景),我们可以用PowerMock弥补Mockito的不足。

http://powermock.github.io/ (opens new window)

https://github.com/powermock/powermock/wiki/Mockito-Maven (opens new window)

# 常用插件

# Maven Surefire Plugin

Maven Surefire Plugin的作用是用于执行单元测试。

这个插件可以自动识别和运行src/test目录下利用特定测试框架(如JUnit、TestNG等)编写的测试用例。Surefire Plugin也能识别和执行符合一定命名约定的普通类中的测试方法,例如,以Test结尾的测试类(如helloTest.java)。在Maven的生命周期中,test阶段默认绑定了Surefire Plugin的test目标,无需额外配置,直接运行run test即可。此外,Surefire Plugin还具有高度的可配置性和灵活性,支持多种单元测试框架,并且可以自定义测试用例的命名模式,通过合理配置和使用该插件,可以帮助开发者提高代码质量和项目稳定性。

# JaCoCo(Java Code Coverage)

JaCoCo(Java Code Coverage)是一种分析单元测试覆盖率的工具。

The JaCoCo Maven Plugin provides the JaCoCo runtime agent to your tests and allows basic report creation.

IDEA配置JaCoCo

正常情况下,测试类所在包结构与被测试的类所在包结构相同,直接点击run tests with coverage就可以运行测试查看覆盖率。若包结构不同,但是你又没有权限修改该项目的包结构,那么可以通过配置来查看单测覆盖率。

# 常见问题

# SpringExtension & MockitoExtension

SpringExtension integrates the Spring TestContext Framework into JUnit 5's Jupiter.

MockitoExtension is the JUnit Jupiter equivalent of our JUnit4 MockitoJUnitRunner.

When to use @Extendwith(SpringExtension.class) or @Extendwith(MockitoExtension.class) in JUnit 5?

When involving Spring:

If you want to use Spring test framework features in your tests like for example @MockBean , then you have to use @Extendwith(SpringExtension.class). It replaces the deprecated JUnit 4 @Runwith(SpringJUnit4classRunner.class)

When NOT involving Spring:

If you just want to involve Mockito and don't have to involve Spring, for example,when you just want to use the @Mock / @InjectMocks annotations, then you want to use @Extendwith(MockitoExtension.class) as it doesn't load in a bunch of unneeded Spring stuff. It replaces the deprecated JUnit 4 @Runwith(MockitooUnitRunner.class).