Voici BDD appliqué à Spring Batch, le batch d’extraction sur Github springbatch-sample sert de support.
Cucumber-JVM a permis d’exécuter et d’implémenter très simplement les étapes correspondantes à des scénarios décrits dans le formalisme BDD.
Etant donnée les 2 scénarios d’extraction en langage Gherkin
Feature: batch extraction+ | |
Scenario: scenario 1 | |
Given les utilisateurs | |
|Id|Nom|Prenom| | |
|1|nom1|prenom1| | |
|2|nom2|prenom2| | |
When je charge les utilisateurs en base de données | |
And j'execute le job d'extraction | |
Then mon fichier de sortie contient les lignes | |
|1,prenom1,nom1| | |
|2,prenom2,nom2| | |
Scenario: scenario 2 | |
Given les utilisateurs | |
|Id|Nom|Prenom| | |
|1|nom1|prenom1| | |
When je charge les utilisateurs en base de données | |
And j'execute le job d'extraction | |
Then mon fichier de sortie contient les lignes | |
|1,prenom1,nom1| |
On configure le runner Junit
package com.giovanetti.sample.batch.job.cucumber; | |
import com.giovanetti.support.batch.rule.BatchProperties; | |
import cucumber.api.CucumberOptions; | |
import cucumber.api.junit.Cucumber; | |
import org.junit.BeforeClass; | |
import org.junit.runner.RunWith; | |
@RunWith(Cucumber.class) | |
@CucumberOptions(format = {"pretty", "html:target/cucumber-report"}, | |
glue = "com.giovanetti.sample.batch.job.cucumber") | |
public class ExtractionFeatureTest { | |
@BeforeClass | |
public static void setupClass() { | |
BatchProperties.getDefault().create(); | |
} | |
} |
Et on implémente les étapes
Dbsetup permet d’insérer directement les données des scénarios en base de données.
package com.giovanetti.sample.batch.job.cucumber; | |
import com.giovanetti.sample.batch.configuration.JobExtractionTestConfiguration; | |
import com.giovanetti.sample.batch.item.User; | |
import com.giovanetti.sample.batch.job.JobExtractionConfiguration; | |
import com.giovanetti.support.batch.annotations.FunctionalDataSource; | |
import com.ninja_squad.dbsetup.DbSetup; | |
import com.ninja_squad.dbsetup.destination.DataSourceDestination; | |
import com.ninja_squad.dbsetup.operation.Insert; | |
import cucumber.api.DataTable; | |
import cucumber.api.java.After; | |
import cucumber.api.java.Before; | |
import cucumber.api.java.en.Given; | |
import cucumber.api.java.en.Then; | |
import cucumber.api.java.en.When; | |
import org.apache.commons.io.FileUtils; | |
import org.springframework.batch.core.JobParametersBuilder; | |
import org.springframework.batch.test.JobLauncherTestUtils; | |
import org.springframework.test.annotation.DirtiesContext; | |
import org.springframework.test.context.ContextConfiguration; | |
import javax.inject.Inject; | |
import javax.sql.DataSource; | |
import java.io.File; | |
import java.io.IOException; | |
import java.nio.file.Files; | |
import java.util.List; | |
import static com.ninja_squad.dbsetup.Operations.insertInto; | |
import static org.assertj.core.api.Assertions.assertThat; | |
@ContextConfiguration(classes = {JobExtractionTestConfiguration.class, JobLauncherTestUtils.class}) | |
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) | |
public class ExtractionSteps { | |
private static final String USER_TABLE = "USER"; | |
private static final String[] USER_COLUMNS = new String[]{"ID", "NOM", "PRENOM"}; | |
private File outputFile; | |
private DataSourceDestination destination; | |
@Inject | |
@FunctionalDataSource | |
private void setDataSourceDestination(DataSource dataSource) { | |
destination = new DataSourceDestination((dataSource)); | |
} | |
@Inject | |
private JobLauncherTestUtils jobLauncherTestUtils; | |
private List<User> users; | |
@Before | |
public void createOutputFile() throws IOException { | |
outputFile = createTempFile(); | |
} | |
private File createTempFile() throws IOException { | |
File tempFolder = File.createTempFile("junit", ""); | |
if (tempFolder.delete() && tempFolder.mkdir()) { | |
return File.createTempFile("junit", null, tempFolder); | |
} else { | |
throw new IllegalStateException("createTempFile fail"); | |
} | |
} | |
@After | |
public void deleteOutputFile() throws IOException { | |
FileUtils.deleteDirectory(outputFile.getParentFile()); | |
} | |
@Given("^les utilisateurs$") | |
public void les_utilisateurs(DataTable table) { | |
users = table.asList(User.class); | |
} | |
@When("^je charge les utilisateurs en base de données$") | |
public void je_charge_les_utilisateurs_en_base_de_données() { | |
Insert.Builder insertBuilder = insertInto(USER_TABLE).columns(USER_COLUMNS); | |
users.forEach(user -> insertBuilder.values(user.getId(), user.getNom(), user.getPrenom())); | |
new DbSetup(destination, insertBuilder.build()).launch(); | |
} | |
@When("^j'execute le job d'extraction$") | |
public void j_execute_le_job_d_extraction() throws Exception { | |
jobLauncherTestUtils.launchJob( | |
new JobParametersBuilder().addString(JobExtractionConfiguration.OUTPUT_FILE_PARAMETER, | |
outputFile.getPath()).toJobParameters()); | |
} | |
@Then("^mon fichier de sortie contient les lignes$") | |
public void mon_fichier_de_sortie_contient_les_lignes(List<String> lines) throws IOException { | |
assertThat(Files.readAllLines(outputFile.toPath())).hasSize(lines.size()).containsAll(lines); | |
} | |
} |
Concernant l’alternative JBehave en tant que framework BDD à la place de Cucumber, la mise en place s’est avérée moins rapide et la configuration plus verbeuse, un exemple est aussi disponible dans springbatch-sample