Spring Boot单体应用单元测试出现OutOfMemoryError问题求助
首先咱们先聚焦OutOfMemoryError的根源,再同步优化测试速度——这俩问题通常是关联的,你的测试配置和代码里有几个关键的调整点:
一、核心问题:Spring上下文的过度创建与内存泄漏
你的测试用了@DirtiesContext(classMode=AFTER_CLASS),结合并行测试配置,这是OOM的主要诱因之一:
1. 移除不必要的@DirtiesContext
@DirtiesContext(classMode=AFTER_CLASS)会在每个测试类执行完后销毁并重建Spring上下文,但你的测试都是用@MockBean模拟依赖(比如Repository、Service),根本没修改上下文状态——完全不需要销毁上下文!
修改方案:
删除所有测试类上的@DirtiesContext(classMode=AFTER_CLASS),让Spring自动复用相同配置的上下文。比如所有用@WebMvcTest(CategoryController.class)的测试类会共享同一个轻量Web上下文,所有用@SpringBootTest的服务测试也会共享上下文,这能大幅减少内存占用和启动时间。
2. 优化并行测试配置
你的Surefire配置里开启了全并行模式,但没有限制线程数,加上每个并行线程可能加载独立的Spring上下文,直接撑爆内存:
<configuration> <properties> <configurationParameters> junit.jupiter.conditions.deactivate = * junit.jupiter.extensions.autodetection.enabled = true junit.jupiter.testinstance.lifecycle.default = per_class junit.jupiter.execution.parallel.enabled = true <!-- 只让同一类内的方法并行,避免同时加载多个Spring上下文 --> junit.jupiter.execution.parallel.mode.default = concurrent junit.jupiter.execution.parallel.mode.classes.default = same_thread <!-- 根据CPU核心数固定并行线程数,比如4或8 --> junit.jupiter.execution.parallel.config.strategy = fixed junit.jupiter.execution.parallel.config.fixed.parallelism = 4 </configurationParameters> </properties> <!-- 直接增大测试堆内存,给测试足够的内存空间 --> <argLine>-Xmx2g -Xms1g</argLine> </configuration>
关键调整:
- 把类级并行改成
same_thread,避免同时加载多个重量级Spring上下文。 - 用
fixed策略限制线程数,别超过CPU核心数的2倍,防止内存过载。 - 添加
argLine参数直接提升JVM堆内存上限。
3. 简化服务测试的上下文配置
看你的服务测试类,用了@TestConfiguration手动注册Bean,但其实你已经用@MockBean模拟了Repository,直接@Autowired Service即可,不需要额外的配置类——这会让每个服务测试类创建独特的上下文,无法复用。
修改服务测试类:
@SpringBootTest(classes = Project.class) @TestPropertySource(locations = "classpath:test.properties") // 移除@DirtiesContext public class Services_CategoryTBTest { // 直接Autowired,无需手动注册Bean @Autowired Service_CategoryTB_impl service; @MockBean CategoryRepository repository; // ... 测试方法保持不变 }
这样结构类似的服务测试类就能共享同一个Spring上下文,不用重复创建。
二、测试加速的额外优化点
解决OOM的同时,这些调整能让测试速度从20分钟大幅降低:
1. 坚持用@WebMvcTest做控制器测试
你已经在控制器测试里用了@WebMvcTest,这很好——它只加载Web层相关的Bean,比@SpringBootTest轻量得多。确保所有控制器测试都用@WebMvcTest,不要用重量级的@SpringBootTest。
2. 优化测试前置操作
比如你的setMock和setSeveralMock方法,可以结合@BeforeAll(配合per_class生命周期)和@BeforeEach来减少重复操作:
@BeforeAll static void setupGlobalMocks() { // 初始化全局通用的模拟数据 } @BeforeEach void resetMocks() { Mockito.reset(repository); // 每个测试方法前重置Mock,避免状态污染 }
3. 改用纯Mockito单元测试(针对Service层)
如果你的Service测试不需要Spring上下文(只是测试业务逻辑),可以完全抛弃@SpringBootTest,改成纯单元测试:
// 不需要任何Spring注解,纯Mockito测试 public class Services_CategoryTBTest { @InjectMocks Service_CategoryTB_impl service; @Mock CategoryRepository repository; @BeforeEach void initMocks() { MockitoAnnotations.openMocks(this); } // ... 测试方法保持不变 }
这种纯单元测试启动速度极快,完全不会占用Spring上下文的内存,适合大部分Service层测试。
4. 清理冗余依赖
检查你的POM,spring-boot-devtools是runtime scope,但测试时加载它可能导致类加载器内存泄漏。可以把它的scope改成provided:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>provided</scope> <optional>true</optional> </dependency>
三、验证调整效果
按上面的步骤修改后,先跑一小部分测试验证OOM是否解决,再全量执行:
- 先跑10个测试类,观察内存占用和启动时间。
- 逐步扩大测试范围,确认并行配置的合理性。
- 最终全量测试的时间应该能降到5分钟以内,同时不会出现OOM。
内容的提问来源于stack exchange,提问作者Andres Olivares

