You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot单体应用单元测试出现OutOfMemoryError问题求助

解决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是否解决,再全量执行:

  1. 先跑10个测试类,观察内存占用和启动时间。
  2. 逐步扩大测试范围,确认并行配置的合理性。
  3. 最终全量测试的时间应该能降到5分钟以内,同时不会出现OOM。

内容的提问来源于stack exchange,提问作者Andres Olivares

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 18:15:36