Tests Web en JS partie 2/3 – Pattern Page Object avec Zombie.js et BDD avec Cucumber-js

Suite à l’article 1/3 je vais montrer comment faire des tests web fonctionnels avec le formalisme BDD à l’aide de Cucumber-js et l’implémentation du composant Page Object avec Zombie.js.

A la différence de CasperJS qui s’appuie sur PhantomJS pour l’exécution des tests, Zombie.js s’exécute dans Node.js.

On obtient donc la stack de test :

  • Zombie.js + Cucumber-js sur Node.js
  • Versus CasperJS sur PhantomJS dans l’article précédent

L’exemple complet en CucumberJs + ZombieJs est disponible sur Github sur la branche zombiejs-it-tests (voir README pour l’exécution des tests).

Composant Page Object avec Zombie.js

  • Contient tous les appels à l’API Zombie.js
  • Encapsule les sélecteurs CSS
  • Expose les fonctions pour lancer les actions sur le SUT (System Under Test)
  • Expose les fonctions pour récupérer les informations sur l’état du SUT

Ecriture des TI avec Mocha / Chai, exemple de l’ajout des todos

  • Description de la suite avec la syntaxe Mocha
  • Lance les actions par l’intermédiaire du Page Object
  • Effectue les vérifications avec les assertions Chai

Scénario de l’ajout en BDD

Implémentation des step Cucumber-js pour faire la liaison avec le BDD

  • Instance Cucumber this fournie les fonctions Given, When, Then
  • Given génère des todos en tant que pré-requis de certains scénarios
  • When lance les actions par l’intermédiaire du Page Object
  • Then effectue les vérifications avec les assertions Chai

Limitations de Zombie.js

  • Ne s’exécute pas dans un browser standard
  • Documentation succinte
  • Recours à l’exécution d’expressions JS pour certaines actions
  • Click sur les filtres des todos gérés par le routeur Backbone.js non pris en compte
  • Pas de screenshot pour le debug

Cas d’usage possible

  • Tests fonctionnels BDD headless vraiment « Insanely fast » (comme le dit le slogan de Zombie.js)
  •   Mais pas des tests vraiment End To End car n’utilise pas un vrai browser et risque de ne pas toujours reproduire fidèlement le comportement du end user
  • Remarque : l’écriture des TI en plus du BDD est présentée ici à titre de comparaison de l’article 1 mais n’est pas toujours indispensable dans un projet réel

Pour réaliser des tests BDD vraiment orientés End to End avec les browsers utilisés par les utilisateurs finaux on va utiliser la techno Selenium dans le dernier article de la série.

Publicités

Tests Web en JS partie 1/3 – Pattern Page Object avec CasperJS

Voici une série de 3 articles sur les tests d’intégration web en Javascript.

La popularité de la todo liste backbone comme exemple de code en fait un candidat idéal pour présenter les technos de test JS.

Un point commun de tous les exemples présentés est l’utilisation du pattern Page Object pour bien séparer l’écriture de la logique de test de la technique d’accès au système testé (SUT).

Les frameworks utilisés pour chaque articles :

L’exemple complet en CasperJS est disponible sur Github sur la branche master (voir le README pour exécuter les tests).

Composant Page Object avec CasperJS

  • Contient tous les appels à l’API Casper
  • Encapsule les sélecteurs CSS
  • Expose les fonctions pour lancer les actions sur le SUT
  • Expose les fonctions pour récupérer les informations sur l’état du SUT

Ecriture des TI avec Mocha / Chai, exemple de l’ajout des todos

  • Description de la suite avec la syntaxe Mocha
  • Lance les actions par l’intermédiaire du Page Object
  • Effectue les vérifications avec les assertions Chai
  • L’adhérence avec CasperJS en terme de gestion des étapes est masquée par la fonction test du Page Object

Limitations de CasperJS

  • Conçu au dessus de PhantomJS ne s’intègre donc pas par défaut avec le framework BDD Cucumber-js qui s’exécute dans Node.js
  • Les browsers les plus répandus (Chrome, Firefox,…) ne sont pas supportés

Cas d’usage possible

  • Tests d’intégrations frontend headless avec stub service

Dans l’article 2/3 on va utiliser le framework Zombie.js pour mettre en place simplement des tests BDD avec Cucumber-js.

Tests End-to-End avec Docker

Ce post montre avec un exemple concret comment utiliser Docker pour faire un test e2e qui déroule le scénario :

  • Etant donnée une base de données Mysql
  • Un batch alimente la base de données avec un fichier csv
  • Une application REST est déployée telle que son stockage s’appuie la base de données
  • On vérifie que l’api REST permet de consulter les données persistées et d’en ajouter

Pré – requis pour exécuter l’exemple

  • JDK 8 et Maven
  • Sur un hôte non Linux, un moyen rapide pour lancer des container docker en partant de rien est d’installer docker machine et le client docker (l’exemple a été testé sous Mac OS).
  • Démarrer la VM host des container docker et configurer les variables d’environnement pour le client docker (voir doc d’installation docker machine)

Cloner les repos Github du test e2e, sources et configuration

Pour exécuter le test e2e complet, lancer la commande : cd e2etest && source env.sh && ./run.sh


Explication de l’exécution du test phase par phase (build, pré-intégration, test, post-intégration)

Build Maven du batch springbatch-sample et de la restapp spring-boot-sample à partir des sources

  •  mvn clean package

Pré-intégration (voir script docker-run.sh)

  • lancement container docker mysql « mysqldb »
  • exécution dans container « mysqldb » d’un ping mysqladmin à intervalle d’une seconde pour attendre que mysql soit prêt à recevoir des connexions
  • exécution création schémas base de données technique Spring batch et fonctionnelle dans le container « mysqldb »
  • lancement du batch java Spring batch dans le container java8 « java »
  • lancement container java8 « webapp » et démarrage du jar restapp exécutable
  • exécution dans container « webapp » d’une vérification à intervalle d’une seconde que tomcat est disponible sur port 8080

docker-run.sh et explications commandes docker :

docker run --name mysqldb -d -v $work/configuration/:/configuration -v $e2e/pre-integration/:/pre-integration \
 -e MYSQL_USER=mysql -e MYSQL_PASSWORD=mysql -e MYSQL_ROOT_PASSWORD=dba -p 3306:3306 mysql:5.7.7
docker exec mysqldb bash /pre-integration/wait/mysql.sh

docker exec mysqldb bash -c "mysql -u root -pdba < configuration/sql/functional-schema.sql"
docker exec mysqldb bash -c "mysql -u root -pdba < configuration/sql/technical-schema.sql"

batch_name=springbatch-sample
job_name=alimentationJob
job_param=input.file.path=/pre-integration/input/alimentation.csv
docker run --rm -v $work/configuration/:/configuration -v $work/dist/:/dist -v $e2e/pre-integration/:/pre-integration \
 java:8 java -Dbatch.properties.path=file:/configuration/properties/batch.properties -Djob.name=$job_name \
 -jar /dist/$batch_name.jar $job_param \
 > $work/log/batch.log

restapp_name=spring-boot-sample
redirect_log="2>&1 | tee /var/log/restapp.log"
run_restapp="java -jar /dist/$restapp_name.jar --spring.config.location=file:/configuration/properties/application.properties $redirect_log"
docker run --name webapp -d -v $work/configuration/:/configuration -v $work/dist/:/dist -v $work/log/:/var/log \
 -v $e2e/pre-integration/:/pre-integration --expose 8080 -p 8080:8080 \
 java:8 bash -c "$run_restapp"
docker exec webapp bash /pre-integration/wait/tomcat.sh
  • docker run : commande pour lancer les containers « mysqldb », « java » et « webapp »
  • les containers « mysqldb » et « webapp » s’exécutent en fond (mode detached -d)
  • suppression du container « java » après après la fin de l’exécution du batch avec la clause –rm
  • les ressource présentes sur l’hôte sont partagées avec la clause -v (jar, fichiers de conf,..)
  • container « webapp » expose et publie le port 8080 vers le host –expose et -p
  • docker exec lance des scripts bash à l’intérieur du container pour attendre que mysql et tomcat soient up (voir scripts complémentaires d’attente ci-après)

Scripts d’attente

  • démarrage mysql
while [ $(mysqladmin -u root -pdba ping | grep 'mysqld is alive' | wc -l) -ne 1 ] 
do
 sleep 1
 echo -n "."
done
  • démarrage tomcat
while [ $(cat /var/log/restapp.log | grep 'Tomcat started on port' | wc -l) -ne 1 ] 
do
 sleep 1
 echo -n "."
done

Test des ressources REST avec CURL

curl -s http://$host:8080/personnes/search?nom=nom1 2>&1 | tee $work/log/test.log
echo -e "\nSHOULD FOUND 1 personne"
curl -H "Content-Type: application/json" -X POST -d '{"nom":"nom3","prenom":"prenom3"}' http://$host:8080/personnes
curl -H "Content-Type: application/json" -X POST -d '{"nom":"nom3","prenom":"prenom4"}' http://$host:8080/personnes
curl -s http://$host:8080/personnes/search?nom=nom3 2>&1 | tee -a $work/log/test.log
echo -e "\nSHOULD FOUND 2 personnes"
  • une ressource personne telle que nom=nom1 existe déjà suite au lancement du batch
  • 2 ressources personne telles que nom=nom3 sont ajoutées avec succès
  • les 2 ressources personne telles nom=nom3 sont bien récupérées

Post-intégration

  • arrêt et suppression des containers docker
docker stop webbap
docker rm webapp
docker stop mysqldb
docker rm mysqldb

Pour aller plus loin dans l’industrialisation

  • utiliser un repository d’artefacts pour récupérer les jars
  • mapper les phases du test e2e dans les phases standard d’un build Maven
  • le test curl peut être remplacé par un client de test REST basé sur JUnit dans la phase Maven intégration-test

Test d’intégration d’une application REST avec Spring Boot

L’article montre comment construire une application Spring Boot exposant un service REST en étant guidé par un test d’intégration.

On va développer un service simple de mise à jour et de recherche de personnes. La démarche TDD est applicable.

Spring Boot permet d’utiliser les versions des spécifications de Java EE 7, à savoir JPA 2.1 (hibernate provider), JAX-RS 2 (jersey provider) et Bean Validation.

Le code source complet est disponible dans Github

Configuration maven

Ajout des dépendances Spring Boot starter web, jersey, data-jpa et test.

Ajout du Spring Boot maven plugin pour gérer le packaging et l’exécution « in-place ».

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.2.3.RELEASE</version>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jersey</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.0.0</version>
    <scope>test</scope>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

Configuration de l’application REST

Mise en place de la configuration jersey et du bootstrap de l’application.

@SpringBootApplication
public class RestApplication {

  @Bean
  public ResourceConfig jerseyConfig() {
    ResourceConfig resourceConfig = new ResourceConfig();
    resourceConfig.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
    resourceConfig.packages(RestApplication.class.getPackage().getName());
    return resourceConfig;
  }

  public static void main(String[] args) {
    SpringApplication.run(RestApplication.class, args);
  }

}

Test d’intégration du service REST

Au lancement du test, l’application est servie par un Tomcat sur un port aléatoire non utilisé.

Spring Data JPA exporte le schéma SQL dans la base de données mémoire H2.

Il n’y a plus qu’à écrire des tests basés sur des clients REST.

Entity Personne JPA

@Entity
public class Personne {

  private Personne() {}

  public Personne(String prenom, String nom) {
    this.prenom = prenom;
    this.nom = nom;
  }

  @Id
  @GeneratedValue(strategy= GenerationType.AUTO)
  @JsonIgnore
  private long id;

  private String prenom;

  @Size(min=2)
  private String nom;

  public String getPrenom() {
    return prenom;
  }

  public String getNom() {
    return nom;
  }
}

Repository Personne Spring Data JPA

Rien de plus à écrire dans le cas d’un CRUD.

 public interface PersonneRepository extends CrudRepository<Personne,Long> {

   List<Personne> findPersonneByNom(String nom);

 }

Service REST

A noter que JAX-RS 2 s’intègre à Bean Validation de manière à déclencher les validations automatiquement lors des appels REST.

@Path("/personnes")
@Produces({MediaType.APPLICATION_JSON})
public class PersonneService {

  @Inject
  private PersonneRepository personneRepository;

  @GET
  @Path("/search")
  public Iterable<Personne> findPersonnesByNom(@Size(min=2) @QueryParam("nom") String nom) {
   return personneRepository.findPersonneByNom(nom);
  }

  @POST
  public Response savePersonne(@Valid Personne personne) {
    personneRepository.save(personne);
    return Response.status(Response.Status.CREATED).build();
  }

}

Packager et exécuter un job Spring Batch avec Spring Boot

Cet article fait suite aux précédents posts sur TDD et BDD avec Spring Batch.

Le but est de packager et exécuter directement un batch Spring Batch sans ajout de configuration spécifique de packaging ni d’installation préalable d’environnement d’exécution particulier.

Le projet Github springbatch-sample montre comment Spring Boot permet de construire de manière simple un livrable auto exécutable.

La documentation de référence spring détaille les étapes pour un projet avec une seule datasource contenant un seul job et sans paramètres de lancement.

On va voir dans l’article les configurations Spring Boot nécessaires pour lancer un job d’alimentation de données dans une base, avec datasources technique et fonctionnelle séparées et comment traiter les paramètres d’entrée et propriétés système.

Configuration maven

Déclaration projet parent Spring Boot


<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>1.2.2.RELEASE</version>
</parent>

Ajout dépendance maven Spring Boot pour les batchs (plus besoin de déclarer les dépendances spring)


<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-batch</artifactId>
</dependency>

Ajout du plugin spring boot

 

<plugin>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

Configuration java du runner

L’écriture de la configuration des beans JobExplorer et JobLauncherCommandLineRunner n’est pas nécessaire dans le cas de projets mono job et mono datasource.

Test du runner à vide

La librairie system rule permet de gérer de manière simple et fiable les propriétés système et les codes de sortie.

Packager le livrable jar exécutable

maven clean package

Pré-requis d’environnement pour exécuter le job d’alimentation

  • Une datasource technique
  • Une datasource fonctionnelle
  • Un fichier de données csv
  • Un fichier de propriétés contenant les clés
    • ds.technical.driverclassname
    • ds.technical.url
    • ds.technical.username
    • ds.technical.password
    • ds.functional.driverclassname
    • ds.functional.url
    • ds.functional.username
    • ds.functional.password
    • commit.interval

Exécuter le jar

avec maven

mvn spring-boot:run -Drun.arguments=input.file.path=alimentation.csv

plus les propriétés de VM -Dbatch.properties.path=file:batch.properties -Djob.name=alimentationJob

ou bien directement le jar

java -Dbatch.properties.path=file:batch.properties -Djob.name=alimentationJob -jar springbatch-sample.jar input.file.path=alimentation.csv

Voir la documentation de référence spring pour les détails des jar exécutable Spring Boot.

BDD Spring Batch

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

On configure le runner Junit

Et on implémente les étapes

Dbsetup permet d’insérer directement les données des scénarios en base de données.

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

TDD Spring Batch

Ici TDD s’applique à un batch Spring Batch, un batch d’extraction est pris comme exemple pour appliquer la démarche.

Les sources complètes sont sur Github : springbatch-sample

Voici la spécification du batch d’extraction d’une base de données dans un fichier plat :

1 Job contient

  • 1 validateur de présence du paramètre chemin du fichier en sortie
  • 1 étape contient
    • 1 reader jdbc
    • 1 writer fichier plat

Le plan de test du batch est le suivant

  • TDD reader jdbc et ensuite
  • TDD writer fichier plat et ensuite
  • TDD job extraction

Le SUT se présente sous la forme de classes java de configuration spring (aucun xml n’a été utilisé…)

Des classes génériques de configuration et des commodités de test telles que des Rules Junit et des templates de reader et writer sont fournies par le module springbatch-support.

On déroule le plan de test, d’abord le TDD du reader jdbc

Quelques explications sur le test du reader jdbc

  • JobExtractionTestConfiguration crée des beans spring utilitaires pour les tests et importe la classe à tester JobExtractionConfiguration
  • La rule BatchProperties créée un fichier de properties temporaire pour configurer les datasources
    • une datasource pour la base de données technique Spring Batch
    • une datasource fonctionnelle pour lire les données à extraire
  • La rule DBunit insère les données de test dans une base mémoire
  • Le composant ItemReaderTemplate permet de récupérer tous les items de la base de données

On enchaîne sur le TDD du writer fichier plat

  • La méthode static getStepExecution est nécessaire pour injecter tardivement (late binding) le paramètre de chemin du fichier de sortie
  • La rule TemporaryFolder permet de créer un fichier de sortie temporaire
  • Le composant ItemWriterTemplate permet d’écrirer tous les items dans le fichier de sortie

Et on finit par le TDD du job extraction

Voilà, TDD permet de contrôler pas à pas et sans douleur la réalisation d’un batch Spring Batch.

Lorsque le dernier composant de test passe au vert alors le batch est conforme à la spécification, voir la solution complète sur Github : springbatch-sample.

Quant au code factorisé dans le module springbatch-support c’est la dernière étape de TDD (clean & refactor)  qui l’a fait émergé.