Testing av en vårpartijobb

1. Introduksjon

I motsetning til andre vårbaserte applikasjoner har testing av batchjobber noen spesifikke utfordringer, hovedsakelig på grunn av den asynkrone naturen til hvordan jobber utføres.

I denne opplæringen skal vi utforske de forskjellige alternativene for å teste en Spring Batch-jobb.

2. Nødvendige avhengigheter

Vi bruker spring-boot-starter-batch, så la oss først sette opp de nødvendige avhengighetene i vår 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 test 

Vi inkluderte spring-boot-starter-test og vår-batch-test som bringer inn noen nødvendige hjelpermetoder, lyttere og løpere for å teste Spring Batch-applikasjoner.

3. Definere vårpartijobben

La oss lage et enkelt program for å vise hvordan Spring Batch løser noen av testutfordringene.

Søknaden vår bruker et totrinn Jobb som leser en CSV-inndatafil med strukturert bokinformasjon og skriver ut bøker og bokdetaljer.

3.1. Definere jobbtrinnene

De to påfølgende Stegs trekke ut spesifikk informasjon fra BookRecords og deretter kartlegge disse til Boks (trinn 1) og BookDetails (trinn 2):

@Bean offentlig trinn 1 (ItemReader csvItemReader, ItemWriter jsonItemWriter) kaster IOException {retur stepBuilderFactory .get ("trinn1"). klump (3) .leser (csvItemReader). prosessor (bookItemProcessor ()) .forfatter (jsonItemWriter) .bygg (); } @Bean public Step Step2 (ItemReader csvItemReader, ItemWriter listItemWriter) {return stepBuilderFactory .get ("step2"). klump (3) .leser (csvItemReader). prosessor (bookDetailsItemProcessor ()) .writer (listItemWriter) .build (); }

3.2. Definere Input Reader og Output Writer

La oss nå konfigurere CSV-filinnleseren ved hjelp av a FlatFileItemReader å avserialisere den strukturerte bokinformasjonen til BookRecord gjenstander:

private static final String [] TOKENS = {"bookname", "bookauthor", "bookformat", "isbn", "publishyear"}; @Bean @StepScope offentlig FlatFileItemReader csvItemReader (@Value ("# {jobParameters ['file.input']}") Strenginngang) {FlatFileItemReaderBuilder-byggmester = ny FlatFileItemReaderBuilder (); FieldSetMapper bookRecordFieldSetMapper = ny BookRecordFieldSetMapper (); return builder .name ("bookRecordItemReader") .resource (new FileSystemResource (input)) .delimited () .names (TOKENS) .fieldSetMapper (bookRecordFieldSetMapper) .build (); }

Det er et par viktige ting i denne definisjonen, som vil få betydning for måten vi tester ut.

Først av alt, vi kommenterte FlatItemReader bønne med @StepScope, og som et resultat, dette objektet vil dele sin levetid med StepExecution.

Dette lar oss også injisere dynamiske verdier ved kjøretid, slik at vi kan sende inngangsfilen vår fra JobParameters i linje 4. I kontrast, symbolene som brukes til BookRecordFieldSetMapper er konfigurert på kompileringstid.

Vi definerer da på samme måte JsonFileItemWriter output skribent:

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

For det andre Steg, bruker vi en Spring Batch-levert ListItemWriter som bare slipper ting til en in-memory-liste.

3.3. Definere egendefinert JobLauncher

La oss deretter deaktivere standard Jobb starte konfigurasjon av Spring Boot Batch ved å innstille spring.batch.job.enabled = false i vår application.properties.

Vi konfigurerer våre egne JobLauncher å passere en skikk JobParameters eksempel når du starter Jobb:

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

4. Testing av vårpartiet

De vår-batch-test avhengighet gir et sett med nyttige hjelpermetoder og lyttere som kan brukes til å konfigurere Spring Batch-konteksten under testing.

La oss lage en grunnleggende struktur for testen vår:

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

De @SpringBatchTest kommentar gir JobLauncherTestUtils og JobRepositoryTestUtils hjelperklasser. Vi bruker dem til å utløse Jobb og Stegs i testene våre.

Vår applikasjon bruker Spring Boot auto-konfigurasjon, som muliggjør et standard minne JobRepository. Som et resultat, å kjøre flere tester i samme klasse krever et oppryddingstrinn etter hver testkjøring.

Endelig, hvis vi vil kjøre flere tester fra flere testklasser, må vi merke konteksten vår som skitten. Dette er nødvendig for å unngå sammenstøt mellom flere JobRepository forekomster som bruker samme datakilde.

4.1. Testing av end-to-end Jobb

Det første vi skal teste er en komplett ende-til-ende Jobb med en liten datasettinngang.

Vi kan deretter sammenligne resultatene med en forventet testutgang:

@Test offentlig ugyldig givenReferenceOutput_whenJobExecuted_thenSuccess () kaster unntak {// gitt 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 (); // deretter assertThat (actualJobInstance.getJobName (), er ("transformBooksRecords")); assertThat (actualJobExitStatus.getExitCode (), er ("KOMPLETT")); AssertFile.assertFileEquals (forventet resultat, faktisk resultat); }

Spring Batch Test gir en nyttig filsammenligningsmetode for å verifisere utganger ved hjelp av AssertFile klasse.

4.2. Testing av individuelle trinn

Noen ganger er det ganske dyrt å teste helheten Jobb ende-til-ende, og det er derfor fornuftig å teste individet Fremgangsmåte i stedet:

@Test offentlig ugyldighet gittReferenceOutput_whenStep1Executed_thenSuccess () kaster unntak {// gitt FileSystemResource expectResult = ny FileSystemResource (EXPECTED_OUTPUT); FileSystemResource actualResult = ny FileSystemResource (TEST_OUTPUT); // når JobExecution jobExecution = jobLauncherTestUtils.launchStep ("trinn1", defaultJobParameters ()); Collection actualStepExecutions = jobExecution.getStepExecutions (); ExitStatus actualJobExitStatus = jobExecution.getExitStatus (); // deretter assertThat (actualStepExecutions.size (), er (1)); assertThat (actualJobExitStatus.getExitCode (), er ("KOMPLETT")); AssertFile.assertFileEquals (forventet resultat, faktisk resultat); } @Test offentlig ugyldig nårStep2Executed_thenSuccess () {// når JobExecution jobExecution = jobLauncherTestUtils.launchStep ("step2", defaultJobParameters ()); Collection actualStepExecutions = jobExecution.getStepExecutions (); ExitStatus actualExitStatus = jobExecution.getExitStatus (); // deretter assertThat (actualStepExecutions.size (), er (1)); assertThat (actualExitStatus.getExitCode (), er ("KOMPLETT")); actualStepExecutions.forEach (stepExecution -> {assertThat (stepExecution.getWriteCount (), er (8));}); }

Legg merke til det vi bruker launchStep metode for å utløse spesifikke trinn.

Husk at vi designet også vår ItemReader og ItemWriter å bruke dynamiske verdier ved kjøretid, som betyr vi kan overføre våre I / O-parametere til JobExecution(linje 9 og 23).

Først og fremst Steg test, sammenligner vi den faktiske produksjonen med en forventet produksjon.

På den andre siden, i den andre testen bekrefter vi StepExecution for de forventede skriftlige varene.

4.3. Testing av trinnkomponenter

La oss nå teste FlatFileItemReader. Husk at vi avslørte det som @StepScope så vi vil bruke Spring Batchs dedikerte støtte for dette:

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

De MetadataInstanceFactory oppretter en skikk StepExecution som er nødvendig for å injisere Step-scoped ItemReader.

På grunn av dette, vi kan sjekke atferden til leseren ved hjelp av doInTestScope metode.

La oss deretter teste JsonFileItemWriter og verifiser utdataene:

@Test offentlig ugyldighet givenMockedStep_whenWriterCalled_thenSuccess () kaster unntak {// gitt FileSystemResource expectResult = ny FileSystemResource (EXPECTED_OUTPUT_ONE); FileSystemResource actualResult = ny FileSystemResource (TEST_OUTPUT); Book demoBook = new Book (); demoBook.setAuthor ("Grisham J."); demoBook.setName ("The Firm"); StepExecution stepExecution = MetaDataInstanceFactory .createStepExecution (defaultJobParameters ()); // når StepScopeTestUtils.doInStepScope (stepExecution, () -> {jsonItemWriter.open (stepExecution.getExecutionContext ()); jsonItemWriter.write (Arrays.asList (demoBook)); jsonItemWriter.close (); return null;}); // deretter AssertFile.assertFileEquals (forventet resultat, faktisk resultat); } 

I motsetning til tidligere tester, vi har nå full kontroll over testobjektene våre. Som et resultat, vi er ansvarlige for å åpne og lukke I / O-strømmer.

5. Konklusjon

I denne opplæringen har vi utforsket de ulike tilnærmingene til å teste en Spring Batch-jobb.

End-to-end-testing verifiserer fullført utførelse av jobben. Testing av individuelle trinn kan hjelpe i komplekse scenarier.

Til slutt, når det gjelder Step-scoped-komponenter, kan vi bruke en rekke hjelpermetoder som tilbys av vår-batch-test. De vil hjelpe oss med å stikke og spotte Spring Batch-domeneobjekter.

Som vanlig kan vi utforske hele kodebasen på GitHub.


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