Test af et springbatchjob

1. Introduktion

I modsætning til andre forårsbaserede applikationer har test af batchjob nogle specifikke udfordringer, hovedsagelig på grund af den asynkrone karakter af, hvordan job udføres.

I denne vejledning skal vi undersøge de forskellige alternativer til test af et Spring Batch-job.

2. Nødvendige afhængigheder

Vi bruger spring-boot-starter-batch, så lad os først oprette de krævede afhængigheder i vores pom.xml:

 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 prøve 

Vi inkluderede spring-boot-starter-test og spring-batch-test som bringer nogle nødvendige hjælpemetoder, lyttere og løbere til test af Spring Batch-applikationer.

3. Definition af Spring Batch Job

Lad os oprette en enkel applikation til at vise, hvordan Spring Batch løser nogle af testudfordringerne.

Vores applikation bruger et to-trins Job der læser en CSV-inputfil med strukturerede bogoplysninger og output bøger og bogdetaljer.

3.1. Definition af jobtrin

De to efterfølgende Trins udtrække specifikke oplysninger fra BookRecords og kortlæg disse derefter til Bestils (trin 1) og BookDetails (trin 2):

@Bean offentligt trin trin (ItemReader csvItemReader, ItemWriter jsonItemWriter) kaster IOException {return stepBuilderFactory .get ("trin1"). klump (3) .læser (csvItemReader) .processor (bookItemProcessor ()) .writer (jsonItemWriter) .build (); } @Bean offentligt trin trin2 (ItemReader csvItemReader, ItemWriter listItemWriter) {return stepBuilderFactory .get ("step2"). klump (3) .reader (csvItemReader) .processor (bookDetailsItemProcessor ()) .writer (listItemWriter) .build (); }

3.2. Definition af Input Reader og Output Writer

Lad os nu konfigurer CSV-filinputlæser ved hjælp af en FlatFileItemReader at de-serialisere de strukturerede bogoplysninger til BookRecord genstande:

privat statisk endelig String [] TOKENS = {"bognavn", "bogforfatter", "bogformat", "isbn", "udgivelsesår"}; @Bean @StepScope offentlig FlatFileItemReader csvItemReader (@Value ("# {jobParameters ['file.input']")) Strenginput) {FlatFileItemReaderBuilder builder = ny FlatFileItemReaderBuilder (); FieldSetMapper bookRecordFieldSetMapper = ny BookRecordFieldSetMapper (); return builder .name ("bookRecordItemReader") .resource (ny FileSystemResource (input)) .delimiteret () .navne (TOKENS) .fieldSetMapper (bookRecordFieldSetMapper) .build (); }

Der er et par vigtige ting i denne definition, som vil få konsekvenser for den måde, vi tester på.

Først og fremmest, vi kommenterede FlatItemReader bønne med @StepScope, og som et resultat dette objekt vil dele dets levetid med StepExecution.

Dette giver os også mulighed for at indsprøjte dynamiske værdier ved kørsel, så vi kan sende vores inputfil fra JobParameters i linje 4. I modsætning hertil er de tokens, der bruges til BookRecordFieldSetMapper konfigureres ved kompileringstid.

Derefter definerer vi ligeledes JsonFileItemWriter output forfatter:

@Bean @StepScope offentlig JsonFileItemWriter jsonItemWriter (@Value ("# {jobParameters ['file.output']}") Strengoutput) kaster IOException {JsonFileItemWriterBuilder builder = ny JsonFileItemWriterBuilder (); JacksonJsonObjectMarshaller marshaller = ny JacksonJsonObjectMarshaller (); return builder .name ("bookItemWriter") .jsonObjectMarshaller (marshaller) .resource (ny FileSystemResource (output)) .build (); } 

For det andet Trin, vi bruger en Spring Batch-leveret ListItemWriter der bare dumper ting til en hukommelsesliste.

3.3. Definition af brugerdefineret JobLauncher

Lad os derefter deaktivere standard Job starte konfiguration af Spring Boot Batch ved at indstille spring.batch.job.enabled = false i vores application.properties.

Vi konfigurerer vores egne JobLauncher at bestå en skik Jobparametre eksempel når du starter Job:

@SpringBootApplication offentlig klasse SpringBatchApplication implementerer CommandLineRunner {// autowired jobLauncher og transformBooksRecordsJob @Value ("$ {file.input}") privat String input; @Value ("$ {file.output}") privat strengoutput; @ Override public void run (String ... args) kaster undtagelse {JobParametersBuilder paramsBuilder = ny JobParametersBuilder (); paramsBuilder.addString ("file.input", input); paramsBuilder.addString ("file.output", output); jobLauncher.run (transformBooksRecordsJob, paramsBuilder.toJobParameters ()); } // andre metoder (hoved osv.)} 

4. Test af Spring Batch Job

Det spring-batch-test afhængighed giver et sæt nyttige hjælpemetoder og lyttere, der kan bruges til at konfigurere Spring Batch-konteksten under test.

Lad os oprette en grundlæggende struktur til vores test:

@RunWith (SpringRunner.class) @SpringBatchTest @EnableAutoConfiguration @ContextConfiguration (classes = {SpringBatchConfiguration.class}) @TestExecutionListeners ({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.ass) Class) andre testkonstanter @Autowired private JobLauncherTestUtils jobLauncherTestUtils; @Autowired private JobRepositoryTestUtils jobRepositoryTestUtils; @ Efter offentlig ugyldighed cleanUp () {jobRepositoryTestUtils.removeJobExecutions (); } private JobParameters standardJobParameters () {JobParametersBuilder paramsBuilder = nye JobParametersBuilder (); paramsBuilder.addString ("file.input", TEST_INPUT); paramsBuilder.addString ("file.output", TEST_OUTPUT); returner paramsBuilder.toJobParameters (); } 

Det @SpringBatchTest kommentar giver JobLauncherTestUtils og JobRepositoryTestUtils hjælperklasser. Vi bruger dem til at udløse Job og Trins i vores test.

Vores applikation bruger Spring Boot auto-konfiguration, som muliggør en standard in-memory Jobopbevaring. Som resultat, at køre flere tests i samme klasse kræver et oprydningstrin efter hver testkørsel.

Langt om længe, hvis vi vil køre flere tests fra flere testklasser, skal vi markere vores kontekst som beskidt. Dette er nødvendigt for at undgå sammenstød mellem flere Jobopbevaring forekomster, der bruger den samme datakilde.

4.1. Test af slutningen til slutningen Job

Den første ting, vi tester, er en komplet ende-til-ende Job med et lille datasæt-input.

Vi kan derefter sammenligne resultaterne med en forventet testoutput:

@Test offentlig ugyldighed givenReferenceOutput_whenJobExecuted_thenSuccess () kaster undtagelse {// givet FileSystemResource expectResult = ny FileSystemResource (EXPECTED_OUTPUT); FileSystemResource actualResult = ny FileSystemResource (TEST_OUTPUT); // når JobExecution jobExecution = jobLauncherTestUtils.launchJob (defaultJobParameters ()); JobInstance actualJobInstance = jobExecution.getJobInstance (); ExitStatus actualJobExitStatus = jobExecution.getExitStatus (); // derefter assertThat (actualJobInstance.getJobName (), er ("transformBooksRecords")); assertThat (actualJobExitStatus.getExitCode (), er ("KOMPLET")); AssertFile.assertFileEquals (expectResult, actualResult); }

Spring Batch Test giver et nyttigt metoden til sammenligning af filer til verificering af output ved hjælp af AssertFile klasse.

4.2. Test af individuelle trin

Nogle gange er det ret dyrt at teste det komplette Job ende-til-ende, og det giver mening at teste individet Trin i stedet:

@Test offentlig ugyldighed givenReferenceOutput_whenStep1Executed_thenSuccess () kaster undtagelse {// givet FileSystemResource expectResult = ny FileSystemResource (EXPECTED_OUTPUT); FileSystemResource actualResult = ny FileSystemResource (TEST_OUTPUT); // når JobExecution jobExecution = jobLauncherTestUtils.launchStep ("trin1", defaultJobParameters ()); Samling actualStepExecutions = jobExecution.getStepExecutions (); ExitStatus actualJobExitStatus = jobExecution.getExitStatus (); // derefter assertThat (actualStepExecutions.size (), er (1)); assertThat (actualJobExitStatus.getExitCode (), er ("KOMPLET")); AssertFile.assertFileEquals (expectResult, actualResult); } @Test offentlig ugyldig nårStep2Executed_thenSuccess () {// når JobExecution jobExecution = jobLauncherTestUtils.launchStep ("step2", defaultJobParameters ()); Samling actualStepExecutions = jobExecution.getStepExecutions (); ExitStatus actualExitStatus = jobExecution.getExitStatus (); // derefter assertThat (actualStepExecutions.size (), er (1)); assertThat (actualExitStatus.getExitCode (), er ("KOMPLET")); actualStepExecutions.forEach (stepExecution -> {assertThat (stepExecution.getWriteCount (), er (8));}); }

Læg mærke til det vi bruger launchStep metode til at udløse specifikke trin.

Huske på, at vi designede også vores ItemReader og ItemWriter at bruge dynamiske værdier ved kørsel, hvilket betyder vi kan videregive vores I / O-parametre til Jobudførelse(linje 9 og 23).

For det første Trin test sammenligner vi den faktiske output med en forventet output.

På den anden side, i den anden test bekræfter vi StepExecution for de forventede skriftlige emner.

4.3. Afprøvning af trin-scoped komponenter

Lad os nu teste FlatFileItemReader. Husk at vi udsatte det som @StepScope bønne, så vi vil gerne bruge Spring Batchs dedikerede support til dette:

// tidligere autowired itemReader @Test offentlig ugyldighed givenMockedStep_whenReaderCalled_thenSuccess () kaster undtagelse {// givet StepExecution stepExecution = MetaDataInstanceFactory .createStepExecution (defaultJobParameters ()); // når StepScopeTestUtils.doInStepScope (stepExecution, () -> {BookRecord bookRecord; itemReader.open (stepExecution.getExecutionContext ()); while ((bookRecord = itemReader.read ())! = null) {// derefter assertThat (bookRecord .getBookName (), er ("Foundation")); assertThat (bookRecord.getBookAuthor (), er ("Asimov I.")); assertThat (bookRecord.getBookISBN (), er ("ISBN 12839")); assertThat ( bookRecord.getBookFormat (), er ("hardcover")); assertThat (bookRecord.getPublishingYear (), er ("2018"));} itemReader.close (); return null;}); }

Det MetadataInstanceFactory opretter en brugerdefineret StepExecution der er nødvendigt for at indsprøjte vores Step-scoped ItemReader.

På grund af dette, vi kan kontrollere læsernes opførsel ved hjælp af doInTestScope metode.

Lad os derefter teste JsonFileItemWriter og kontrollere dens output:

@Test offentlig ugyldighed givenMockedStep_whenWriterCalled_thenSuccess () kaster undtagelse {// givet FileSystemResource expectResult = ny FileSystemResource (EXPECTED_OUTPUT_ONE); FileSystemResource actualResult = ny FileSystemResource (TEST_OUTPUT); Book demoBook = ny bog (); demoBook.setAuthor ("Grisham J."); demoBook.setName ("Firmaet"); StepExecution stepExecution = MetaDataInstanceFactory .createStepExecution (defaultJobParameters ()); // når StepScopeTestUtils.doInStepScope (stepExecution, () -> {jsonItemWriter.open (stepExecution.getExecutionContext ()); jsonItemWriter.write (Arrays.asList (demoBook)); jsonItemWriter.close (); return null;}); // derefter AssertFile.assertFileEquals (expectResult, actualResult); } 

I modsætning til de tidligere tests, vi har nu fuld kontrol over vores testobjekter. Som resultat, vi er ansvarlige for at åbne og lukke I / O-streams.

5. Konklusion

I denne vejledning har vi undersøgt de forskellige tilgange til test af et Spring Batch-job.

End-to-end-test bekræfter den fuldstændige udførelse af jobbet. Test af individuelle trin kan hjælpe i komplekse scenarier.

Endelig, når det kommer til Step-scoped-komponenter, kan vi bruge en masse hjælpemetoder leveret af spring-batch-test. De hjælper os med at stubbe og spotte Spring Batch-domæneobjekter.

Som sædvanligt kan vi udforske den komplette codebase på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found