SpringBoot 测试实战:从配置到数据,5 招让你的测试代码既优雅又靠谱!
作为一名Java 领域的程序员,我见过太多团队踩过 “测试” 的坑 —— 要么是测试环境和生产环境配置对不上,测了个寂寞;要么是跑个数据层测试把数据库改得一团糟,要么是 Web 接口测试写得比业务代码还复杂…… 其实 SpringBoot 早就为我们准备了一套 “测试全家桶”,今天就从 5 个核心场景入手,带大家手把手搞定 SpringBoot 测试,让你的测试代码既省心又靠谱!
目录
1. 最直接的方式:用 application-test.yml
1. 用 @TestConfiguration 注解:局部生效的测试配置
2. 用 @Profile ("test"):按环境隔离配置
三、Web 环境模拟测试:不用启动 Tomcat,也能测接口
1. 用 @WebMvcTest:专注测 Controller 层
2. 用 @SpringBootTest+@AutoConfigureMockMvc:全环境模拟
1. 用 @Transactional+@Rollback:自动回滚事务
五、测试用例数据设定:用 @Sql 和 @CsvSource,告别硬编码
2. 用 @CsvSource: inline 方式准备数据(适合少量数据)
一、加载测试专用属性:告别 “配置混乱症”
你是不是也遇到过这种情况:本地跑测试用的数据库是test_db,生产用的是prod_db,每次切换环境都要改application.yml?其实 SpringBoot 早就想到了 “测试专属配置” 的方案,根本不用手动改!
你是不是也遇到过这种情况:本地跑测试用的数据库是test_db,生产用的是prod_db,每次切换环境都要改application.yml?其实 SpringBoot 早就想到了 “测试专属配置” 的方案,根本不用手动改!
1. 最直接的方式:用 application-test.yml
SpringBoot 默认会识别src/test/resources下的application-test.yml(或.properties),只要在测试类上加个@ActiveProfiles("test"),就能自动加载这套测试配置。比如这样:
@SpringBootTest
@ActiveProfiles("test") // 激活test环境的配置
public class UserServiceTest {
// 测试代码里直接用测试环境的配置,比如test_db的连接信息
}
这时你的application-test.yml里就可以放心写测试专用配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/test_db?useSSL=false
username: test_user
password: test123
好处是 “环境隔离” 做得彻底,测试配置和生产配置完全分开,再也不怕手抖提交错配置。
2. 更灵活的方式:@TestPropertySource
如果只是想临时覆盖几个配置,不用单独建配置文件,直接用@TestPropertySource注解就行。比如临时改个端口或者开关某个功能:
@SpringBootTest
@TestPropertySource(properties = {
"server.port=8081", // 测试时用8081端口
"spring.datasource.url=jdbc:mysql://localhost:3306/temp_test_db" // 临时用另一个数据库
})
public class OrderControllerTest {
// 测试代码
}
这种方式适合 “局部微调”,比如某个测试用例需要特殊配置,不用动全局文件。
二、加载测试专用配置:让 “测试 Bean” 按需生效
有时候我们需要测试专用的 Bean—— 比如生产环境用RedisCache,测试环境用CaffeineCache(本地缓存,更快更省心);或者生产用真实的PaymentService,测试用模拟的MockPaymentService。这时候怎么让 SpringBoot 只加载测试专用的配置呢?
1. 用 @TestConfiguration 注解:局部生效的测试配置
在测试类所在的包下,写一个测试专用的配置类,加上@TestConfiguration注解,这个配置只会在测试时生效,生产环境完全不会加载:
// 测试专用的缓存配置
@TestConfiguration
public class TestCacheConfig {
@Bean
public CacheManager cacheManager() {
// 测试用Caffeine缓存,不用连Redis
return CaffeineCacheManager.builder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
}
然后在测试类里用@Import引入这个配置,或者直接让测试类扫描到这个配置(比如放在同一个包下):
@SpringBootTest
@Import(TestCacheConfig.class) // 引入测试专用配置
public class ProductServiceTest {
@Autowired
private CacheManager cacheManager; // 这里注入的就是CaffeineCacheManager
// 测试代码
}
2. 用 @Profile ("test"):按环境隔离配置
如果测试专用配置比较多,也可以用@Profile("test")标记,配合前面的@ActiveProfiles("test")一起用。比如:
// 只有test环境才会加载这个配置
@Configuration
@Profile("test")
public class TestGlobalConfig {
// 测试专用的Bean1
@Bean
public PaymentService paymentService() {
return new MockPaymentService(); // 模拟支付服务,不用真的调用第三方
}
// 测试专用的Bean2
@Bean
public LogService logService() {
return new ConsoleLogService(); // 测试时日志直接打控制台,不用写文件
}
}
这样只要测试类激活了test环境,这些 Bean 就会自动加载,生产环境因为没有激活test profile,完全不会用到这些 Bean—— 隔离性拉满!
三、Web 环境模拟测试:不用启动 Tomcat,也能测接口
很多人测 Web 接口时,习惯直接启动整个 SpringBoot 应用(用@SpringBootTest默认不启动 Web 容器),然后用 Postman 或者 HttpClient 调接口。但这样有两个问题:一是启动慢(要加载所有 Bean),二是依赖外部环境(比如端口不能被占用)。其实 SpringBoot 提供了 “Web 环境模拟” 功能,不用启动真实 Tomcat,就能快速测接口!
1. 用 @WebMvcTest:专注测 Controller 层
如果只需要测试 Controller 层(比如参数校验、接口映射、返回值处理),用@WebMvcTest注解最合适,它只会加载 Controller 相关的 Bean(比如@Controller、@RestController、@RequestMapping等),其他 Bean(比如 Service、Repository)会自动忽略,启动速度超快:
// 只加载UserController相关的Bean
@WebMvcTest(UserController.class)
public class UserControllerTest {
// 注入MockMvc,用来模拟HTTP请求
@Autowired
private MockMvc mockMvc;
// 注入Mock的Service(因为@WebMvcTest不会加载真实的Service)
@MockBean
private UserService userService;
@Test
public void testGetUserById() throws Exception {
// 1. 模拟Service的返回值
User mockUser = new User(1L, "测试用户", 25);
Mockito.when(userService.getUserById(1L)).thenReturn(mockUser);
// 2. 模拟HTTP GET请求:/api/users/1
mockMvc.perform(get("/api/users/1")
.contentType(MediaType.APPLICATION_JSON))
// 3. 断言响应结果
.andExpect(status().isOk()) // 状态码200
.andExpect(jsonPath("$.id").value(1)) // 响应体id=1
.andExpect(jsonPath("$.name").value("测试用户")); // 响应体name=测试用户
}
}
这里有两个关键点:
@MockBean:用来模拟 Service 层(因为@WebMvcTest不加载真实 Service),可以自定义 Service 的返回值,完全脱离数据库依赖;MockMvc:用来模拟 HTTP 请求(GET/POST/PUT/DELETE),不用启动 Tomcat,直接在 JVM 内部调用 Controller,速度快到飞起。
2. 用 @SpringBootTest+@AutoConfigureMockMvc:全环境模拟
如果需要测试整个链路(Controller→Service→Repository),但又不想启动真实 Web 容器,可以用@SpringBootTest配合@AutoConfigureMockMvc:
@SpringBootTest
@AutoConfigureMockMvc // 自动配置MockMvc
@ActiveProfiles("test") // 加载测试配置
public class OrderControllerFullTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private OrderRepository orderRepository; // 真实的Repository(但用测试数据库)
@Test
public void testCreateOrder() throws Exception {
// 1. 准备测试数据(插入测试数据库)
Order testOrder = new Order(null, "测试订单", 99.9, LocalDateTime.now());
// 2. 模拟POST请求:/api/orders
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"测试订单\",\"amount\":99.9}")) // 请求体
.andExpect(status().isCreated()) // 状态码201
.andExpect(jsonPath("$.name").value("测试订单"));
// 3. 断言数据库确实插入了数据
Assertions.assertEquals(1, orderRepository.countByOrderName("测试订单"));
}
}
这种方式会加载整个 SpringBoot 上下文,但用MockMvc模拟 Web 请求,不用启动 Tomcat,兼顾了 “全链路测试” 和 “快速启动”。
四、数据层测试回滚:测完数据自动清空,再也不怕脏数据
数据层测试(Repository 层)最头疼的问题就是 “脏数据”—— 比如测试插入一条数据后,没删除,下次再跑测试就会因为 “唯一键冲突” 失败;或者测试删除数据后,把测试数据库的真实数据删了,导致其他测试用例报错。其实 SpringBoot 早就帮我们解决了这个问题:测试数据自动回滚!
1. 用 @Transactional+@Rollback:自动回滚事务
只要在测试类或测试方法上加@Transactional注解,SpringBoot 就会在测试结束后自动回滚事务,不管测试成功还是失败,都不会留下脏数据:
@SpringBootTest
@ActiveProfiles("test") // 用测试数据库
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
@Transactional // 开启事务
@Rollback(true) // 测试结束后回滚(默认就是true,可省略)
public void testSaveUser() {
// 1. 插入测试数据
User user = new User(null, "测试用户", 30);
User savedUser = userRepository.save(user);
// 2. 断言插入成功
Assertions.assertNotNull(savedUser.getId());
// 3. 测试结束后,事务自动回滚,数据库里不会留下这条数据
}
}
这里要注意:@Transactional只对@SpringBootTest或@DataJpaTest等加载了 Spring 上下文的测试有效,而且必须用支持事务的数据库(比如 MySQL、PostgreSQL,SQLite 不支持事务)。
2. 用 @DataJpaTest:专注数据层测试
如果只需要测试 Repository 层,不用加载 Service 和 Controller,可以用@DataJpaTest注解,它会自动配置 Repository 相关的 Bean(比如EntityManager、JpaRepository),并且默认开启事务回滚:
@DataJpaTest // 只加载Repository层Bean,默认事务回滚
@ActiveProfiles("test")
public class ProductRepositoryTest {
@Autowired
private ProductRepository productRepository;
@Test
public void testFindByProductName() {
// 1. 插入测试数据(测试结束后自动回滚)
Product product = new Product(null, "测试商品", 199.9);
productRepository.save(product);
// 2. 测试查询方法
Optional<Product> foundProduct = productRepository.findByProductName("测试商品");
// 3. 断言查询结果
Assertions.assertTrue(foundProduct.isPresent());
Assertions.assertEquals(199.9, foundProduct.get().getPrice());
}
}
@DataJpaTest比@SpringBootTest启动更快,因为它只加载数据层相关的 Bean,适合专注测试 Repository 的场景。
五、测试用例数据设定:用 @Sql 和 @CsvSource,告别硬编码
测试用例中经常需要准备测试数据 —— 比如测试 “查询用户列表”,需要先插入几条用户数据;测试 “删除订单”,需要先有一条存在的订单。如果每次都在测试方法里用repository.save()硬编码插入数据,代码会很臃肿,而且不好维护。SpringBoot 提供了两种更优雅的方式:@Sql和@CsvSource。
1. 用 @Sql:执行 SQL 脚本准备数据
如果测试数据比较多,可以写一个 SQL 脚本(比如test-data.sql),放在src/test/resources/sql目录下,然后用@Sql注解在测试前执行脚本:
-- src/test/resources/sql/test-user-data.sql
INSERT INTO user (id, name, age) VALUES (1, '张三', 20);
INSERT INTO user (id, name, age) VALUES (2, '李四', 25);
INSERT INTO user (id, name, age) VALUES (3, '王五', 30);
然后在测试类里用@Sql引入脚本:
@SpringBootTest
@ActiveProfiles("test")
@Sql("/sql/test-user-data.sql") // 测试前执行SQL脚本,插入测试数据
@Transactional // 测试结束后回滚,避免脏数据
public class UserRepositorySqlTest {
@Autowired
private UserRepository userRepository;
@Test
public void testFindByAgeGreaterThan() {
// 直接查询脚本插入的数据
List<User> users = userRepository.findByAgeGreaterThan(22);
// 断言结果:李四(25)和王五(30),共2条
Assertions.assertEquals(2, users.size());
}
}
@Sql还有更灵活的用法,比如指定脚本执行时机(executionPhase):
@Test
@Sql(
scripts = "/sql/test-user-data.sql",
executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD // 测试方法前执行(默认)
)
@Sql(
scripts = "/sql/clean-user-data.sql",
executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD // 测试方法后执行(手动清理)
)
public void testFindByName() {
// 测试代码
}
2. 用 @CsvSource: inline 方式准备数据(适合少量数据)
如果测试数据比较少,不想写单独的 SQL 脚本,可以用@CsvSource注解,直接在测试方法里写 CSV 格式的数据,配合@ParameterizedTest实现 “多组数据测试”:
@SpringBootTest
@ActiveProfiles("test")
@Transactional
public class UserServiceParamTest {
@Autowired
private UserService userService;
// 多组数据测试:参数1是年龄,参数2是预期是否成年
@ParameterizedTest
@CsvSource({
"18, true", // 18岁→成年
"17, false", // 17岁→未成年
"20, true", // 20岁→成年
"0, false" // 0岁→未成年
})
public void testIsAdult(Integer age, Boolean expected) {
// 调用Service方法
Boolean result = userService.isAdult(age);
// 断言结果
Assertions.assertEquals(expected, result);
}
}
这种方式适合 “参数化测试”—— 比如测试一个方法在不同输入下的输出,不用写多个测试方法,一行 CSV 就是一组测试数据,代码简洁又清晰。
最后:SpringBoot 测试的核心原则
其实 SpringBoot 测试的核心不是 “炫技”,而是 “隔离” 和 “高效”:
- 环境隔离:测试用测试配置、测试数据库,不影响生产;
- 依赖隔离:用
@MockBean模拟外部依赖(比如第三方接口、Redis),让测试不依赖外部服务; - 数据隔离:用事务回滚、SQL 脚本清理,避免脏数据;
- 高效启动:用
@WebMvcTest、@DataJpaTest等注解只加载必要的 Bean,减少启动时间。
掌握了这 5 个场景,你就能应对 90% 以上的 SpringBoot 测试需求,写出既优雅又靠谱的测试代码。下次再有人说 “测试代码难写”,你就把这篇文章甩给他!
如果大家在实际项目中遇到特殊的测试场景(比如测试 WebSocket、定时任务),或者对某个测试注解有疑问,欢迎在评论区留言,我们一起探讨解决方案!
更多推荐


所有评论(0)