你真的需要每个测试都把整个 Spring Boot 应用启动一遍吗?
🏆本文收录于《滚雪球学SpringBoot 3》:
https://blog.csdn.net/weixin_43970743/category_12795608.html,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。
本专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。 如果想快速定位学习,可以看这篇【SpringBoot3教程导航帖】https://blog.csdn.net/weixin_43970743/article/details/151115907,你想学习的都被收集在内,快速投入学习!!两不误。
若还想学习更多,可直接前往《滚雪球学SpringBoot(全版本合集)》:https://blog.csdn.net/weixin_43970743/category_11599389.html,涵盖SpringBoot所有版本教学文章。
演示环境说明:
- 开发工具:IDEA 2021.3
- JDK版本: JDK 17(推荐使用 JDK 17 或更高版本,因为 Spring Boot 3.x 系列要求 Java 17,Spring Boot 3.5.4 基于 Spring Framework 6.x 和 Jakarta EE 9,它们都要求至少 JDK 17。)
- Spring Boot版本:3.5.4(于25年7月24日发布)
- Maven版本:3.8.2 (或更高)
- Gradle:(如果使用 Gradle 构建工具的话):推荐使用 Gradle 7.5 或更高版本,确保与 JDK 17 兼容。
- 操作系统:Windows 11
反问一句:**你希望上线后才知道“对方改了字段名”,还是在 PR 阶段就让测试把它按在地上摩擦?**🙂
前言
反问一句:**你是想测功能,还是想测耐心?**🙂
0. 先把“官方定义”摆在桌面上
Spring Boot 官方文档明确提到:除了 @SpringBootTest 之外,还提供了许多注解用于测试应用程序的“更具体切片”。也就是说,切片测试是官方路线,不是什么民间偏方。
同时,Spring Boot 还专门列了一个“Test Slices”清单,告诉你各种 @…Test 注解默认导入哪些自动配置。
这两句的潜台词很直白:
@SpringBootTest:你需要“整机联调”,那就上。- Test Slices:你只想验证某一层的行为,就别把全家桶端出来。
1) @SpringBootTest 这种“大而全”测试的性能瓶颈
1.1 它到底“全”在哪里?
官方说得很客气:@SpringBootTest 会通过 SpringApplication 来创建测试用的 ApplicationContext,以便启用 Spring Boot 的各种特性。
翻译成人话:
- 会走自动配置(Auto-Configuration):你项目里引了啥 starter,它就可能配啥。
- 会做组件扫描:配置类、Bean 定义、各种
@Configuration都可能进来。 - 可能还会准备 Web 环境(取决于
webEnvironment)。 - 上下文启动成本高:类多、Bean 多、条件装配多,启动就慢。
你会发现瓶颈通常不在“跑测试方法”,而在——加载上下文。
1.2 你以为你在测业务,其实你在测“启动速度”
我见过最经典的尴尬场景:
- 你写了 200 个测试类
- 每个类都
@SpringBootTest - 其中 180 个其实只是测一些简单逻辑、或者测 Controller 的参数校验
最后 CI 直接变成“上下文加载马拉松”。
更要命的是:上下文还可能被你自己“整碎”。比如:
- 测试类上加了一堆
@TestPropertySource或 profiles,导致上下文无法复用。 - 你动不动
@DirtiesContext,上下文缓存直接失效,等于每次重启应用。 - 你的 Auto-Config 里顺手连外部依赖(比如某个定制 starter 启动时就去探测远程服务),CI 环境一抖就崩。
1.3 什么时候它“必须上”?
我对 @SpringBootTest 的态度其实很中立:该用就用。比如:
- 你要测多个层的真实协作:Controller → Service → Repository → DB
- 你要验证条件装配、Bean 覆盖、配置属性绑定是否正确
- 你要做端到端集成测试(甚至随机端口跑 HTTP 调用)
但——如果你只是测 MVC 映射、JPA 查询、JSON 序列化,这种“局部行为”,那你上 @SpringBootTest 就像:为了削一根铅笔,把木工车间整个开起来。帅是帅,划不来。
2) @WebMvcTest:只加载 Controller 层组件(以及它的“最常见坑”)
2.1 它到底加载什么?
@WebMvcTest 的官方 API 文档写得很硬核:它会限制组件扫描范围,主要聚焦在 Web 层相关 Bean(例如 @Controller、@ControllerAdvice、@JsonComponent、各种 Converter、Filter、HttpMessageConverter 等),并且默认会自动配置 Spring Security 与 MockMvc。
这句话信息量很大:
- 你能直接注入
MockMvc做请求级别测试 - 你不会把 Service/Repository 自动带进来
- 你如果用了 Spring Security,它还会帮你把安全过滤器链也配出来(这点既是优点,也是“坑源”)
2.2 一个很“像人类”的误解:我以为它会把 Service 也注入
不会。它不会。它真的不会。🙂
@WebMvcTest 的哲学是:你测 Controller,就别把 Service 也拉来陪跑。
所以你有两种常见做法:
- 用
@MockBean把依赖的 Service mock 掉(最常用) - 或者用
@Import显式引入你想带进来的少量配置(谨慎)
2.3 实战代码:用 @WebMvcTest 测参数校验、异常处理与返回 JSON
假设我们有一个 Controller:
@RestController
@RequestMapping("/api/books")
class BookController {
private final BookService bookService;
BookController(BookService bookService) {
this.bookService = bookService;
}
@PostMapping
public BookDto create(@Valid @RequestBody CreateBookCmd cmd) {
return bookService.create(cmd);
}
@GetMapping("/{id}")
public BookDto get(@PathVariable Long id) {
return bookService.get(id);
}
}
record CreateBookCmd(
@NotBlank(message = "title 不能为空")
@Size(max = 80, message = "title 太长了")
String title
) {}
record BookDto(Long id, String title) {}
然后我们写测试:
@WebMvcTest(BookController.class)
class BookControllerWebMvcTest {
@Autowired MockMvc mockMvc;
@MockBean BookService bookService;
@Test
void should_return_400_when_title_blank() throws Exception {
mockMvc.perform(post("/api/books")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title": " "}
"""))
.andExpect(status().isBadRequest())
.andExpect(content().string(org.hamcrest.Matchers.containsString("title 不能为空")));
}
@Test
void should_create_book() throws Exception {
Mockito.when(bookService.create(Mockito.any()))
.thenReturn(new BookDto(1L, "Spring in Action"));
mockMvc.perform(post("/api/books")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title": "Spring in Action"}
"""))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.title").value("Spring in Action"));
}
}
你看这个测试的“爽点”在哪里?
- 不启动服务器
- 不连数据库
- 直接验证:路由映射 + JSON 反序列化 + 参数校验 + 响应序列化
- 速度通常比
@SpringBootTest轻快一大截
2.4 @WebMvcTest 的两个高频“翻车点”
翻车点 A:Spring Security 把你挡在门外
因为它默认会自动配置 Spring Security。
于是你可能写了个“最普通的 GET”,结果返回 401/403,心态瞬间爆炸:
“我只是想测 Controller,你为什么要考我安全?”
解决思路(常用三选一):
- 在测试里使用
@WithMockUser - 或者配置
SecurityFilterChain的测试替身(测试配置类) - 或者用
@AutoConfigureMockMvc(addFilters = false)暂时关过滤器(适合非安全相关测试,但别滥用)
翻车点 B:Controller 依赖的 Bean 没进上下文
因为它只加载 Web 层,Service 不会自动出现。
所以你要用 @MockBean 把缺的依赖补上。
3) @DataJpaTest / @DataRedisTest:专注持久层测试(别拿它们去测 Service)
3.1 @DataJpaTest:JPA 组件专用“切片”
官方 API 文档说得很明确:
- 只启用与 Data JPA 测试相关的自动配置
- 组件扫描限制在 JPA repositories 与
@Entity实体 - 默认是事务性的,并且每个测试结束会回滚
- 默认会用嵌入式内存数据库替换 DataSource(可用
@AutoConfigureTestDatabase改)
我特别喜欢它的两个默认行为:
- 事务 + 自动回滚:你不用自己 cleanup 数据,测试之间天然隔离。
- 只测持久层:Repository 查询写错了,它能很快、很准地揪出来。
3.2 实战代码:测一个“看似简单但很容易写错”的查询
假设你有实体与仓库:
@Entity
class Book {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 80)
private String title;
protected Book() {}
public Book(String title) { this.title = title; }
public Long getId() { return id; }
public String getTitle() { return title; }
}
interface BookRepository extends JpaRepository<Book, Long> {
List<Book> findByTitleContainingIgnoreCase(String keyword);
}
测试:
@DataJpaTest
class BookRepositoryDataJpaTest {
@Autowired BookRepository bookRepository;
@Autowired TestEntityManager em;
@Test
void should_find_books_by_title_keyword_ignore_case() {
em.persist(new Book("Spring Boot Guide"));
em.persist(new Book("Kotlin for Spring"));
em.persist(new Book("Distributed Systems"));
em.flush();
var result = bookRepository.findByTitleContainingIgnoreCase("spring");
org.assertj.core.api.Assertions.assertThat(result)
.extracting(Book::getTitle)
.containsExactlyInAnyOrder("Spring Boot Guide", "Kotlin for Spring");
}
}
这里的关键不是“能跑”,而是:
- 用真实 JPA 行为验证方法命名派生查询是否符合预期
- 避免你在 Service 层用假数据自嗨(很多 bug 就是这么自嗨出来的)
3.3 @DataRedisTest:Redis 组件专用“切片”
官方 API 文档同样清晰:
- 只启用与 Data Redis 测试相关的自动配置
- 组件扫描限制在 Redis repositories 与
@RedisHash实体
**它的定位很明确:**你测的是 Redis 映射、Repository 行为、序列化策略、TTL 等持久层相关内容,而不是测 Controller 或业务编排。
一个现实提醒(别装):Redis 测试最容易被“环境”坑
JPA 默认能给你内存数据库兜底,但 Redis 没那么统一。很多团队会用:
- Testcontainers 拉一个真实 Redis
- 或者在 CI 里起 Redis 服务
这不是切片本身的问题,是 Redis 的性质决定的:你最好给它一个真实环境,否则你测出来的东西可能“像 Redis”,但不一定“是 Redis”。(这句话我说得已经很克制了😅)
3.4 实战代码:用 @DataRedisTest 测 @RedisHash 与 Repository
@RedisHash("book")
class BookCache {
@Id
private String id;
private String title;
protected BookCache() {}
BookCache(String id, String title) {
this.id = id;
this.title = title;
}
public String getId() { return id; }
public String getTitle() { return title; }
}
interface BookCacheRepository extends CrudRepository<BookCache, String> {}
测试:
@DataRedisTest
class BookCacheDataRedisTest {
@Autowired BookCacheRepository repo;
@Test
void should_save_and_find_book_cache() {
repo.save(new BookCache("1", "Spring Boot Guide"));
var found = repo.findById("1");
org.assertj.core.api.Assertions.assertThat(found)
.isPresent()
.get()
.extracting(BookCache::getTitle)
.isEqualTo("Spring Boot Guide");
}
}
如果你跑不起来,十有八九不是你代码写错,而是 Redis 环境没准备好——这时候别急着怀疑人生,先怀疑基础设施(这很“工程师”,也很“人类”🙂)。
4) @JsonTest:测试 JSON 序列化与反序列化(这玩意儿真的值)
4.1 它“切”出来的到底是什么?
官方 API 文档写得很直:
@JsonTest只启用与 JSON 测试相关的自动配置- 组件扫描限制在
@JacksonComponent(以及 Jackson Module 等) - 默认会初始化
JacksonTester / JsonbTester / GsonTester字段(可用@AutoConfigureJsonTesters更细控制)
你看,这就是典型的“我只想测 JSON,其他都别来烦我”。
4.2 为什么我强烈建议你写 @JsonTest?
因为 JSON 出 bug 的方式非常阴险:
- 字段名大小写、下划线、别名映射
LocalDateTime格式- 忽略字段(
@JsonIgnore) - 枚举序列化策略
null到底要不要输出
这些东西在 Controller 测试里也能覆盖一部分,但 Controller 测试太“综合”,一旦失败你很难第一时间定位到底是 MVC 绑定问题、还是 JSON 映射问题。
而 @JsonTest 的价值就是:**把变量收敛到只有 JSON。**失败了就别狡辩,基本就是序列化/反序列化本身的问题。
4.3 实战代码:用 JacksonTester 验证序列化输出
DTO:
class PaymentResponse {
@com.fasterxml.jackson.annotation.JsonIgnore
private String internalId;
@com.fasterxml.jackson.annotation.JsonProperty("payment_amount")
private java.math.BigDecimal amount;
@com.fasterxml.jackson.annotation.JsonFormat(shape = com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ss")
private java.time.LocalDateTime paidAt;
// getters/setters 省略(别嫌我偷懒,我怕你嫌我太啰嗦🙂)
}
测试:
@JsonTest
class PaymentResponseJsonTest {
@Autowired private JacksonTester<PaymentResponse> json;
@Test
void should_serialize_payment_response() throws Exception {
var dto = new PaymentResponse();
// dto.setInternalId("SECRET-42");
dto.setAmount(new java.math.BigDecimal("42.50"));
dto.setPaidAt(java.time.LocalDateTime.of(2026, 1, 12, 10, 30, 0));
var content = json.write(dto);
org.assertj.core.api.Assertions.assertThat(content).doesNotHaveJsonPath("$.internalId");
org.assertj.core.api.Assertions.assertThat(content).extractingJsonPathNumberValue("$.payment_amount")
.isEqualTo(42.50);
org.assertj.core.api.Assertions.assertThat(content).extractingJsonPathStringValue("$.paidAt")
.isEqualTo("2026-01-12T10:30:00");
}
}
这段测试的观感很“干净”:
- 不关心 Spring MVC
- 不关心数据库
- 不关心应用上下文里有什么 Service
只验证:对象 ↔ JSON 的契约有没有被你写崩。
5) 选型心法:什么时候用切片,什么时候回到 @SpringBootTest?
我给你一个非常“上班族真实”的判断标准(没那么学术,但好用):
5.1 你关心的是“边界契约”,优先切片
- Controller 的路由、参数绑定、校验、异常映射 →
@WebMvcTest - Repository 的查询、映射、事务回滚 →
@DataJpaTest - Redis Repository /
@RedisHash映射 →@DataRedisTest - JSON 序列化反序列化 →
@JsonTest
5.2 你关心的是“跨层协作”,再上 @SpringBootTest
比如:
- 下单:Controller 收请求 → Service 编排 → Repository 写库 → 发消息
这种你切片切不出来真实信心,就该@SpringBootTest上场。
5.3 切片测试不是“降级”,它是“更精准”
很多人潜意识觉得:
“集成测试更高级,切片测试像小儿科。”
我每次听到都想笑:精准才高级。
你把问题空间缩小,失败定位更快,反馈更短,CI 更稳定——这才是工程的正道。
6) 最后一点“带情绪但不偏激”的真话:别把测试写成自我感动
测试的目的从来不是“让覆盖率好看”,而是:
- 让你改代码时没那么心虚
- 让你上线前睡得踏实一点
- 让你出事故时能迅速缩小嫌疑范围
@SpringBootTest 很好,但它不是万金油;Test Slices 很香,但它也不是万能。真正成熟的姿势是:
- 大量切片测试覆盖“边界契约”
- 少量
@SpringBootTest覆盖“跨层协作” - 再配上少量端到端测试兜底“真实链路”
所以我也想再反问你一句(别嫌我啰嗦😅):
你写测试,是想证明“我写过测试”,还是想在未来某天救自己一命?
如果是后者——欢迎来到切片测试的世界,它不花哨,但真的管用。
🧧福利赠与你🧧
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G PDF编程电子书、简历模板、技术文章Markdown文档等海量资料。
ps:本文涉及所有源代码,均已上传至Gitee:
https://gitee.com/bugjun01/SpringBoot-demo开源,供同学们一对一参考 Gitee传送门https://gitee.com/bugjun01/SpringBoot-demo,同时,原创开源不易,欢迎给个star🌟,想体验下被🌟的感jio,非常感谢❗
🫵 Who am I?
我是 bug菌:
- 热活跃于 CSDN:
https://blog.csdn.net/weixin_43970743| 掘金:https://juejin.cn/user/695333581765240| InfoQ:https://www.infoq.cn/profile/4F581734D60B28/publish| 51CTO:https://blog.51cto.com/u_15700751| 华为云:https://bbs.huaweicloud.com/community/usersnew/id_1582617489455371| 阿里云:https://developer.aliyun.com/profile/uolxikq5k3gke| 腾讯云:https://cloud.tencent.com/developer/user/10216480/articles等技术社区; - CSDN 博客之星 Top30、华为云多年度十佳博主&卓越贡献奖、掘金多年度人气作者 Top40;
- 掘金、InfoQ、51CTO 等平台签约及优质作者;
- 全网粉丝累计 30w+。
更多高质量技术内容及成长资料,可查看这个合集入口 👉 点击查看:https://bbs.csdn.net/topics/612438251 👈️
硬核技术公众号 「猿圈奇妙屋」https://bbs.csdn.net/topics/612438251 期待你的加入,一起进阶、一起打怪升级。
- End -
- 点赞
- 收藏
- 关注作者
评论(0)