Menguji Pekerjaan Batch Musim Semi

1. Perkenalan

Tidak seperti aplikasi berbasis Spring lainnya, tugas batch pengujian hadir dengan beberapa tantangan khusus, sebagian besar karena sifat asynchronous tentang bagaimana pekerjaan dijalankan.

Dalam tutorial ini, kita akan menjelajahi berbagai alternatif untuk menguji pekerjaan Spring Batch.

2. Ketergantungan yang Diperlukan

Kami menggunakan spring-boot-starter-batch , jadi pertama-tama mari kita siapkan dependensi yang diperlukan di pom.xml kami :

 org.springframework.boot spring-boot-starter-batch 2.1.9.RELEASE   org.springframework.boot spring-boot-starter-test 2.1.9.RELEASE test   org.springframework.batch spring-batch-test 4.2.0.RELEASE test 

Kami termasuk semi-boo t-starter-test dan semi-batch-test yang membawa beberapa pembantu diperlukan metode, pendengar dan pelari untuk menguji aplikasi Batch Spring.

3. Mendefinisikan Pekerjaan Batch Musim Semi

Mari buat aplikasi sederhana untuk menunjukkan bagaimana Spring Batch memecahkan beberapa tantangan pengujian.

Aplikasi kami menggunakan Pekerjaan dua langkah yang membaca file masukan CSV dengan informasi buku terstruktur dan keluaran buku dan detail buku.

3.1. Mendefinisikan Langkah Kerja

Dua Langkah berikutnya mengekstrak informasi spesifik dari BookRecord dan kemudian memetakannya ke Book (langkah1) dan BookDetail (langkah2):

@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. Mendefinisikan Pembaca Input dan Penulis Output

Sekarang mari kita konfigurasikan pembaca input file CSV menggunakan FlatFileItemReader untuk membatalkan serialisasi informasi buku terstruktur menjadi objek BookRecord :

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

Ada beberapa hal penting dalam definisi ini, yang akan berdampak pada cara kami menguji.

Pertama-tama, kami menganotasi kacang FlatItemReader dengan @StepScope , dan sebagai hasilnya, objek ini akan berbagi masa pakai dengan StepExecution .

Ini juga memungkinkan kita untuk memasukkan nilai dinamis saat runtime sehingga kita bisa meneruskan file masukan kita dari JobParameter di baris 4 . Sebaliknya, token yang digunakan untuk BookRecordFieldSetMapper dikonfigurasi pada waktu kompilasi.

Kami kemudian mendefinisikan penulis keluaran JsonFileItemWriter serupa :

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

Untuk Langkah kedua , kami menggunakan ListItemWriter yang disediakan Spring Batch yang hanya membuang barang ke daftar dalam memori.

3.3. Mendefinisikan JobLauncher Kustom

Selanjutnya, mari kita nonaktifkan konfigurasi peluncuran Pekerjaan default dari Spring Boot Batch dengan menyetel spring.batch.job.enabled = false di application.properties kita .

Kami mengonfigurasi JobLauncher kami sendiri untuk meneruskan instance JobParameters kustom saat meluncurkan Job :

@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. Menguji Pekerjaan Batch Musim Semi

The semi-batch-test ketergantungan menyediakan satu set metode pembantu berguna dan pendengar yang dapat digunakan untuk mengkonfigurasi konteks Musim Semi Batch selama pengujian.

Mari buat struktur dasar untuk pengujian kita:

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

The @SpringBatchTest penjelasan menyediakan JobLauncherTestUtils dan JobRepositoryTestUtils penolong kelas. Kami menggunakannya untuk memicu Pekerjaan dan Langkah dalam pengujian kami.

Aplikasi kami menggunakan konfigurasi otomatis Spring Boot, yang mengaktifkan JobRepository dalam memori default . Akibatnya, menjalankan beberapa pengujian di kelas yang sama memerlukan langkah pembersihan setelah setiap pengujian dijalankan .

Terakhir, jika kita ingin menjalankan beberapa pengujian dari beberapa kelas pengujian, kita perlu menandai konteks kita sebagai kotor . Ini diperlukan untuk menghindari bentrok pada beberapa instance JobRepository yang menggunakan sumber data yang sama.

4.1. Menguji Pekerjaan End-To-End

Hal pertama yang akan kami uji adalah Pekerjaan ujung ke ujung lengkap dengan masukan kumpulan data kecil.

Kami kemudian dapat membandingkan hasil dengan keluaran tes yang diharapkan:

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

Spring Batch Test menyediakan metode perbandingan file yang berguna untuk memverifikasi keluaran menggunakan kelas AssertFile .

4.2. Menguji Langkah Individual

Terkadang cukup mahal untuk menguji Pekerjaan lengkap secara menyeluruh sehingga masuk akal untuk menguji setiap Langkah sebagai gantinya:

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

Perhatikan bahwa kami menggunakan metode launchStep untuk memicu langkah-langkah tertentu .

Ingat bahwa kami juga mendesain ItemReader dan ItemWriter kami untuk menggunakan nilai dinamis saat runtime , yang berarti kami dapat meneruskan parameter I / O kami ke JobExecution (baris 9 dan 23).

Untuk tes Langkah pertama , kami membandingkan keluaran aktual dengan keluaran yang diharapkan.

Di sisi lain, dalam pengujian kedua, kami memverifikasi StepExecution untuk item tertulis yang diharapkan .

4.3. Menguji Komponen Cakupan Langkah

Sekarang mari kita uji FlatFileItemReader . Ingatlah bahwa kami mengeksposnya sebagai @StepScope bean, jadi kami ingin menggunakan dukungan khusus Spring Batch untuk ini :

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

The MetadataInstanceFactory menciptakan kustom StepExecution yang diperlukan untuk menyuntikkan kami Langkah-scoped ItemReader.

Karena itu, kita dapat memeriksa perilaku pembaca dengan bantuan metode doInTestScope .

Selanjutnya, mari kita uji JsonFileItemWriter dan verifikasi outputnya:

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

Tidak seperti pengujian sebelumnya, kami sekarang memiliki kendali penuh atas objek pengujian kami . Akibatnya, kita bertanggung jawab untuk membuka dan menutup I / O stream .

5. Kesimpulan

Dalam tutorial ini, kami telah mempelajari berbagai pendekatan untuk menguji pekerjaan Spring Batch.

Pengujian ujung-ke-ujung memverifikasi pelaksanaan pekerjaan secara lengkap. Menguji langkah individu dapat membantu dalam skenario yang kompleks.

Terakhir, jika menyangkut komponen Step-scoped, kita dapat menggunakan banyak metode pembantu yang disediakan oleh uji batch-pegas. Mereka akan membantu kami menghentikan dan mengejek objek domain Spring Batch.

Seperti biasa, kita dapat menjelajahi basis kode lengkap di GitHub.