bigywater.tech
Spring · 82

测试:@SpringBootTest、切片测试与上下文缓存

@SpringBootTest 和 @WebMvcTest 差在哪?为什么测试越写越慢?上下文缓存什么时候会失效?@MockitoBean 会让缓存失效吗?这篇用 JUnit Platform Console Launcher 把测试类跑一遍,数上下文的创建次数。

107 / 154
@WebMvcTest 与 @SpringBootTest 的上下文里的 Bean 定义数
8 类 9 方法 → 7 个上下文
同一个 JVM 里按类名顺序跑,UserController 被创建的次数
@MockitoBean
加上 @MockitoBean 的测试类不能和没有它的测试类共用上下文

三种测试方式

实测 · 本机执行输出
  Plain(不启动 Spring):整条命令(含 JVM 启动)3 次中位 373 ms(范围 373~396);结果 tests successful 1, tests failed 0, containers failed 0
    [Plain] 不启动 Spring:user-1
  C6_Slice1(@WebMvcTest):整条命令(含 JVM 启动)3 次中位 1585 ms(范围 1541~1734);结果 tests successful 1, tests failed 0, containers failed 0
    [C6_Slice1] Bean 定义 107 个,GET /users/1 → mocked
  C1_Full1(@SpringBootTest + @AutoConfigureMockMvc):整条命令(含 JVM 启动)3 次中位 1647 ms(范围 1460~2015);结果 tests successful 1, tests failed 0, containers failed 0
    [C1_Full1] Bean 定义 154 个,GET /users/1 → user-1

不启动 Spring 最快;切片只有 Web 层的 Bean;完整上下文最大;这个小应用上切片和完整上下文耗时差别不大。

上下文缓存

实测 · 本机执行输出
  >> C1_Full1
      (新建上下文 #1)
     [C1_Full1] Bean 定义 154 个,GET /users/1 → user-1
  >> C2_Full2
     [C2_Full2] Bean 定义 154 个,GET /users/1 → user-1
  >> C3_FullMock
      (新建上下文 #2)
     [C3_FullMock] Bean 定义 154 个,GET /users/1 → mocked
  >> C4_FullProp
      (新建上下文 #3)
     [C4_FullProp] Bean 定义 154 个,GET /users/1 → user-1
  >> C5_FullProfile
      (新建上下文 #4)
     [C5_FullProfile] Bean 定义 154 个,GET /users/1 → user-1
  >> C6_Slice1
      (新建上下文 #5)
     [C6_Slice1] Bean 定义 107 个,GET /users/1 → mocked
  >> C7_Slice2
     [C7_Slice2] Bean 定义 107 个
  >> C9_Dirties(2 个测试方法)
      (新建上下文 #6)
     [C9_Dirties.a] Bean 定义 146 个
      (新建上下文 #7)
     [C9_Dirties.b] Bean 定义 146 个
  整个 JVM 里 UserController 一共被创建了 7 次(= 创建的 Spring 上下文个数)
  结果 tests successful 9, tests failed 0, containers failed 0,整条命令 2309 ms

配置相同则共用;@MockitoBean、@TestPropertySource、@ActiveProfiles、@DirtiesContext 都会导致新建上下文。

切片的边界

实测 · 本机执行输出
  退出码 1;tests successful 0, tests failed 1, containers failed 0
    => java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@388623ad testClass = demo.C8_SliceNoMock, locations = [], classes = [demo.App], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.webmvc
    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userController' defined in file [<jtmp>/x82/out/demo/UserController.class]: Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'demo.UserService' available: expected at least 1 bea
    Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'demo.UserService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

@WebMvcTest 不加载 @Service,缺少的依赖要用 @MockitoBean 补上,否则上下文启动失败。

分步演示

测试
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(JUnit Jupiter 6.0.3、Mockito 5.20.0、spring-boot-starter-test 与 spring-boot-starter-webmvc-test)。用 JUnit Platform Console Launcher 跑测试类,按类名顺序;耗时是整条 java 命令的时间(含 JVM 与 JUnit 启动),不是上下文创建的纯耗时;上下文个数用 UserController 构造器里的计数器得到,所以只统计了含这个控制器的上下文;被测应用只有一个控制器和一个 Service,没有数据库、安全、消息
    • 没有验证:@DataJpaTest、@JdbcTest、@JsonTest、@WebFluxTest 等其他切片(见第 83 篇和数据篇);RANDOM_PORT 下的 @SpringBootTest 与 TestRestTemplate / RestTestClient;spring.test.context.cache.maxSize 的上限与淘汰;并行执行测试对上下文缓存的影响;@TestConfiguration 与 @Import 对缓存键的影响;Maven Surefire 或 Gradle 里的运行方式;切片在大型应用(带 JPA、安全等)里节省多少时间
    京ICP备2026067502号-1京公网安备11010502063487号