十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot测试注解@SpringBootTest:原理、实战与性能优化

Spring Boot测试注解@SpringBootTest:原理、实战与性能优化 1. SpringBootTest到底是什么一个注解背后的完整Spring容器1.1 从一次亲历的测试报错说起SpringBootTest这个注解可以说是Spring Boot项目里的常客但也是很多人一上来就被劝退的地方。我第一次在项目里用它是想写一个简单的登录接口测试。测试类一共没几行一运行却傻眼了控制台刷了一堆Caused by最上面写着“Failed to load ApplicationContext”往下一翻数据库连接超时、Redis连接失败、还有几个Bean找不到的报错。我当时的第一反应是我不就是想测一个方法吗为什么要把数据库、缓存、消息队列全都拉起来后来才反应过来这不是bug这是SpringBootTest的设计风格。它的核心作用就一句话启动完整的Spring应用上下文ApplicationContext把项目里所有由Spring管理的Bean装配好然后才去执行你写的测试方法。换句话说加上SpringBootTest的那一刻你的测试就不再是单点验证而是一个微型的“集成测试”——它把一个近似真实运行时的项目原原本本搬到了测试环境里。正因为如此SpringBootTest才会成为Spring Boot开发中最常用的测试注解之一。它解决的核心痛点是很多问题只有在Bean被完整装配、依赖被正确注入、AOP切面正常生效的情况下才会暴露出来。你在普通单元测试里用Mock对象搭出来的“理想世界”往往发现不了这类问题。这篇文章我就把这个注解翻个底朝天把原理、用法、踩坑经验一并梳理出来。1.2 SpringBootTest和普通单元测试的根本区别要理解这个注解的价值先要对测试类型有个粗略的认知。平时说的“单元测试”最朴素的写法就是直接new一个对象把它的依赖用Mockito或者其他工具打桩然后调用目标方法做断言。它的优点很明显启动快、隔离性好、跑一遍只要几毫秒。但它也有个天然短板你只验证了“一个类在自己的小天地里逻辑对不对”没法验证“这个类和外面的其他Bean装配在一起时还对不对”。SpringBootTest走的是另一条路。它会加载整个Spring容器让Configuration、Component、Service、Repository、Bean方法这些该生效的全部生效。拿开车来类比普通单元测试是把发动机拆下来接上油管和电瓶单独转一圈看转速正不正常SpringBootTest则是整车通电踩下油门让发动机和变速箱、电控系统、传感器一起工作更接近真实上路的感觉。这个区别决定了使用场景完全不同。如果你的目标是验证一个工具类的日期格式转换逻辑完全没有必要上SpringBootTest但如果你想测试一个Service方法的事务行为或者一个Controller请求从进来到响应全链路的串联情况SpringBootTest几乎是不可替代的。它不是用来替代单元测试的而是用来补齐单元测试覆盖不到的那一块拼图。1.3 为什么需要SpringBootTest而不是直接new对象有人可能会问那我不加SpringBootTest直接在测试类里把Service、Repository手动new出来行不行技术上不是不行但会遇到几个很现实的问题。第一依赖注入失效。Spring里的Service往往又依赖了其他Bean可能还有Value注入的配置项、Autowired注入的依赖。手动new一个Service这些注入全部不会发生你只能手动塞参数塞的过程中很容易漏掉一个测试跑起来报空指针你还得回头排查到底是哪一个依赖没给。第二AOP和事务失效。Service方法上标着Transactional或者被切面拦截了日志、权限控制、缓存逻辑。这些横切逻辑是靠Spring容器在运行期生成代理对象实现的。你手动new出来的对象就是原始对象事务和切面统统不会生效。这样测试结果和生产行为完全不一致测了等于没测。第三配置项丢失。很多Service的方法会读取application.yml里的配置比如订单超时时间、积分比例。加载不到这些配置你只能把值写成死代码测试环境和生产割裂后面调整配置时还得再改一遍测试代码。所以结论很清楚当你的测试对象已经深度依赖Spring容器的时候直接用SpringBootTest把容器拉起来才是正确姿势。当然代价就是启动慢、依赖环境多这个“性价比”的问题我会在第四章单独聊。先继续把原理讲透。2. 工作原理拆解SpringBootTest是如何把“整个项目”搬进测试的2.1 TestContext框架与上下文缓存SpringBootTest不是凭空创造了一套独立机制它建立在Spring TestContext Framework之上。整个体系的核心是一个叫TestContextManager的类由它负责管理测试类的生命周期在某个测试方法执行前先初始化测试实例、注入依赖、准备事务环境方法执行后再做清理。这些动作对开发者几乎透明你只看到测试方法跑起来了背后其实有一整条流程在支撑。在这个框架里最值得一提的设计是ApplicationContext缓存。你可能遇到过这种情况测试类有A、B、C三个类每个类都加了SpringBootTest跑完之后发现总共只启动了一次Spring容器后面两个类跑得快很多。这就是上下文缓存的功劳。TestContext框架会缓存加载好的ApplicationContext下次遇到配置相同的测试类直接复用缓存里的容器不会再次启动。这里有个隐藏的细节值得注意上下文缓存不是无限复用的它要求所有影响容器构成的配置都一致。比如你在测试类A上用了ActiveProfiles(test)测试类B没有指定Profile那这两个类一定不能共享缓存因为环境变了容器行为可能就变了。再比如某次测试用MockBean替换了一个Bean这个容器实例就被“污染”了后续不同配置的测试都会重新创建上下文而不是复用。下面这些条件任何一个不满足都会触发一次新的上下文创建启动类SpringBootConfiguration不一致。加载的配置文件、属性源不一致。ActiveProfiles指定的环境不一致。MockBean、MockitoBean等Mock注解的Bean列表不一致。ContextConfiguration指定的配置类不一致。自定义的ApplicationContextInitializer不一致。所以当你的测试类比较多建议优先保证它们共享同一个上下文配置能省下不少跑测试的时间。这一点在做大型项目时尤其重要后面第四章还会展开。2.2 从ContextConfiguration到SpringBootTest的演进早期用Spring写集成测试经常见到的注解是ContextConfiguration。这个注解很灵活需要你自己指定加载哪些配置类比如ContextConfiguration(classes {DataSourceConfig.class, MybatisConfig.class})。缺点也很明显配置类一旦变多维护这个列表就很痛苦而且你很有可能漏掉某个Bean导致测试跑一半报NoSuchBeanDefinitionException。SpringBootTest对这个过程做了很大的简化。它本身被ContextConfiguration注解了元信息同时自动添加了一个特殊的上下文加载器测试时只需要有一个类标注了SpringBootConfiguration通常就是我们的启动类SpringBootTest就会自动找到它把它当成配置源然后按照Spring Boot自动装配的规则去加载所有需要的AutoConfiguration。这里的核心价值体现在两个关键词上一个是自动一个是完整。自动意味着你不需要关心启动类在哪里、配置类有哪些完整意味着你在生产环境里能用的功能比如配置绑定、条件装配、内置的Tomcat、各种Starter自动配置在测试里几乎都保留。这也是为什么SpringBootTest在Spring Boot 3.x、Spring AI等新项目里依然是测试体系的地基。从这层关系也能看出SpringBootTest不是要取代Spring TestContext而是在它上面做了面向Spring Boot项目的封装和增强。你能用ContextConfiguration做到的它基本都能做而且更省心。这也是为什么面试官经常会问“SpringBootTest和ContextConfiguration有什么关系”——就是为了考察你是否理解这套测试机制的层次结构。2.3 webEnvironment的四种模式怎么选SpringBootTest里有一个参数叫webEnvironment默认值是WebEnvironment.MOCK很多新手没注意过其实它决定了测试时Web环境怎么模拟用对了能省掉很多奇怪的错误。第一种MOCK。默认模式不会启动真实的嵌入式Web服务器Tomcat、Jetty这些都不会起来而是通过Spring的MockHttpServletRequest、MockHttpServletResponse模拟一套Web请求环境。这个模式下做接口测试通常会配合AutoConfigureMockMvc一起用注入MockMvc对象让它代替真实的HTTP请求去访问Controller。优点是速度快、无端口占用缺点是不是真实的HTTP协议链路一些和Servlet容器初始化有关的细节测不到。第二种RANDOM_PORT。启动一个真实的嵌入式Web服务器端口随机分配一般不用你操心端口冲突。这种模式下你可以直接用TestRestTemplate或者HTTP客户端对本地随机端口发一个真实请求。整个链路都是真实的包括Servlet容器初始化、过滤器、拦截器、异步请求处理全都走一遍。适合做端到端的冒烟验证。第三种DEFINED_PORT。和RANDOM_PORT类似但端口固定取自server.port配置。这种模式在本地单机调试时还好一旦并发跑测试容易端口冲突所以CI环境不建议用。如果你确实需要固定端口比如要和外部系统联调可以用这个模式。第四种NONE。不提供任何Web环境Spring容器照常启动但不做Web层的装配。如果你只是测试Service或者Repository不想被Web环境启动拖慢速度用NONE比较合适。我自己的习惯是测试Service、Repository这类纯业务逻辑用默认MOCK就够了甚至有时配合NONE来加快启动需要走完整HTTP链路时选RANDOM_PORT避免端口冲突DEFINED_PORT只在特殊联调场景才用。搞清楚了这四者的区别面试时被问到也能快速给出答案。3. 实操篇写一个真实可运行的SpringBootTest3.1 依赖引入与测试目录结构开始写代码之前先确认工程里有没有spring-boot-starter-test这个依赖。只要你用的是Spring Initializr创建的项目pom.xml里一般都会自动带上。如果是老项目手动补一下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency注意scope是test它不会被打进生产包。这个starter会帮你把JUnit 5、Spring Test、AssertJ、Mockito、JSONPath等一堆测试相关库都引进来不用挨个手工加。如果你项目用的是Gradle对应写法也类似。需要特别提醒的是不要随意排除spring-boot-starter-test里的依赖除非你明确知道自己在做什么否则很容易出现“类找不到”或者“方法不存在”的诡异问题。目录结构上Spring Boot默认约定测试代码放在src/test/java目录下配置文件放在src/test/resources目录下。这里有个很容易被忽略的点测试类最好和被测试类所在的包名保持一致。比如被测试的类是com.example.demo.service.UserService那测试类就放在com.example.demo.service包下面取名叫UserServiceTest。这样不仅能保证包级私有成员能访问到也方便Maven的surefire插件按约定自动找到测试类。如果你的测试需要连一个独立的测试数据库强烈建议在src/test/resources下放一个application.yml里面配置好测试环境的数据库地址、日志级别等。测试环境下的配置文件优先级一般高于src/main/resources下的同名文件这样主工程配置不用动测试又不至于连到生产数据库上。这个习惯很小但能救命的次数不少。3.2 一个完整的Service层测试用例接下来写一个完整示例。假设有一个订单服务方法里用到了事务注解也读取了配置项。先写一个简单的ServiceService public class OrderService { Value(${order.default-duration:30}) private int defaultDuration; Transactional public Order createOrder(Long userId, String productId) { // 这里省略实际的存储逻辑真实项目里一般会调用mapper或repository return new Order(userId, productId, LocalDateTime.now().plusMinutes(defaultDuration)); } }对应这个Service写一个最基础的SpringBootTest测试类SpringBootTest class OrderServiceTest { Autowired private OrderService orderService; Test void shouldCreateOrderWithDefaultDuration() { Order order orderService.createOrder(1001L, P-2024-0001); Assertions.assertThat(order.getUserId()).isEqualTo(1001L); Assertions.assertThat(order.getProductId()).isEqualTo(P-2024-0001); Assertions.assertThat(order.getExpireTime()).isAfter(LocalDateTime.now()); } }这段代码看着简单里面有几个点很关键。首先是Autowired private OrderService orderService这个注入能成功前提就是类上的SpringBootTest已经把Spring容器启动起来了。容器里所有被Spring管理的Bean都可以这么拿到至于你的Service依赖了哪个Mapper、哪个FeignClient都不用关心容器会按规则装配好。其次是Value(${order.default-duration:30})这个配置在测试环境里如果没有显式指定就会取默认值30。如果想要指定值就在src/test/resources/application.yml里加一行。从这也可以看出来SpringBootTest测的不只是代码逻辑顺带把配置加载机制也测了这是普通单元测试做不到的。再说断言。我习惯用AssertJ的链式断言就是Assertions.assertThat(...)这种写法。spring-boot-starter-test默认带上了AssertJ不需要额外导入。如果你的团队更习惯JUnit自带的assertEquals也没问题不过链式写法在出错时提示信息更友好一点哪个值不对、期望是什么、实际是什么一目了然。3.3 测试Controller层结合MockMvcService测通之后下一步往往就是接口层。这时推荐的组合是SpringBootTest配合AutoConfigureMockMvc。先看代码SpringBootTest AutoConfigureMockMvc class OrderControllerTest { Autowired private MockMvc mockMvc; Test void shouldReturnOrderInfo() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get(/order/1001)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath($.userId).value(1001)) .andExpect(MockMvcResultMatchers.jsonPath($.productId).value(P-2024-0001)); } }这里用的是MOCK模式MockMvc本质上不走真实的网络端口而是模拟请求进入DispatcherServlet再一路走到Controller、Service、Mapper。所以速度和安全性都比真实HTTP请求更好也不会有端口冲突的问题。整个请求-响应链路只要不涉及真正的Socket通信基本都能覆盖到。MockMvc的链式调用比较长建议把静态导入优化一下代码会更干净import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; mockMvc.perform(get(/order/1001)) .andExpect(status().isOk()) .andExpect(jsonPath($.productId).value(P-2024-0001));jsonPath是按JSON结构取值的关键字语法$表示根节点。如果你的返回结果里是数组可以写$.items[0].name这种形式。遇到接口需要登录态时可以用MockMvc的with方法比如with(request - { request.setAttribute(userId, 1001); return request; })或者配一个MockMvcConfigurer统一设置请求头。这些技巧不用背用到的时候去查文档即可但要记住方向MockMvc给了你很多“后门”来模拟真实请求里不方便构造的东西。3.4 事务回滚与数据准备在Service测试里还有一个让人迷惑的问题测试方法往数据库里插了数据跑完之后数据还在吗答案取决于你有没有加Transactional。默认情况下加了Transactional的测试方法会在方法结束时自动回滚事务也就是你插入的数据不会真正落库。这个设计其实是特意为之它可以让多个测试方法互不干扰每次跑都是干净的数据库状态。但这里有一个常见的坑如果被测试方法内部自己开启了事务或者调用了其他使用REQUIRES_NEW传播级别的事务方法组合起来可能不符合你想象的“全部回滚”。更稳妥的做法是在测试类的类级别加上Transactional让整个测试方法处于一个事务中Spring Test会在结束时统一回滚。不过要留意这也会让事务边界变得更“理想化”和生产环境的真实调用路径不完全一致。如果你的确有需要保留数据的场景可以在方法上加Rollback(false)或者Commit告诉Spring测试完成后不要回滚把数据真正提交。还有一种情况是把测试数据放在SQL脚本里用Sql注解在测试前执行SpringBootTest Transactional Sql(scripts /sql/init_order.sql, executionPhase Sql.ExecutionPhase.BEFORE_TEST_METHOD) class OrderServiceTest { // 测试方法执行前先执行init_order.sql }Sql的executionPhase可以设置为BEFORE_TEST_METHOD或者AFTER_TEST_METHOD灵活控制脚本执行时机。脚本文件放在src/test/resources/sql目录下即可。如果你用的不是JUnit 5而是TestNG也可以通过扩展配置达到类似效果但Spring Boot项目里还是JUnit 5占主流。关于测试数据我强烈建议不要依赖数据库里已有的脏数据。测试用例应该自己创建、自己清理尽量做到每个测试方法独立可重复跑。这样哪怕某天有人手动改了开发库的数据你的测试结果也不会受影响。这算是我踩过很多次坑之后的一条铁律。4. 性能取舍什么时候用SpringBootTest什么时候别用4.1 全量启动 vs 切片测试SpringBootTest虽然好用但它不是万能钥匙最大的问题就是慢。一个中大型项目依赖了几十个数据源、消息队列、缓存组件之后启动一次Spring容器可能要十来秒甚至更久。如果每个测试类都用SpringBootTest整个测试套件跑下来会让人怀疑人生。好在Spring Boot提供了一系列“切片测试”注解每个注解只加载和某个技术栈相关的组件启动速度快很多见下表切片注解用途加载范围WebMvcTest只测试Controller层装配MVC相关组件不加载Service、RepositoryDataJpaTest只测试JPA Repository装配JPA相关组件默认使用嵌入式数据库MybatisTest只测试MyBatis Mapper装配MyBatis相关组件JsonTest只测试JSON序列化装配Jackson等JSON框架DataRedisTest只测试Redis仓库装配Redis相关组件切片测试的核心思想是把测试范围缩小到某个“切片”不加载无关Bean因此启动快、隔离性好。但代价是你想测Controller时Service和Repository不会自动加载需要通过MockBean把依赖Mock掉。这实际上是回到了“单元测试轻量集成”的混合模式。举例说明测一个BookController用WebMvcTest可以这样写WebMvcTest(BookController.class) class BookControllerTest { Autowired private MockMvc mockMvc; MockBean private BookService bookService; Test void shouldGetBookList() throws Exception { when(bookService.list()).thenReturn(List.of(new Book(978-7-111-00001-0, Spring Boot实战))); mockMvc.perform(get(/books)) .andExpect(status().isOk()) .andExpect(jsonPath($.length()).value(1)); } }这样只加载了MVC层的东西Service直接打桩返回固定数据整个测试跑起来用不了几百毫秒和SpringBootTest的全量启动比简直是天壤之别。所以我的建议是能切片就切片只有在需要验证跨层集成时再用SpringBootTest。4.2 上下文缓存的复用条件与坑前面提过TestContext框架会缓存ApplicationContext。但这个缓存非常“娇气”一旦测试类上存在以下情况缓存就会失效为该测试类新建一个上下文使用了不同的ActiveProfiles使用了不同的TestPropertySource属性使用了MockBean且Mock的Bean名称或类型不同通过ContextConfiguration指定了不同的配置类自定义了不同的ApplicationContextInitializer其中MockBean这个坑最隐蔽。假设测试类A里MockBean了一个UserService测试类B没有。A跑完后缓存里已经存了一个带Mock Bean的容器。B来了因为配置不同算不到同一个上下文于是又启动一次真实容器。如果你有20个测试类都各自Mock了不同的Bean那就等于启动了20次容器速度慢得惊人。解决办法倒不复杂尽量少用MockBean能切片测试就切片。如果注定要Mock尽量把Mock集中在少数几个配置相同的测试类里。还有一个思路是使用MockitoBean这是Spring Boot 3.4以上版本中官方推荐的方式它基于Mockito的新机制对上下文缓存的影响已经优化兼容性也更好。不过要注意项目版本得支持否则直接套用会编译报错。另一个常见误解是以为添加了DirtiesContext就能自动清理资源。实际上DirtiesContext的作用是告诉框架“这个测试结束之后上下文需要关闭并重建”它本身不会帮你Mock什么。滥用它同样会导致频繁重建上下文测试变慢。只有在测试确实需要修改共享Bean状态、导致后续测试可能被污染时才用。4.3 常用搭配注解与最佳实践认真用了一段时间SpringBootTest之后我整理出几个比较顺手的组合方案按场景选择效率会高很多。第一个组合SpringBootTest ActiveProfiles(test)。测试环境单独指定Profile避免读到开发或生产的配置。配合src/test/resources/application-test.yml把测试用的数据源、缓存地址都写在这里。这是避免测试污染线上数据的第一道防线。第二个组合SpringBootTest Transactional。适合Service层的集成测试每个方法独立事务、自动回滚数据库始终干净。注意我前面提醒过的如果被测试方法里用了REQUIRES_NEW传播级别回滚行为会变得复杂需要想办法避开或者调整事务边界。第三个组合WebMvcTest MockBean。适合Controller层测试启动快逻辑简单。如果你用的Spring Boot版本较新优先用MockitoBean替换MockBean。但要注意切片测试不会加载完整的启动类配置如果你在启动类里注册了自定义过滤器、拦截器切片测试里这些逻辑可能不会生效需要额外处理。第四个组合SpringBootTest(webEnvironment RANDOM_PORT) TestRestTemplate。适合需要真实HTTP链路的冒烟测试或端到端验证。TestRestTemplate是专门为测试准备的REST客户端它在底层处理了测试环境的端口注入一些相对路径用起来也很顺手。我自己在写测试时还会刻意遵守几条规矩。测试方法命名用中文描述或者清晰的英文短语都可以团队统一就行关键是让失败信息在报表里一眼能看懂。测试类不依赖执行顺序每个方法都能单独运行成功。不共享可变静态状态不依赖硬编码的当前时间需要时间点就通过参数传入或者用Clock注入。这些规矩看起来散但组合在一起能避免绝大多数“测试跑挂了却不知道怎么修”的尴尬。5. 常见问题与排查技巧实录5.1 测试启动失败数据库、Redis、中间件全都要连这是SpringBootTest最常见的翻车现场。启动报错的核心原因很简单它加载了完整的自动配置项目里依赖了DataSource、RedisTemplate、KafkaTemplate这些Bean容器启动时会尝试连接这些东西。如果测试环境没有对应的中间件启动自然失败。排查思路按顺序来先看报错栈的第一行Caused by到底是连接超时、认证失败还是Bean定义冲突。连接超时说明环境里确实没服务可连解决办法分两类一是测试环境把中间件起起来二是通过配置或Mock把外围依赖挡在测试之外。第二种更轻量常见做法是写一个测试配置类用MockBean把相关Bean替换掉或者在application-test.yml里把自动配置关掉比如spring.autoconfigure.exclude。还有一种情况是项目里明确排除了某个自动配置类但测试时又引入了一个同类型的Bean报“Consider defining a bean of type ...”。这种多数是因为某个自动配置在测试classpath下重新生效了。解决办法一般是在测试配置里排除该配置类或者明确指定主配置类。如果报错信息里带了明确的Bean名称也可以直接在测试类里用MockBean把这个Bean替换掉省得纠结排除哪些配置。5.2 MockBean不生效怎么回事MockBean是测试里很常用的注解但有几种情况会让它看起来像“没生效”。第一种被Mock的目标是通过static方法或者构造器直接创建的。Spring的依赖注入管不到这些地方你Mock出来的Bean根本没被用到。解决办法是重构代码把依赖改成Spring管理的Bean再注入。第二种测试类之间互相污染。前面说过MockBean会导致上下文缓存key变化。如果你发现某个测试类单独跑通过和其他测试类一起跑却失败大概率就是上下文复用出了问题。试着给相关测试类加上DirtiesContext或者合并配置让Mock的Bean列表保持一致。第三种方法内部直接new了依赖对象和Spring容器无关。这种情况很隐蔽比如一个Service方法里写着UserService userService new UserService()那你Mock的UserService怎么可能进得去这时候只能靠你改变代码风格让依赖通过构造器或工厂注入而不是方法里new。第四种Spring Boot版本和Mockito版本不兼容。Spring Boot 3.x默认用Mockito 5如果你额外引入了低版本的Mockito就可能导致代理创建失败Mock Bean没有真正替换原Bean。排查时看一下依赖树命令是mvn dependency:tree确认没有版本冲突。这个问题在升级Spring Boot版本后尤其容易出现所以热词里才会有“springboot版本太高”这个说法。5.3 版本差异导致的“找不到测试类”等问题这里说的版本差异主要是Spring Boot 2.x和3.x之间的变化也是很多同学从老教程里复制代码后跑不通的原因。第一Spring Boot 2.1之前用SpringBootTest必须搭配RunWith(SpringRunner.class)RunWith(SpringRunner.class) SpringBootTest public class OldStyleTest { }Spring Boot 2.1之后默认使用JUnit 5就不需要再写RunWith了。如果你还在网上看到大量带RunWith的代码多半是旧教程。不过如果你的项目还在用JUnit 4依然要加RunWith否则测试类不会被识别。第二javax.annotation包变更为jakarta.annotation。这是Spring Boot 3.x和JDK 17带来的变化如果测试里引用了一些被换包名的类编译期就会报错。升级版本时这一块特别容易漏尤其是老项目里那些Resource、PostConstruct相关的老用法。第三spring-boot-starter-test的依赖范围变化。Spring Boot 3.2之后starter-test里默认不再包含某些较老的库比如cglib、easymock等。如果你用了这些需要显式加依赖。所以升级版本后跑测试出现ClassNotFoundException先去看依赖树别急着怀疑自己的代码。第四Spring Boot 3.4开始推荐MockitoBean旧的MockBean被标注为deprecated。如果你看到项目有大量MockBean警告说明项目已经在考虑升级或者正在进行升级。从功能上讲MockitoBean更贴近新的Mockito机制但迁移时也需要逐步替换观察测试结果。5.4 几个面试官爱问的点既然热词里有一大堆springboot面试题这里顺手把我被问过或者见别人被问过的问题整理一下供你参考。问得最多的是“SpringBootTest和WebMvcTest有什么区别”回答思路SpringBootTest会加载完整应用上下文负责集成测试WebMvcTest是切片测试只加载MVC层配合MockMvc做Controller层的轻量测试速度更快适合快速反馈。其次“SpringBootTest里的webEnvironment有哪几种模式”答MOCK、RANDOM_PORT、DEFINED_PORT、NONE。其中MOCK是默认模拟Web环境RANDOM_PORT启动真实服务器且端口随机DEFINED_PORT用固定端口NONE不提供Web环境。还有“SpringBootTest的测试方法为什么要加Transactional”答默认测试事务在方法结束后回滚保证数据环境干净每个测试方法都是独立可重复的。如果想提交数据可以用Rollback(false)或Commit。最后“Spring测试上下文为什么有时候会重复加载”答因为上下文缓存要求配置一致只要MockBean、ActiveProfiles、配置类等发生变化就会重新创建上下文。这些考点其实并不难平时动手写几个测试类基本都能理解背后的原因比死记硬背面试题要靠谱得多。我自己面试候选人的时候也更倾向于问这种“你实际遇到过什么问题”的题目而不是让候选人背注解清单。说到这我突然想起自己第一次用SpringBootTest的心路历程。跑挂了一下午最后才发现是测试环境没有Redis气得不行。后来把测试配置和环境依赖梳理清楚又顺手在项目里定了规矩能切片的测试一律切片必须走全量集成测试的单独打标签、单独跑慢任务。测试的速度上去了大家才愿意真正把测试跑起来而不是当作摆设。这也算是我最想分享给后来人的一点真实经验吧——SpringBootTest本身不复杂真正复杂的是怎么控制它背后的生产级环境让它快、稳、可依赖。
返回列表