测试开发规范
测试分层策略
层次 测试类型 启动 Spring 执行速度
单元测试 工具类/枚举/POJO 否 < 1s
集成测试 Service/Controller/Mapper 是(@SpringBootTest ) 5-10s
测试文件位置
src/test/java/org/dromara/test/ # 通用测试 src/test/java/org/dromara/{模块}/ # 模块测试(与源码包路径一致)
重要:@SpringBootTest 只能在 SpringBoot 主包下使用(需包含 main 方法和 yml 配置)。
- 单元测试(纯 JUnit5,无 Spring)
package org.dromara.test;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test;
@DisplayName("StringUtils 测试") public class StringUtilsTest {
@Test
@DisplayName("测试 isBlank 方法")
public void testIsBlank() {
Assertions.assertTrue(StringUtils.isBlank(null));
Assertions.assertTrue(StringUtils.isBlank(""));
Assertions.assertFalse(StringUtils.isBlank("hello"));
}
@Test
@DisplayName("测试异常抛出")
public void testThrowsException() {
Assertions.assertThrows(NullPointerException.class, () -> {
String str = null;
str.length();
});
}
}
- Service 集成测试
@SpringBootTest @Transactional // 测试后自动回滚,不污染数据库 @DisplayName("TestDemo Service 测试") public class TestDemoServiceTest {
@Autowired
private ITestDemoService testDemoService;
@Test
@DisplayName("测试添加数据")
public void testAdd() {
// Arrange
TestDemoBo bo = new TestDemoBo();
bo.setDeptId(103L);
bo.setUserId(1L);
bo.setTestKey("测试数据");
bo.setValue("test_value");
// Act
Boolean result = testDemoService.insertByBo(bo);
// Assert
Assertions.assertTrue(result);
}
@Test
@DisplayName("测试查询详情")
public void testGetById() {
TestDemoBo bo = new TestDemoBo();
bo.setTestKey("测试查询");
bo.setValue("test_query");
testDemoService.insertByBo(bo);
TestDemoVo vo = testDemoService.queryById(bo.getId());
Assertions.assertNotNull(vo);
Assertions.assertEquals("测试查询", vo.getTestKey());
}
}
Mock 外部依赖
@SpringBootTest @DisplayName("订单服务测试") public class OrderServiceTest {
@Autowired
private IOrderService orderService;
@MockBean
private IPaymentService paymentService;
@Test
@DisplayName("测试订单支付(Mock 支付服务)")
public void testPayOrder() {
Long orderId = 123L;
Mockito.when(paymentService.pay(orderId)).thenReturn(true);
Boolean success = orderService.payOrder(orderId);
Assertions.assertTrue(success);
Mockito.verify(paymentService, Mockito.times(1)).pay(orderId);
}
}
- Controller 测试
@SpringBootTest @AutoConfigureMockMvc @DisplayName("TestDemo Controller 测试") public class TestDemoControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
@DisplayName("测试分页查询")
public void testPageList() throws Exception {
mockMvc.perform(get("/demo/demo/list")
.param("pageNum", "1")
.param("pageSize", "10"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
}
@Test
@DisplayName("测试添加数据")
public void testAdd() throws Exception {
String requestBody = """
{
"deptId": 103,
"userId": 1,
"testKey": "测试数据",
"value": "test_value"
}
""";
mockMvc.perform(post("/demo/demo")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
}
}
- 参数化测试
@DisplayName("参数化测试") public class ParamUnitTest {
@ParameterizedTest
@ValueSource(strings = {"t1", "t2", "t3"})
public void testValueSource(String str) {
Assertions.assertNotNull(str);
}
@ParameterizedTest
@EnumSource(UserType.class)
public void testEnumSource(UserType type) {
Assertions.assertNotNull(type);
}
@ParameterizedTest
@CsvSource({"1, Banner, 横幅广告", "2, Popup, 弹窗广告"})
public void testCsvData(String code, String name, String desc) {
Assertions.assertNotNull(code);
}
@ParameterizedTest
@MethodSource("getParam")
public void testMethodSource(String str) {
Assertions.assertNotNull(str);
}
public static Stream<String> getParam() {
return Stream.of("t1", "t2", "t3");
}
}
更多参数化测试示例详见 references/parameterized-examples.md
- 异常测试
@Test @DisplayName("测试 key 为空抛出异常") public void testAdd_ThrowsException() { TestDemoBo bo = new TestDemoBo(); // 不设置必填字段
ServiceException exception = Assertions.assertThrows(
ServiceException.class,
() -> testDemoService.insertByBo(bo)
);
Assertions.assertTrue(exception.getMessage().contains("key键不能为空"));
}
- 测试标签(@Tag)
@Test @Tag("dev") public void testDev() { } @Test @Tag("prod") public void testProd() { }
mvn test -Dgroups=dev # 运行 dev 标签 mvn test -DexcludedGroups=prod # 排除 prod 标签
运行测试
mvn test # 运行所有测试 mvn test -Dtest=AdServiceTest # 运行单个测试类 mvn test -Dtest=AdServiceTest#testAddAd # 运行单个方法 mvn clean install -DskipTests # 跳过测试
开发检查清单
命名规范
-
测试类:{被测试类名}Test
-
测试方法:test{功能}
-
使用 @DisplayName 添加中文描述
-
位于 src/test/java ,包路径与源码一致
注解选择
场景 注解组合
工具类/枚举/POJO 不加 @SpringBootTest
Service 测试 @SpringBootTest
- @Transactional
Controller 测试 @SpringBootTest
- @AutoConfigureMockMvc
Mock Spring Bean @MockBean
Mock(纯单元测试) @Mock
- @InjectMocks
常见错误
错误 正确做法
测试在 src/main/java
放到 src/test/java
缺少 @DisplayName
必须添加描述
需要注入但没加 @SpringBootTest
加上注解
@SpringBootTest 在非主包下 确保在主类同包或子包
Mock 后不验证调用 Mockito.verify()
测试方法相互依赖 每个测试独立
Service 测试不加 @Transactional
加上防止污染数据库