测试基础设施
Spring Test 的关键不是提供更多注解,而是把测试实例、ApplicationContext、事务和监听器组织成一个可复用的执行上下文。
先给答案:测试上下文缓存优化的是启动成本,隔离责任仍由测试配置承担
Section titled “先给答案:测试上下文缓存优化的是启动成本,隔离责任仍由测试配置承担”Spring 测试框架根据测试类、配置位置、Profile 和自定义上下文定制器生成缓存键;相同键可以复用已启动的 ApplicationContext,避免每个测试重新创建容器。
缓存不会自动修复测试之间的状态污染。Mock Bean、动态属性、数据库状态和容器中可变单例都可能让“同一个上下文”在测试间泄漏状态。需要清理时,测试框架通过监听器和 @DirtiesContext 让缓存失效,代价是下一次测试重新启动。
test instance -> TestContextManager -> TestExecutionListeners -> prepareTestInstance -> load/cache ApplicationContext -> before/after test method -> transactional listener源码入口可从 spring-test/src/main/java/org/springframework/test/context/TestContextManager.java、DefaultTestContext.java 和 support/DependencyInjectionTestExecutionListener.java 开始;事务监听器位于 spring-test/.../TransactionalTestExecutionListener.java。
测试上下文根据配置、profiles、loader 和 parent 等条件构造 cache key。相同 key 复用 ApplicationContext,避免每个测试方法重新执行 refresh();脏上下文则通过 @DirtiesContext 使对应缓存失效。
监听器责任链
Section titled “监听器责任链”监听器分别承担依赖注入、DirtiesContext、事务、SQL 脚本和 Servlet 模拟环境。每个监听器只关心测试生命周期的某一阶段,测试框架通过排序保证前置和后置操作成对出现。
为什么缓存上下文而不是每次重建
Section titled “为什么缓存上下文而不是每次重建”替代方案:每个测试类/方法都创建新容器,保证绝对隔离。
为什么不行:大型应用的组件扫描、Bean 初始化和数据库连接代价很高,测试反馈周期会被启动成本主导。
证据:TestContext 使用 context cache,并把显式污染声明交给 @DirtiesContext,在速度和隔离之间提供可见取舍。
| 场景 | 现象 | 原因 | 规避 |
|---|---|---|---|
| 测试修改单例状态 | 后续测试互相影响 | 上下文被缓存 | 清理状态或标记 @DirtiesContext |
| 动态属性改变 cache key | 上下文数量激增 | 配置组合不同 | 统一测试配置来源 |
| 事务测试调用异步代码 | 返回后事务已结束 | 测试事务绑定当前线程 | 等待任务或采用显式测试事务 |
测试基础设施可以复用生产级生命周期组件,但必须把缓存 key、隔离边界和失效条件设计成可观察的协议。
面试锚点
- Spring Test 为什么能复用 ApplicationContext?
@DirtiesContext解决什么问题?- 测试事务为什么不一定覆盖异步线程?