programing

스프링 테스트 방법 @Scheduled

powerit 2023. 3. 29. 21:58
반응형

스프링 테스트 방법 @Scheduled

스프링 부트 응용 프로그램에서 작업 태스크를 테스트하려면 어떻게 해야 합니까?

 package com.myco.tasks;

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

작업이 너무 짧은 간격으로 실행되어 작업이 실행될 때까지 테스트를 기다리고 작업이 호출되는지 테스트하고 싶을 경우 다음 솔루션을 사용할 수 있습니다.

클래스 경로에 대기 기능 추가:

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

제 질문은 "무엇을 테스트하고 싶으십니까?"입니다.

답변이 "스프링에서 스케줄된 작업을 원하는 시간에 실행하는지 알고 싶다"인 경우, 스프링을 테스트하고 있는 것이지 코드를 테스트하고 있는 것이 아닙니다.이것은 유닛 테스트를 할 필요가 없습니다.

"작업이 올바르게 구성되었는지 알고 싶습니다"라고 대답한 경우 자주 실행되는 작업을 사용하여 테스트 앱을 작성하고 작업이 예상할 때 실행되는지 확인합니다.이 테스트는 단위 테스트는 아니지만 작업을 올바르게 구성하는 방법을 알고 있음을 보여줍니다.

"내가 작성한 태스크가 올바르게 기능하는지 알고 싶다"는 답변이 있을 경우 태스크 방법을 유닛 테스트해야 합니다.이 예에서는 다음 테스트의 유닛을 사용합니다.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 컨텍스트에서 빈을 위조하는 것을 고려할 수 있습니다.

내 Github repo에 그런 예가 있다.설명한 접근방식을 사용하여 테스트한 간단한 일정 예가 있습니다.

@Maciej의 답변은 문제를 해결하지만 @cristian-batista에서 언급한 것처럼 너무 긴 간격(시간 등)으로 @Scheduled 테스트의 어려운 부분은 해결하지 않습니다.

실제 스케줄링 간격과 독립적으로 @Scheduled를 테스트하려면 테스트에서 파라미터 지정이 가능하도록 해야 합니다.다행히 봄은...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
     }
 }

대기 테스트:

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

이 클래스는 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));
        }

    }
}

출력 로깅을 다음에 나타냅니다.

<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

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>

도 있어요.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);
    }
}
  • 또 다른 대안은 @maciej-walkowiak과 같은 Waitility를 사용하는 것입니다.

이 경우 Waitility 의존성을 추가해야 합니다.

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

그들이 좋긴 하지만 작업 방식 내의 로직 단위 테스트에 집중하는 것이 더 낫다는 것을 고려해야 합니다.

여기 예를 들어볼게요.

또한 "*/15 * 1-4 * * *"와 같은 CRON 식을 테스트해야 할 경우 다음 클래스를 사용할 수 있습니다.

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

공식 저장소에서 더 많은 예를 찾을 수 있습니다.

언급URL : https://stackoverflow.com/questions/32319640/how-to-test-spring-scheduled

반응형