Подтвердить что ты не робот

Как протестировать Spring @Scheduled

Как я могу протестировать @Scheduled задания @Scheduled в моем приложении весенней загрузки?

 package com.myco.tasks;

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

Ответ 1

Если мы предположим, что ваша работа выполняется с такими небольшими интервалами, что вы действительно хотите, чтобы ваш тест дождался выполнения задания, и вы просто хотите проверить, запущено ли задание, вы можете использовать следующее решение:

Добавить Awaitility в classpath:

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

Напишите тест, аналогичный:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

    @SpyBean
    private MyTask myTask;

    @Test
    public void jobRuns() {
        await().atMost(Duration.FIVE_SECONDS)
               .untilAsserted(() -> verify(myTask, times(1)).work());
    }
}

Ответ 2

Мой вопрос: "что вы хотите проверить?"

Если вы ответите "Я хочу знать, что Spring выполняет мое запланированное задание, когда я этого хочу", то вы тестируете Spring, а не ваш код. Это не то, что вам нужно для модульного тестирования.

Если вы ответите "Я хочу знать, что я правильно настроил свою задачу", то напишите тестовое приложение с часто выполняемой задачей и убедитесь, что задача выполняется, когда вы ожидаете, что она будет запущена. Это не unit тест, но покажет, что вы знаете, как правильно настроить текст.

Если ответ "Я хочу знать, что задание, которое я написал, работает правильно", то вам нужно выполнить модульное тестирование метода задачи. В вашем примере вы хотите выполнить модульное тестирование метода work(). Сделайте это, написав unit тест, который напрямую вызывает ваш метод задачи (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.
  }

Ответ 3

Это часто бывает трудно. Вы можете рассмотреть возможность загрузки контекста Spring во время теста и подделать его с помощью bean, чтобы иметь возможность проверять запланированный вызов.

У меня есть такой пример в моем реестре Github. Простой пример запланированного примера с описанным подходом.

Ответ 4

этот класс означает создание планировщиков 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));
        }

    }
}

вот запись журнала:

<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

Ответ 5

Мы можем использовать как минимум два подхода для тестирования запланированных задач с помощью Spring:

  • Интеграционное тестирование

Если мы используем весеннюю загрузку, нам понадобятся следующие зависимости:

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

Мы могли бы добавить count к Task и увеличить его внутри метода work:

 public class MyTask {
   private final AtomicInteger count = new AtomicInteger(0);

   @Scheduled(fixedRate=1000)
   public void work(){
     this.count.incrementAndGet();
   }

   public int getInvocationCount() {
    return this.count.get();
   }
 }

Затем проверьте count:

@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);
    }
}
  • Другой альтернативой является использование Awaitility, например mentions @maciej-walkowiak.

В этом случае нам нужно добавить зависимость Awaitility:

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

И используйте его DSL для проверки количества вызовов метода work:

@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());
    }
}

Мы должны принять во внимание, что, хотя они хороши, лучше сосредоточиться на модульном тестировании логики внутри метода работы.

Я положил пример здесь.

Ответ 6

Решение, упомянутое Maciej Walkowiak, отлично работает, но с небольшими изменениями. @SpyBean не работал для меня, поэтому я использовал @autowired, и он работал отлично.