Java 如何测试Spring@Scheduled

Java 如何测试Spring@Scheduled,java,spring,junit,spring-boot,mockito,Java,Spring,Junit,Spring Boot,Mockito,如何在spring boot应用程序中测试作业任务 package com.myco.tasks; public class MyTask { @Scheduled(fixedRate=1000) public void work() { // task execution logic } } 这通常很难做到。您可以考虑在测试期间加载Spring上下文,并从中伪造一些bean,以便能够验证计划调用。 有一个简单的计划示例使用所描述的方法进

如何在spring boot应用程序中测试作业任务

 package com.myco.tasks;

 public class MyTask {
     @Scheduled(fixedRate=1000)
     public void work() {
         // task execution logic
     }
 }

这通常很难做到。您可以考虑在测试期间加载Spring上下文,并从中伪造一些bean,以便能够验证计划调用。


有一个简单的计划示例使用所描述的方法进行测试。

如果我们假设您的作业在很短的时间间隔内运行,您确实希望您的测试等待作业执行,并且您只想测试是否调用了作业,则可以使用以下解决方案:

添加到类路径:

<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>3.1.0</version>
    <scope>test</scope>
</dependency>

此类表示使用springframework调度生成调度程序cron

import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.scheduling.support.CronSequenceGenerator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@RunWith(SpringJUnit4ClassRunner.class)
@Configuration
@PropertySource("classpath:application.properties")
public class TrimestralReportSenderJobTest extends AbstractJUnit4SpringContextTests {

    protected Logger LOG = Logger.getLogger(getClass());

    private static final String DATE_CURRENT_2018_01_01 = "2018-01-01";
    private static final String SCHEDULER_TWO_MIN_PERIOD = "2 0/2 * * * *";
    private static final String SCHEDULER_QUARTER_SEASON_PERIOD = "0 0 20 1-7 1,4,7,10 FRI";

    @Test
    public void cronSchedulerGenerator_0() {
        cronSchedulerGenerator(SCHEDULER_QUARTER_SEASON_PERIOD, 100);
    }

    @Test
    public void cronSchedulerGenerator_1() {
        cronSchedulerGenerator(SCHEDULER_TWO_MIN_PERIOD, 200);
    }

    public void cronSchedulerGenerator(String paramScheduler, int index) {
        CronSequenceGenerator cronGen = new CronSequenceGenerator(paramScheduler);
        java.util.Date date = java.sql.Date.valueOf(DATE_CURRENT_2018_01_01);

        for (int i = 0; i < index; i++) {
            date = cronGen.next(date);
            LOG.info(new java.text.SimpleDateFormat("EEE, MMM d, yyyy 'at' hh:mm:ss a").format(date));
        }

    }
}
import org.apache.log4j.Logger;
导入org.junit.Test;
导入org.junit.runner.RunWith;
导入org.springframework.scheduling.support.CronSequenceGenerator;
导入org.springframework.test.context.ContextConfiguration;
导入org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
导入org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
导入org.springframework.context.annotation.Configuration;
导入org.springframework.context.annotation.PropertySource;
@RunWith(SpringJUnit4ClassRunner.class)
@配置
@PropertySource(“类路径:application.properties”)
公共类TrimestralReportSenderJobTest扩展了AbstractJUnit4S普林gContextTests{
受保护的记录器日志=Logger.getLogger(getClass());
私有静态最终字符串日期\u当前\u 2018\u 01\u 01=“2018-01-01”;
私有静态最终字符串调度程序\u TWO\u MIN\u PERIOD=“2 0/2****”;
私有静态最终字符串调度程序\u季度\u季节\u期间=“0 0 0 1-7 1,4,7,10周五”;
@试验
public void cronSchedulerGenerator_0(){
cronSchedulerGenerator(调度程序季度周期,100);
}
@试验
public void cronSchedulerGenerator_1(){
cronSchedulerGenerator(调度器两分钟周期,200);
}
public void cronSchedulerGenerator(字符串参数调度程序,int索引){
CronSequenceGenerator cronGen=新的CronSequenceGenerator(paramScheduler);
java.util.Date-Date=java.sql.Date.valueOf(Date\u CURRENT\u 2018\u 01);
对于(int i=0;i
以下是输出日志记录:

<com.medici.scheduler.jobs.TrimestralReportSenderJobTest> - lun, gen 1, 2018 at 12:02:02 AM
<com.medici.scheduler.jobs.TrimestralReportSenderJobTest> - lun, gen 1, 2018 at 03:02:02 AM
<com.medici.scheduler.jobs.TrimestralReportSenderJobTest> - lun, gen 1, 2018 at 06:02:02 AM
<com.medici.scheduler.jobs.TrimestralReportSenderJobTest> - lun, gen 1, 2018 at 09:02:02 AM
<com.medici.scheduler.jobs.TrimestralReportSenderJobTest> - lun, gen 1, 2018 at 12:02:02 PM
-lun,gen 1,2018年12月2日上午12:02:02
-lun,gen 1,2018年凌晨3:02:02
-2018年第1代lun上午6:02:02
-2018年第1代lun上午9:02:02
-2018年第1代lun于下午12:02:02
我的问题是:“你想测试什么?”

如果你的答案是 “我想知道Spring在我需要的时候运行我的计划任务”, 然后你在测试Spring, 不是你的代码。 这不是您需要进行单元测试的东西

如果您的回答是“我想知道我正确配置了任务”, 然后编写一个带有频繁运行任务的测试应用程序,并验证 任务在您希望它运行时运行。 这不是单元测试, 但这将表明您知道如何正确配置任务

如果答案是“我想知道我写的任务功能是否正确”, 然后需要对任务方法进行单元测试。 以你为例,, 您想对
work()
方法进行单元测试。 为此,编写一个单元测试,直接调用任务方法(
work()
)。 比如说,

public class TestMyTask
{
  @InjectMocks
  private MyTask classToTest;

  // Declare any mocks you need.
  @Mock
  private Blammy mockBlammy;

  @Before
  public void preTestSetup()
  {
    MockitoAnnotations.initMocks(this);

    ... any other setup you need.
  }

  @Test
  public void work_success()
  {
    ... setup for the test.


    classToTest.work();


    .. asserts to verify that the work method functioned correctly.
  }

为了使用Spring测试计划任务,我们至少可以使用两种方法:

  • 集成测试
如果使用spring boot,则需要以下依赖项:

<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>

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
</dependency>
然后检查
计数

@SpringJUnitConfig(ScheduledConfig.class)
public class ScheduledIntegrationTest {
 
    @Autowired
    MyTask task;

    @Test
    public void givenSleepBy100ms_whenWork_thenInvocationCountIsGreaterThanZero() 
      throws InterruptedException {
        Thread.sleep(2000L);

        assertThat(task.getInvocationCount()).isGreaterThan(0);
    }
}
  • 另一种选择是使用等待,比如提到@maciej walkowiak
在这种情况下,我们需要添加等待依赖项:

<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>3.1.6</version>
    <scope>test</scope>
</dependency>
我们需要考虑的是,尽管它们很好,但最好集中在工作方法内部逻辑的单元测试上

我举了一个例子

此外,如果需要测试诸如“*/15*1-4***”之类的CRON表达式,可以使用以下类:

您可以在中找到更多示例。

的答案解决了问题,但没有解决@cristian batista提到的测试时间间隔过长(如小时)的难题

为了独立于实际调度间隔测试@Scheduled,我们需要通过测试将其参数化。幸运的是,Spring添加了一个
fixedRateString
参数

下面是一个完整的示例:

public class MyTask {
     // Control rate with property `task.work.rate` and use 3600000 (1 hour) as a default:
     @Scheduled(fixedRateString = "${task.work.rate:3600000}")
     public void work() {
         // task execution logic
     }
 }
使用以下各项进行测试:


你到底想测试什么?如果您想测试work()是否执行了它应该执行的操作,您可以像任何其他bean的任何其他方法一样对其进行测试:创建bean的实例,调用该方法,并测试它是否执行了它应该执行的操作。如果你想测试Spring是否每秒都调用这个方法,那就没有真正的意义了:Spring已经为你测试过了。我同意你的观点,尝试测试框架的功能对我来说似乎没有必要,但我被要求这样做。我通过添加一条小日志消息并检查预期消息是否确实在预期的时间范围内被记录,找到了解决方法。测试的另一个好处是,如果
@EnableScheduling
注释被删除,则测试将失败。仅仅等待计划的任务肯定不是办法。这应该是一个玩时钟的把戏,这样调度程序就可以响应它了。@rohit,请随意发布您的解决方案。如果没有,我假设您没有。
verify()
times()
函数找不到。你能指定软件包吗?这些函数来自Mockito。这个包是:
org.mockito.mockito#verify
和类似的
times
。这不是一个好的解决方案。这只适用于在几秒钟内执行的@Scheduled。每周执行一次怎么样?@CristianBatista“如果我们假设您的作业在如此短的时间间隔内运行”。我认为测试作业是否运行没有多大意义,但是
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>3.1.6</version>
    <scope>test</scope>
</dependency>
@SpringJUnitConfig(ScheduledConfig.class)
public class ScheduledAwaitilityIntegrationTest {

    @SpyBean 
    MyTask task;

    @Test
    public void whenWaitOneSecond_thenWorkIsCalledAtLeastThreeTimes() {
        await()
          .atMost(Duration.FIVE_SECONDS)
          .untilAsserted(() -> verify(task, atLeast(3)).work());
    }
}
@Test
public void at50Seconds() {
    assertThat(new CronSequenceGenerator("*/15 * 1-4 * * *").next(new Date(2012, 6, 1, 9, 53, 50))).isEqualTo(new Date(2012, 6, 2, 1, 0));
}
public class MyTask {
     // Control rate with property `task.work.rate` and use 3600000 (1 hour) as a default:
     @Scheduled(fixedRateString = "${task.work.rate:3600000}")
     public void work() {
         // task execution logic
     }
 }
@RunWith(SpringRunner.class)
@SpringBootTest
// Override the scheduling rate to something really short:
@TestPropertySource(properties = "task.work.rate=100") 
public class DemoApplicationTests {

    @SpyBean
    private MyTask myTask;

    @Test
    public void jobRuns() {
        Awaitility.await().atMost(10, TimeUnit.SECONDS).untilAsserted(() ->
            verify(myTask, Mockito.atLeastOnce()).work()
        );
    }
}