web-dev-qa-db-ja.com

Spring @Scheduledをテストする方法

テスト方法 @Scheduled 私のスプリングブートアプリケーションのジョブタスクは?

 package com.myco.tasks;

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

ジョブが実行されるのをテストで待ちたいほど短い間隔でジョブが実行されると仮定し、ジョブが呼び出されたかどうかをテストしたい場合は、次のソリューションを使用できます。

Awaitility をクラスパスに追加します:

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

私の質問は、「何をテストしますか?」です。

あなたの答えが「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.
  }
3
DwB

これはしばしば困難です。テスト中にSpringコンテキストをロードし、そこからBeanを偽造して、スケジュールされた呼び出しを検証できるようにすることを検討できます。

私のGithubリポジトリにそのような例があります。 説明されたアプローチでテストされた簡単なスケジュールされた例があります。

2
luboskrnac

このクラスは、スプリングフレームワークスケジューリングを使用してスケジューラ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
1
Tiago Medici

Springでスケジュールされたタスクをテストするには、少なくとも2つのアプローチを使用できます。

  • 統合テスト

スプリングブートを使用する場合、次の依存関係が必要になります。

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

countTaskを追加し、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);
    }
}
  • もう1つの選択肢は、@ maciej-walkowiakに言及したようなAwaitilityを使用することです。

その場合、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());
    }
}

良いが、作業メソッド内のロジックの単体テストに焦点を当てる方が良いことを考慮する必要があります。

例を挙げます here

0
SHoko