Pre
SpringBoot - 单元测试利器Mockito入门
SpringBoot - 应用程序测试方案
SpringBoot - @SpringBootTest加速单元测试的小窍门
Spring Boot - Junit4 / Junit5 / Spring Boot / IDEA 关系梳理
package org.junit.jupiter.api;
import static org.apiguardian.api.API.Status.STABLE;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.apiguardian.api.API;
import org.junit.platform.commons.annotation.Testable;
/**
* {@code @Test} is used to signal that the annotated method is a
* <em>test</em> method.
*
* <p>{@code @Test} methods must not be {@code private} or {@code static}
* and must not return a value.
*
* <p>{@code @Test} methods may optionally declare parameters to be
* resolved by {@link org.junit.jupiter.api.extension.ParameterResolver
* ParameterResolvers}.
*
* <p>{@code @Test} may also be used as a meta-annotation in order to create
* a custom <em>composed annotation</em> that inherits the semantics of
* {@code @Test}.
*
* <h2>Test Execution Order</h2>
*
* <p>By default, test methods will be ordered using an algorithm that is
* deterministic but intentionally nonobvious. This ensures that subsequent runs
* of a test suite execute test methods in the same order, thereby allowing for
* repeatable builds. In this context, a <em>test method</em> is any instance
* method that is directly annotated or meta-annotated with {@code @Test},
* {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or
* {@code @TestTemplate}.
*
* <p>Although true <em>unit tests</em> typically should not rely on the order
* in which they are executed, there are times when it is necessary to enforce
* a specific test method execution order — for example, when writing
* <em>integration tests</em> or <em>functional tests</em> where the sequence of
* the tests is important, especially in conjunction with
* {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}.
*
* <p>To control the order in which test methods are executed, annotate your
* test class or test interface with {@link TestMethodOrder @TestMethodOrder}
* and specify the desired {@link MethodOrderer} implementation.
*
* @since 5.0
* @see RepeatedTest
* @see org.junit.jupiter.params.ParameterizedTest
* @see TestTemplate
* @see TestFactory
* @see TestInfo
* @see DisplayName
* @see Tag
* @see BeforeAll
* @see AfterAll
* @see BeforeEach
* @see AfterEach
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.0")
@Testable
public @interface Test {
}
JUnit 4 vs JUnit 5
以下是JUnit 4和JUnit 5注解之间的一些主要区别
功能/特性 | JUnit 4注解 | JUnit 5注解 |
测试方法声明 | @Test | @Test |
测试类声明 | @RunWith | @ExtendWith |
断言 | org.junit.Assert类 | org.junit.jupiter.api.Assertions类 |
测试生命周期 | 无 | @BeforeAll, @BeforeEach, @AfterEach, @AfterAll |
参数化测试 | @Parameterized | @ParameterizedTest |
条件测试 | 无 | @EnabledOnOs, @DisabledOnOs, @EnabledOnJre, @DisabledOnJre, 等等 |
标记重复测试 | 无 | @RepeatedTest |
依赖性测试 | 无 | @Test中使用@TestDependsOn和@TestDependency |
测试实例生命周期 | 无 | @TestInstance |
测试接口和默认方法 | 不支持 | 支持 |
扩展模型 | 自定义Runner | 自定义Extension |
这些是JUnit 4和JUnit 5之间的一些重要区别,JUnit 5引入了许多新的功能和改进,以提供更灵活、强大的测试框架。你可以根据项目的需要选择适合的JUnit版本。
Junit5 常用注解
@SpringBootTest
: 用于指定测试类启用Spring Boot Test,默认会提供Mock环境。@ExtendWith
: 如果只想启用Spring环境进行简单测试,不想启用Spring Boot环境,可以配置扩展为:SpringExtension
。@Test
: 指定方法为测试方法。@TestMethodOrder
: 用于配置测试类中方法的执行顺序策略,配置为OrderAnnotation
时,按@Order
顺序执行。@Order
: 用于配置方法的执行顺序,数字越低执行顺序越高。@DisplayName
: 用于指定测试类和测试方法的别名。@BeforeAll
: 在测试类的所有测试方法前执行一次,可用于全局初始化。@AfterAll
: 在测试类的所有测试方法后执行一次,可用于全局销毁资源。@BeforeEach
: 在测试类的每个测试方法前都执行一次。@AfterEach
: 在测试类的每个测试方法后都执行一次。@Disabled
: 禁用测试方法。@RepeatedTest
: 指定测试方法重复执行。@ParameterizedTest
: 指定参数化测试方法,类似重复执行,从@ValueSource
中获取参数。@ValueSource
: 用于参数化测试指定参数。@AutoConfigureMockMvc
: 启用MockMvc的自动配置,可用于测试接口。
栗子
以下是上述注解的使用方法示例以及相应的Java代码:
@SpringBootTest
:用于指定Spring Boot测试。示例:
@SpringBootTest
public class MySpringBootTest {
// 测试方法
}
@ExtendWith
:用于配置测试类的执行环境。示例:
@ExtendWith(SpringExtension.class)
public class MySpringTest {
// 测试方法
}
@Test
:指定方法为测试方法。示例:
@Test
public void testSomeMethod() {
// 测试逻辑
}
@TestMethodOrder
和@Order
:配置测试方法的执行顺序。示例:
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OrderedTestExample {
@Order(1)
@Test
public void testMethod1() {
// 测试逻辑
}
@Order(2)
@Test
public void testMethod2() {
// 测试逻辑
}
}
@DisplayName
:用于指定测试类和测试方法的别名。示例:
@DisplayName("My Test Suite")
public class MyTestSuite {
@Test
@DisplayName("My Test Case")
public void myTestCase() {
// 测试逻辑
}
}
@BeforeAll
和@AfterAll
:在测试类的所有测试方法前和后执行一次,可用于全局初始化和销毁资源。示例:
@BeforeAll
public static void setup() {
// 初始化操作
}
@AfterAll
public static void teardown() {
// 资源销毁操作
}
@BeforeEach
和@AfterEach
:在测试类的每个测试方法前和后都执行一次。示例:
@BeforeEach
public void beforeEachTest() {
// 执行前的准备工作
}
@AfterEach
public void afterEachTest() {
// 执行后的清理工作
}
@Disabled
:禁用测试方法。示例:
@Test
@Disabled("This test is not ready yet.")
public void disabledTest() {
// 未完成的测试逻辑
}
@RepeatedTest
:指定测试方法重复执行。示例:
@RepeatedTest(5)
public void repeatedTest() {
// 该测试方法会重复执行5次
}
@ParameterizedTest
和@ValueSource
:用于参数化测试。示例:
@ParameterizedTest
@ValueSource(strings = { "apple", "banana", "cherry" })
public void testFruit(String fruit) {
// 使用参数化的水果名称进行测试
}
@AutoConfigureMockMvc
:启用MockMvc的自动配置,可用于测试接口。示例:
@SpringBootTest
@AutoConfigureMockMvc
public class MyControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testController() throws Exception {
mockMvc.perform(get("/api/someendpoint"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON));
}
}
这些示例演示了如何使用这些注解来编写JUnit 5和Spring Boot测试。您可以根据您的具体需求和测试场景进行相应的配置和使用。
package com.artisan.boottest.example;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BasicTest {
@Test
public void test() {
String artisan = "artisan good";
Assertions.assertEquals("artisan good", artisan);
}
}
package com.artisan.boottest.example;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* @author 小工匠
* @version 1.0
* @description: JUnit指定方法测试顺序
* @mark: show me the code , change the world
*/
@Slf4j
@ExtendWith(SpringExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class MethodOrderTest {
@Test
@Order(1)
@DisplayName("order为1的方法")
void lowOrder(){
log.info("lowOrder method");
}
@Test
@Order(2)
@DisplayName("order为2的方法")
void highOrder(){
log.info("highOrder method");
}
}
package com.artisan.boottest.example;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* @author 小工匠
* @version 1.0
* @description: JUnit生命周期测试
* @mark: show me the code , change the world
*/
@Slf4j
@ExtendWith(SpringExtension.class)
public class LifecycleTest {
@BeforeAll
static void allInit() {
log.info("allInit():在所有方法前执行,只执行一次");
}
@BeforeEach
void eachInit() {
log.info("eachInit():在测试方法前执行,每个测试方法前都执行");
}
@Test
void successTest() {
log.info("successTest():方法执行成功");
}
@AfterEach
void eachDown() {
log.info("eachDown():在测试方法后执行,每个测试方法后都执行");
}
@AfterAll
static void allDown() {
log.info("allDown():在测试方法后执行,每个测试方法后都执行");
}
}
package com.artisan.boottest.example;
import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.time.Duration;
/**
* @author 小工匠
* @version 1.0
* @description: JUnit断言测试
* @mark: show me the code , change the world
*/
@Slf4j
@ExtendWith(SpringExtension.class)
public class AssertTest {
// 可以使用fail方法直接断言方法执行失败并输出提示信息。
@Test
void failTest() {
Assertions.fail("failTest():方法执行失败");
}
// 还可以通过assertTrue、assertNull、assertEquals这类方法来断言结果是否符合预期。
@Test
void trueTest(){
Assertions.assertTrue(666==666);
}
@Test
void trueFalse(){
Assertions.assertFalse(8888<=9999);
}
@Test
void nullTest(){
String str = null;
Assertions.assertNull(str);
}
@Test
void notNullTest(){
String str = "test";
Assertions.assertNotNull(str);
}
@Test
void equalsTest(){
String str1 = "test";
String str2 = "test";
Assertions.assertEquals(str1,str2);
}
@Test
void notEqualsTest(){
String str1 = "test";
String str2 = "test";
Assertions.assertNotEquals(str1,str2);
}
// 也可以使用assertThrows方法来断言方法中抛出的异常。
@Test
void throwsTest(){
Assertions.assertThrows(NullPointerException.class,()->{
String str = null;
log.info(str.toLowerCase());
});
}
// 还可通过assertTimeout方法断言方法的执行时间。
@Test
void timeoutTest(){
Assertions.assertTimeout(Duration.ofMillis(1000),()->{
long sleepTime = 2000;
ThreadUtil.sleep(sleepTime);
log.info("timeoutTest():休眠{}毫秒",sleepTime);
});
}
// 或者通过assertAll方法将几个断言结合起来使用,Assertions类中提供的工具方法很多,具体可以参考它的代码。
@Test
void assertAllTest(){
Assertions.assertAll(()->{
trueTest();
},()->{
nullTest();
},()->{
equalsTest();
});
}
}
package com.artisan.boottest.example;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* @author 小工匠
* @version 1.0
* @description: 其他常用的注释
* @mark: show me the code , change the world
*/
@Slf4j
@ExtendWith(SpringExtension.class)
public class OthreTest {
// Spring Boot Test除了上述测试功能,还可以使用@Disabled来禁用某个测试方法
@Test
@Disabled("用于测试@Disabled注解")
void disabledTest() {
log.info("disabledTest():方法被执行");
}
// 也可以使用@RepeatedTest来实现循环测试
private static int count = 0;
@RepeatedTest(5)
void repeatedTest() {
count++;
log.info("repeatedTest():重复执行第{}次",count);
}
// 还可以通过@ParameterizedTest来进行参数化测试
@ParameterizedTest
@ValueSource(ints = {1,2,3})
public void parameterizedTest(int a){
log.info("parameterizedTest():a={}",a);
}
}
【三层测试】
package com.artisan.boottest.project;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
/**
* @author 小工匠
* @version 1.0
* @description: Dao层方法测试
* @mark: show me the code , change the world
*/
@Slf4j
@SpringBootTest
public class MapperTest {
@Autowired
private PmsBrandMapper brandMapper;
@Test
void testGetById(){
long id = 6;
PmsBrand pmsBrand = brandMapper.selectByPrimaryKey(id);
LOGGER.info("brand name:{}",pmsBrand.getName());
Assertions.assertEquals("小米",pmsBrand.getName());
}
}
package com.artisan.boottest.project;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author 小工匠
* @version 1.0
* @description: Service层方法测试
* @mark: show me the code , change the world
*/
@Slf4j
@SpringBootTest
public class ServiceTest {
@Autowired
private PmsBrandService brandService;
@Test
void testGetById(){
long id = 6;
PmsBrand pmsBrand = brandService.getBrand(id);
log.info("brand name:{}",pmsBrand.getName());
Assertions.assertEquals("小米",pmsBrand.getName());
}
}
package com.artisan.boottest.project;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
/**
* @author 小工匠
* @version 1.0
* @description: 对于Controller层方法进行测试,有时我们需要模拟请求,使用MockMvc即可
* @mark: show me the code , change the world
*/
@SpringBootTest
@AutoConfigureMockMvc
public class ControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void mvcTest() throws Exception {
//模拟发送一个请求访问分页查询品牌列表的接口
mockMvc.perform(MockMvcRequestBuilders.get("/brand/list") //设置请求地址
.param("pageNum", "1") //设置请求参数
.param("pageSize", "5"))
.andExpect(MockMvcResultMatchers.status().isOk()) //断言返回状态码为200
.andDo(MockMvcResultHandlers.print()) //在控制台打印日志
.andReturn(); //返回请求结果
}
}