Рубрики
Без рубрики

Тестирование работы весеннего пакета

Изучите различные подходы к тестированию задания Spring Batch.

Автор оригинала: baeldung.

1. Введение

В отличие от других приложений на основе весны, тестирование пакетных заданий поставляется с некоторыми конкретными проблемами, в основном из-за асинхронного характера того, как выполняются задания.

В этом учебнике мы изумим различные альтернативы для тестирования задания Spring Batch.

2. Обязательные зависимости

Мы используем весна-загрузка-стартер-пакет , так что сначала давайте набудем настроить необходимые зависимости в нашей пом.xml :


    org.springframework.boot
    spring-boot-starter-batch
    2.4.0


    org.springframework.boot
    spring-boot-starter-test
    2.4.0
    test


    org.springframework.batch
    spring-batch-test
    4.3.0.RELEASE
    test

Мы включили весенне-бу t-стартер-тест и весна-пакет-тест которые приносят в некоторых необходимых методов помощник, слушателей и бегунов для тестирования приложений Spring Batch.

3. Определение весенней работы пакета

Давайте создадим простое приложение, чтобы показать, как Spring Batch решает некоторые проблемы тестирования.

Наше приложение использует двухшаговую работа который читает файл ввода CSV со структурированной книжной информацией и выводит книги и детали книги.

3.1. Определение шагов работы

Два последующих Шаг s извлечь конкретную информацию из КнигаРекорд s, а затем сопоставить их, чтобы Книжный s (шаг1) и BookDetail s (шаг2):

@Bean
public Step step1(
  ItemReader csvItemReader, ItemWriter jsonItemWriter) throws IOException {
    return stepBuilderFactory
      .get("step1")
      . chunk(3)
      .reader(csvItemReader)
      .processor(bookItemProcessor())
      .writer(jsonItemWriter)
      .build();
}

@Bean
public Step step2(
  ItemReader csvItemReader, ItemWriter listItemWriter) {
    return stepBuilderFactory
      .get("step2")
      . chunk(3)
      .reader(csvItemReader)
      .processor(bookDetailsItemProcessor())
      .writer(listItemWriter)
      .build();
}

3.2. Определение чтения ввода и выхода писателя

Давайте теперь настроить считыватель ввода файлов CSV с помощью FlatFileItemЧитатель для де-сериализации структурированной книжной информации в КнигаРекорд Объектов:

private static final String[] TOKENS = { 
  "bookname", "bookauthor", "bookformat", "isbn", "publishyear" };

@Bean
@StepScope
public FlatFileItemReader csvItemReader(
  @Value("#{jobParameters['file.input']}") String input) {
    FlatFileItemReaderBuilder builder = new FlatFileItemReaderBuilder<>();
    FieldSetMapper bookRecordFieldSetMapper = new BookRecordFieldSetMapper();
    return builder
      .name("bookRecordItemReader")
      .resource(new FileSystemResource(input))
      .delimited()
      .names(TOKENS)
      .fieldSetMapper(bookRecordFieldSetMapper)
      .build();
}

Есть несколько важных вещей в этом определении, которые будут иметь последствия на то, как мы тестируем.

Прежде всего, мы аннотировали FlatItemЧитатель фасоль с @StepScope , и, как следствие, этот объект будет делиться своим сроком службы с StepExecution .

Это также позволяет нам вводить динамические значения во время выполнения, чтобы мы могли передать наш входной файл из JobParameter s в строке 4 . В отличие от этого, токены, используемые для BookRecordFieldSetMapper настроены во время компиляции.

Затем мы аналогичным образом определяем JsonFileItemWriter выход писателя:

@Bean
@StepScope
public JsonFileItemWriter jsonItemWriter(
  @Value("#{jobParameters['file.output']}") String output) throws IOException {
    JsonFileItemWriterBuilder builder = new JsonFileItemWriterBuilder<>();
    JacksonJsonObjectMarshaller marshaller = new JacksonJsonObjectMarshaller<>();
    return builder
      .name("bookItemWriter")
      .jsonObjectMarshaller(marshaller)
      .resource(new FileSystemResource(output))
      .build();
}

На второй Шаг , мы используем весенний пакет при условии ListItemWriter , что просто сбрасывает вещи в список памяти.

3.3. Определение пользовательского JobLauncher

Далее, давайте отключим систему Работа запуск конфигурации Spring Boot Batch путем настройки spring.batch.job.enabled-false в нашем application.properties.

Мы настраиваем наши собственные ДжойвЛаунчер пройти пользовательский JobParameters при запуске работа :

@SpringBootApplication
public class SpringBatchApplication implements CommandLineRunner {

    // autowired jobLauncher and transformBooksRecordsJob

    @Value("${file.input}")
    private String input;

    @Value("${file.output}")
    private String output;

    @Override
    public void run(String... args) throws Exception {
        JobParametersBuilder paramsBuilder = new JobParametersBuilder();
        paramsBuilder.addString("file.input", input);
        paramsBuilder.addString("file.output", output);
        jobLauncher.run(transformBooksRecordsJob, paramsBuilder.toJobParameters());
   }

   // other methods (main etc.)
}

4. Тестирование работы весеннего пакета

весна-пакет-тест зависимость предоставляет набор полезных методов помощников и слушателей, которые могут быть использованы для настройки контекста Spring Batch во время тестирования.

Давайте создадим базовую структуру для нашего теста:

@RunWith(SpringRunner.class)
@SpringBatchTest
@EnableAutoConfiguration
@ContextConfiguration(classes = { SpringBatchConfiguration.class })
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, 
  DirtiesContextTestExecutionListener.class})
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class SpringBatchIntegrationTest {

    // other test constants
 
    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;
  
    @Autowired
    private JobRepositoryTestUtils jobRepositoryTestUtils;
  
    @After
    public void cleanUp() {
        jobRepositoryTestUtils.removeJobExecutions();
    }

    private JobParameters defaultJobParameters() {
        JobParametersBuilder paramsBuilder = new JobParametersBuilder();
        paramsBuilder.addString("file.input", TEST_INPUT);
        paramsBuilder.addString("file.output", TEST_OUTPUT);
        return paramsBuilder.toJobParameters();
   }

@SpringBatchTest аннотация обеспечивает JobLauncherTestUtils и JobRepositoryTestUtils классы помощников. Мы используем их, чтобы вызвать Работа и Шаг s в наших тестах.

Наше приложение использует Автоматическая конфигурация spring Boot , что позволяет по умолчанию в памяти JobRepository . В результате запуск нескольких тестов в одном классе требует шага очистки после каждого тестового запуска .

Наконец, если мы хотим запустить несколько тестов из нескольких тестовых классов, мы должны отметить нашу контекст как грязный . Это необходимо для того, чтобы избежать столкновения нескольких JobRepository экземпляров с использованием одного и того же источника данных.

4.1. Тестирование задания «от конца до конца»

Первое, что мы будем тестировать это полный конец-конец Работа с небольшим вводом набора данных.

Затем мы можем сравнить результаты с ожидаемым результатом тестирования:

@Test
public void givenReferenceOutput_whenJobExecuted_thenSuccess() throws Exception {
    // given
    FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT);
    FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT);

    // when
    JobExecution jobExecution = jobLauncherTestUtils.launchJob(defaultJobParameters());
    JobInstance actualJobInstance = jobExecution.getJobInstance();
    ExitStatus actualJobExitStatus = jobExecution.getExitStatus();
  
    // then
    assertThat(actualJobInstance.getJobName(), is("transformBooksRecords"));
    assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED"));
    AssertFile.assertFileEquals(expectedResult, actualResult);
}

Весенний пакетный тест обеспечивает полезную метод сравнения файлов для проверки выходов с помощью AssertFile класс .

4.2. Тестирование индивидуальных шагов

Иногда это довольно дорого, чтобы проверить полный Работа конец-конец, и поэтому имеет смысл проверить индивидуальные Шаги вместо:

@Test
public void givenReferenceOutput_whenStep1Executed_thenSuccess() throws Exception {
    // given
    FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT);
    FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT);

    // when
    JobExecution jobExecution = jobLauncherTestUtils.launchStep(
      "step1", defaultJobParameters()); 
    Collection actualStepExecutions = jobExecution.getStepExecutions();
    ExitStatus actualJobExitStatus = jobExecution.getExitStatus();

    // then
    assertThat(actualStepExecutions.size(), is(1));
    assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED"));
    AssertFile.assertFileEquals(expectedResult, actualResult);
}

@Test
public void whenStep2Executed_thenSuccess() {
    // when
    JobExecution jobExecution = jobLauncherTestUtils.launchStep(
      "step2", defaultJobParameters());
    Collection actualStepExecutions = jobExecution.getStepExecutions();
    ExitStatus actualExitStatus = jobExecution.getExitStatus();

    // then
    assertThat(actualStepExecutions.size(), is(1));
    assertThat(actualExitStatus.getExitCode(), is("COMPLETED"));
    actualStepExecutions.forEach(stepExecution -> {
        assertThat(stepExecution.getWriteCount(), is(8));
    });
}

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

Помните, что мы также разработали наш ПунктЧитатель и ПунктРайтер использовать динамические значения при , что означает мы можем передать наши параметры вико- JobExecution (линии 9 и 23).

Впервые Шаг тест, мы сравниваем фактический результат с ожидаемым выходом.

С другой стороны, во втором тесте мы проверяем StepExecution для ожидаемых письменных элементов .

4.3. Тестирование компонентов step-scoped

Давайте теперь проверить FlatFileItemЧитатель . Напомним, что мы разоблачили его как @StepScope боб, поэтому мы хотим использовать специальную поддержку Spring Batch для этой :

// previously autowired itemReader

@Test
public void givenMockedStep_whenReaderCalled_thenSuccess() throws Exception {
    // given
    StepExecution stepExecution = MetaDataInstanceFactory
      .createStepExecution(defaultJobParameters());

    // when
    StepScopeTestUtils.doInStepScope(stepExecution, () -> {
        BookRecord bookRecord;
        itemReader.open(stepExecution.getExecutionContext());
        while ((bookRecord = itemReader.read()) != null) {

            // then
            assertThat(bookRecord.getBookName(), is("Foundation"));
            assertThat(bookRecord.getBookAuthor(), is("Asimov I."));
            assertThat(bookRecord.getBookISBN(), is("ISBN 12839"));
            assertThat(bookRecord.getBookFormat(), is("hardcover"));
            assertThat(bookRecord.getPublishingYear(), is("2018"));
        }
        itemReader.close();
        return null;
    });
}

тем МетаданныеInstanceFactory создает пользовательский StepExecution , что необходимо, чтобы придать наш шаг-scoped ПунктЧитатель.

Из-за этого, мы можем проверить поведение читателя с помощью doInTestScope метод.

Далее, давайте проверить JsonFileItemWriter и проверить его выход:

@Test
public void givenMockedStep_whenWriterCalled_thenSuccess() throws Exception {
    // given
    FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT_ONE);
    FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT);
    Book demoBook = new Book();
    demoBook.setAuthor("Grisham J.");
    demoBook.setName("The Firm");
    StepExecution stepExecution = MetaDataInstanceFactory
      .createStepExecution(defaultJobParameters());

    // when
    StepScopeTestUtils.doInStepScope(stepExecution, () -> {
        jsonItemWriter.open(stepExecution.getExecutionContext());
        jsonItemWriter.write(Arrays.asList(demoBook));
        jsonItemWriter.close();
        return null;
    });

    // then
    AssertFile.assertFileEquals(expectedResult, actualResult);
}

В отличие от предыдущих тестов, в настоящее время мы полностью контролируем наши испытательные объекты . В результате мы несем ответственность за открытие и закрытие потоков ви-во .

5. Заключение

В этом учебнике мы изучили различные подходы тестирования задания Spring Batch.

Все-в-конце тестирования проверяет полное выполнение задания. Тестирование отдельных этапов может помочь в сложных сценариях.

Наконец, когда дело доходит до компонентов Step-scoped, мы можем использовать кучу методов помощников, предоставляемых весна-пакет-тест. Они помогут нам в stubbing и насмешливый Весенний Batch доменных объектов.

Как обычно, мы можем изучить полную кодовую базу более на GitHub .