测试类型 | 主要目的 | Spring Context |
---|---|---|
单元测试 (Unit Test) | 只测 Java 逻辑,完全隔离Spring | ❌ 不用Spring |
切片测试 (Slice Test) | 只加载一部分 Spring Context | ✅ 只加载需要的部分 |
集成测试 (Integration Test) | 加载整个 Spring Boot 应用 | ✅ 全部加载 |
端到端 (E2E) | 启动全部+真实依赖 | ✅ 全部加载 + 外部容器 |
👉 单元测试是最基础的一层:不依赖 Spring,运行最快,价值最大。
1. 单元测试
单元测试(Unit Test)定义:
-
测试一个类的最小可测单元
-
不依赖 Spring
-
不访问数据库、文件系统、网络
-
通过模拟(Mock)替代外部依赖
特点:快、隔离、确保业务逻辑正确
2. Spring Boot 中单元测试依赖
虽然真正的单元测试不需要Spring,但通常使用Mockito + JUnit 5来Mock依赖:
在Maven里:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
✅ 包含:
- JUnit 5 (Jupiter)、Mockito、AssertJ、Hamcrest
3. 单元测试写法详解
3.1 不依赖Spring的最简单写法
示例类:
public class Calculator {
public int add(int a, int b) { return a + b; }
}
测试:
java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@Test
void testAdd() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3));
}
}
特点:
✅ 没有Spring注解
✅ 没有IOC容器
✅ 执行毫秒级
3.2 有依赖的服务,使用 Mockito
业务场景 :
Service里依赖Repository:
java
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new NotFoundException());
}
}
单元测试目标 :
✅ 不启动Spring
✅ Mock UserRepository
测试写法:
java
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock UserRepository userRepository;
@InjectMocks UserService userService;
@Test
void testGetUserById() {
User mockUser = new User(1L, "Alice");
when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));
User result = userService.getUserById(1L);
assertEquals("Alice", result.getName());
}
}
✅ 依赖注入通过Mockito
✅ 无Spring Context启动
✅ 速度极快
4. 单元测试的Mock策略
Spring Boot推荐的单元测试Mock实践:
场景 | 工具 |
---|---|
普通Bean | 纯Java手写Mock或Mockito |
接口依赖 | Mockito |
静态方法 | Mockito (mockStatic) |
构造函数/私有方法 | Mockito(较新版本支持)或PowerMock |
5. 为什么要Mock
✅ **隔离被测对象:**UserServiceTest 只关心 UserService 的逻辑,不需要真正的数据库
✅ 提高速度:无IO
✅ 更容易写失败场景:模拟异常
✅ 可重复:永远稳定
6. @SpringBootTest
✅ 是Spring Boot官方推荐的集成测试注解
✅ 启动完整的Spring Application Context
✅ 模拟「像生产里那样」启动你的Spring Bean、配置、自动装配
特点:
-
测试的粒度:集成级
-
加载速度:相对慢
-
依赖注入:自动可用
-
非常适合测试Service层、Controller层、整体协作
@SpringBootTest 注解核心原理
✅ 解析@SpringBootApplication
✅ 加载完整的Spring上下文
✅ 自动注入Bean
✅ 支持@MockBean替换Bean
✅ 可以模拟Web容器(随机端口、本地端口)
@SpringBootTest 常见用途
✅ Service层集成测试
-
有些Service需要Autowired进来其他Service/Repository
-
用@MockBean来mock下游依赖
✅ Controller端到端测试
-
启动Web服务器
-
用TestRestTemplate/RestAssured真正发HTTP
✅ 全局配置测试
- 验证Spring Profile、Properties
✅ 事务测试
- Spring Boot 测试默认有@Rollback
✅ 与Testcontainers结合
- 启动真实PostgreSQL/MySQL
7. 单元测试 vs 切片测试 vs 集成测试
Spring Boot里常见误区是把一切都用@SpringBootTest,这是集成测试,非常慢。
建议分层:
层级 | 用途 | 注解 | 启动容器? |
---|---|---|---|
单元测试 | 只测Java逻辑 | 无或MockitoExtension | ❌ |
切片测试 | 测试部分Spring配置(MVC、JPA) | @WebMvcTest、@DataJpaTest | ✅(部分) |
集成测试 | 测试整个Spring上下文 | @SpringBootTest | ✅ |
端到端测试 | 测试整个系统+外部依赖 | @SpringBootTest + Testcontainers | ✅ |
8. 推荐写法
✅ 80% 逻辑 → 纯单元测试
✅ 15% Spring 切片 → @WebMvcTest、@DataJpaTest
✅ 5% 全局集成 → @SpringBootTest / Testcontainers
9. 进阶:使用参数化测试
JUnit 5 支持:
java
@ParameterizedTest
@CsvSource({"1,2,3", "5,7,12"})
void testAdd(int a, int b, int expected) {
Calculator calc = new Calculator();
assertEquals(expected, calc.add(a, b));
}
✅ 避免重复代码
✅ 增加覆盖率
10. 单元测试常用断言库
✅ JUnit Assertions
✅ AssertJ (更好看、链式调用)
✅ Hamcrest
示例:
java
import static org.assertj.core.api.Assertions.assertThat;
@Test
void testSomething() {
String result = "hello";
assertThat(result)
.isNotNull()
.startsWith("he")
.endsWith("lo");
}
11. CI/CD流程:单元测试
Maven
mvn test
✅ 无Spring启动的单元测试 → 非常适合频繁在CI里跑
✅ 成本最低
✅ 总结
Spring Boot 单元测试=真正Java单元测试 + Mock依赖
✅ 不要动不动用 @SpringBootTest,它本质是集成测试,不是严格意义的单元测试
✅ 依赖外部组件要Mock
✅ 用正确层次,减少维护成本
✅ 让测试成为生产力,而不是负担