1. 简介
Spring Framework 提供了一个非常实用的注解 @Scheduled
,可以方便地实现定时任务。通过它,我们可以按照固定频率、延迟或 cron 表达式来执行方法。
本篇文章将带你了解如何对使用了 @Scheduled
注解的方法进行测试。
2. 依赖配置
首先我们创建一个基于 Maven 的 Spring Boot 工程。可以从 Spring Initializer 快速生成模板项目。
在 pom.xml
中添加必要的依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<relativePath/>
</parent>
接着引入两个核心 starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
同时加上 JUnit 5 的依赖:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
最后,为了支持异步等待断言(后面会用到),我们还需要引入 Awaitility:
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>3.1.6</version>
<scope>test</scope>
</dependency>
✅ 最新版 Spring Boot 可以去 Maven Central 查找。
3. 一个简单的 @Scheduled 示例
我们先定义一个简单的计数器类 Counter
:
@Component
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
@Scheduled(fixedDelay = 5)
public void scheduled() {
this.count.incrementAndGet();
}
public int getInvocationCount() {
return this.count.get();
}
}
这个类中有一个被 @Scheduled(fixedDelay = 5)
标记的方法,表示每次执行完后延迟 5 毫秒再次执行。
再写一个配置类启用调度功能:
@Configuration
@EnableScheduling
@ComponentScan("com.baeldung.scheduled")
public class ScheduledConfig {
}
⚠️ 注意:一定要加上 @EnableScheduling
才能让定时任务生效!
4. 使用集成测试方式验证
最直观的方式就是启动 Spring 上下文,让定时任务真正跑起来,然后验证其行为。
我们可以使用 @SpringJUnitConfig
来加载配置类并启动容器:
@SpringJUnitConfig(ScheduledConfig.class)
public class ScheduledIntegrationTest {
@Autowired
Counter counter;
@Test
public void givenSleepBy100ms_whenGetInvocationCount_thenIsGreaterThanZero()
throws InterruptedException {
Thread.sleep(100L);
assertThat(counter.getInvocationCount()).isGreaterThan(0);
}
}
📌 这个测试逻辑很简单粗暴:
- 启动上下文
- 等待 100 毫秒
- 验证计数器是否大于 0
虽然这种方式有效,但缺点也很明显:测试时间长,容易受环境影响。
5. 使用 Awaitility 更优雅地测试
Awaitility 是一个专门用于异步系统测试的库,可以让我们的测试代码更清晰、更具可读性。
来看下面这个例子:
@SpringJUnitConfig(ScheduledConfig.class)
public class ScheduledAwaitilityIntegrationTest {
@SpyBean
private Counter counter;
@Test
public void whenWaitOneSecond_thenScheduledIsCalledAtLeastTenTimes() {
await()
.atMost(Duration.ONE_SECOND)
.untilAsserted(() -> verify(counter, atLeast(10)).scheduled());
}
}
💡 解释一下关键点:
- 使用
@SpyBean
注入目标 Bean,这样就能 mock 并验证方法调用次数 await().atMost(Duration.ONE_SECOND)
表示最多等待 1 秒钟verify(counter, atLeast(10))
验证scheduled()
方法至少被调用了 10 次
这种方式比起硬编码 sleep 更加灵活和准确。
6. 小结
本文介绍了两种常见的测试 @Scheduled
定时任务的方式:
✅ 集成测试:适合验证整个流程是否正常运行
✅ Awaitility + SpyBean:更适合做细粒度的行为验证
不过需要提醒的是:虽然这些方法能验证定时任务是否触发,但更重要的是单元测试定时方法内部的业务逻辑,这才是测试的重点所在。
📌 示例代码可以从 GitHub 获取:https://github.com/eugenp/tutorials/tree/master/testing-modules/spring-testing