Tests End-to-End avec Docker Partie 5 : Docker Newman

Cet article fait suite à Tests End-to-End avec Docker parties 1 à 4.

Cette 5 ième partie montre comment utiliser l’application Postman et sa ligne de commande Newman pour exécuter des tests d’intégration de services REST.

Newman docker est utilisé afin d’étendre à l’exécution des tests d’api la technologie Docker par ailleurs déjà utilisée pour les phases précédentes du pipeline.

L’exemple complet est disponible sur github (branche e2e-docker-ansible).

Voir le README pour lancer le test E2E, l’exemple a été testé sous Mac OS avec Docker for Mac .


Le changement par rapport à la partie précédente de l’article est le remplacement du script bash pour tester les services avec curl par l’outil de test Postman

Il faut d’abord créer une collection de test Postman avec l’application, la collection de l’exemple a été créée avec la Mac App.

capture-decran-2016-12-12-a-22-58-35La collection contient un enchaînement de 4 requêtes http

  • recherche personne telle que nom=nom1 (elle existe déjà via l’alimentation batch)
  • post 1 personne telle que nom=nom3
  • post 1 autre personne telle que nom=nom3
  • recherche personnes telles que nom=nom3

A noter également le host de l’url résolu avec la variable {{restapp.host}}  du fichier d’environnement json de Postman.

Détail des assertions de test de l’url get de recherche de personnes

capture-decran-2016-12-12-a-23-12-36

  • vérification status response http 200
  • log et extraction de la réponse json
  • assertions sur les attributs de la réponse json

Pour voir l’exemple complet dans l’application Postman

  • importer la collection au format json
  • importer aussi l’environnement au format json et adapter la valeur du host

Voir tutoriaux du site Postman pour plus de détails.

Une fois la collection et l’environnement Postman créés, il suffit de les exécuter avec la ligne de commande Postman, autrement dit Newman

Le lanceur run.sh des tests E2E est légèrement remanié pour exécuter la collection à l’aide du container Docker newman. (voir ci-dessous #e2e : test postman)

capture-decran-2016-12-12-a-23-35-44

Le pipeline est donc définit comme ceci :

  • build image mysql avec ses schémas
  • package batch & build image docker
  • package restapp & build image docker
  • pre E2E deploy 
    • création structure
    • déploiement config
    • lancement containers
  • pre E2E prepare
    • insérer les données initiales via le batch
  • E2E tests
    • exécution collection postman via docker newman
  • post E2E = stop containers

 

Publicités

Tests End-to-End avec Docker Partie 4 : Docker & Ansible

Cet article fait suite à Tests End-to-End avec Docker parties 1 à 3.

Cette 4 ième partie montre comment utiliser Ansible pour orchestrer le démarrage des images Docker.

L’exemple complet est disponible sur github (branche e2e-docker-ansible).

Voir le README pour lancer le test E2E, l’exemple a été testé sous Mac OS avec Docker for Mac .


Explication de ce qui change avec Ansible sur la phase de préparation des tests e2e par rapport à l’approche bash

  • Un playbook ansible au format YAML spécifie le lancement des containers mysql et webapp
  • Pour exécuter le lancement il suffit d’utiliser la commande ansible-playbook avec le fichier deploy.yml ci-après (voir run complet de toutes les phases)
- name: run mysql and webapp docker containers
  hosts: 127.0.0.1
  connection: local

  tasks:

  - name: create log directory
    file: path={{ log }} state=absent
    file: path={{ log }} state=directory

  - name: create properties directory
    file: path={{ properties_dest }} state=directory

  - include: download.yml name=application

  - include: download.yml name=batch

  - name: run mysql
    docker:
      name: mysqldb
      image: mysql-sample
      ports: "3306:3306"
      env:
        MYSQL_ROOT_PASSWORD: dba
      state: started

  - include: wait.yml image=mysqldb

  - name: run webapp
    docker:
      name: webapp
      image: spring-boot-sample
      ports: "8080:8080"
      volumes:
      - "{{ log }}:/var/log"
      - "{{ properties_dest }}:/properties"
      state: started

  - include: wait.yml image=webapp
  • création de la structure nécessaire pour les logs et configurations
  • téléchargement des configurations
  • description lancement des containers avec module docker
  • attente des healthcheck ok des containers

download.yml : détail du téléchargement d’un type de fichier de configuration

- name: download {{ name }} properties
  get_url:
    url: "{{ properties_url }}/{{ name }}.properties"
    dest: "{{ properties_dest }}/{{ name }}.properties"
    force: yes

wait.yml : détail du traitement d’attente d’un container up & healthy

- name: wait {{ image }} up
  shell: docker inspect --format={{ '{{' }}.State.Health.Status{{ '}}' }} {{ image }}
  register: result
  until: result.stdout == "healthy"
  retries: 30
  delay: 5

Les autres phases ne sont pas remaniées par rapport à la partie 3

Concernant l’usage de docker compose, il est aussi possible de décrire et lancer les containers docker grâce à cet outil mais

  • on ne dispose pas de la même souplesse pour insérer les vérifications de healthchek par exemple (feature demandée mais non intégrée à ce jour)
  • la partie gestion des configurations des playbook est hors scope

Le pipeline est donc définit comme ceci :

  • build image mysql avec ses schémas
  • package batch & build image docker
  • package restapp & build image docker
  • pre e2e deploy 
    • création structure
    • déploiement config
    • lancement containers
  • pre e2e prepare
    • insérer les données initiales via le batch
  • e2e tests
  • post e2e = stop containers

Pour pratiquer ce pipeline dans un outil d’intégration continue on pourra s’appuyer sur l’usage de la docker registry afin de « pusher » les images docker après chaque phase de build et de « puller » les images en phase pré e2e.

Tests End-to-End avec Docker Partie 3 : Docker Maven Plugin

Cet article fait suite à Tests End-to-End avec Docker parties 1 & 2.

Cette 3 ième partie montre comment intégrer le build des images docker dans la phase package de l’outil de build Maven.

L’exemple complet est disponible sur github (branche e2e-docker-maven).

Voir le README pour lancer le test E2E, l’exemple a été testé sous Mac OS avec Docker for Mac .


Explication de ce qui change par rapport à l’approche de construction des images sans Maven

  •  Build avec maven package construit le packaging du batch et de la restapp, mais aussi les images docker
  • Ajout de la configuration du Docker maven plugin de fabric8 dans le pom.xml du batch
<plugin>
    <groupId>io.fabric8</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>0.15.16</version>
    <configuration>
        <verbose>true</verbose>
        <images>
            <image>
                <name>springbatch-sample:latest</name>
                <build>
                    <dockerFile>Dockerfile</dockerFile>
                    <assembly>
                        <descriptorRef>artifact</descriptorRef>
                    </assembly>
                </build>
            </image>
        </images>
    </configuration>
    <executions>
        <execution>
            <id>build</id>
            <phase>package</phase>
            <goals>
                <goal>build</goal>
            </goals>
        </execution>
    </executions>
</plugin>
  • Utilisation du Dockerfile dans répertoire par défaut src/main/docker
  • L’assembly prédéfinit artifact récupère le package jar spring boot afin de le copier dans l’image
    • dans Dockerfile on a « COPY maven/springbatch-sample*.jar /springbatch-sample.jar »
  • Exécution du goal build est liée à la phase package de maven

Dans le pom.xml de la restapp, le principe est le même que pour le batch

<plugin>
    <groupId>io.fabric8</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>0.15.16</version>
    <configuration>
        <verbose>true</verbose>
        <images>
            <image>
                <name>spring-boot-sample:latest</name>
                <build>
                    <dockerFile>Dockerfile</dockerFile>
                    <assembly>
                        <descriptorRef>artifact</descriptorRef>
                    </assembly>
                </build>
            </image>
        </images>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>build</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Script résultant de build des images

mysql=$1
batch=$2
restapp=$3

cd $mysql && docker build -t mysql-sample:latest .
cd $batch && mvn clean package
cd $restapp && mvn clean package

Les autres phases ne sont pas remaniées par rapport à la partie 2

Le docker maven plugin de fabric8 permettrait aussi de lancer les containers dans la phase verify d’un build maven mais dans le cas présent cela ne rentre pas dans le pipeline qui est définit par la séquence suivante

  • build image mysql avec ses schémas
  • package batch & build image docker
  • package restapp & build image docker
  • pre e2e = run containers docker 
    • cette étape de préparation est responsable d’orchestrer les containers
    • renseigner l’environnement à utiliser
    • insérer les données initiales via le batch
  • e2e tests
  • post e2e = stop containers

Tests End-to-End avec Docker Partie 2

Cet article fait suite à Tests End-to-End avec Docker.

Cette 2 ième partie montre comment construire ses propres images avec les Dockerfile  au lieu de provisionner des images officielles par commandes bash.

L’exemple complet est disponible sur github (branche e2e-dockerfile).

Voir le README pour lancer le test E2E, l’exemple a été testé sous Mac OS avec Docker for Mac (l’installation de docker machine n’est plus nécessaire).


Explication de ce qui change par rapport à l’approche sans Dockerfile phase par phase (build, pré-intégration, test, post-intégration)

Build

  •  Build maven et
  • Build images docker des repos mysql, batch et restapp, il faut lancer la commande docker build à la racine de chaque repo.
  • La commande build construit les images en exécutant le Dockerfile présent dans le contexte relatif au repo.
    • docker build -t mysql-sample:latest .
    • docker build -t springbatch-sample:latest .
    • docker build -t spring-boot-sample:latest .

Mysql Dockerfile (voir repo mysql-sample)

FROM mysql:latest

COPY functional-schema.sql /docker-entrypoint-initdb.d/
COPY technical-schema.sql /docker-entrypoint-initdb.d/
COPY healthcheck.sh /healthcheck.sh

EXPOSE 3306

HEALTHCHECK --interval=3s --retries=10 CMD /healthcheck.sh
  • Copie des schémas base de données technique Spring batch et fonctionnelle dans l’image docker à l’emplacement /docker-entrypoint-initdb.d
  • docker-entrypoint-initdb.d est un répertoire standard fourni par l’image officielle mysql (FROM mysql)
  • Les scripts copiés dans /docker-entrypoint-initdb.d sont exécutés au démarrage du container (docker run).
  • Copie du script healthcheck dans l’image
  • Exposition du port standard mysql aux autres containers
  • Déclaration du script healthchek à exécuter au démarrage du container
    • S’exécutera 10 fois maximum à intervalles de 3 secondes jusqu’au succès

Healthcheck mysql

set -e

echo "SELECT 1 FROM USER;" | mysql --user="root" --password="$MYSQL_ROOT_PASSWORD" functional > /dev/null
echo "SELECT 1 FROM BATCH_JOB_SEQ;" | mysql --user="root" --password="$MYSQL_ROOT_PASSWORD" technical > /dev/null

exit $?
  • Sortie du script avec code 0 si les requêtes de test des schémas sont exécutées avec succès.

Batch Dockerfile (voir repo springbatch-sample)

FROM java:8

COPY springbatch-sample/target/springbatch-sample*.jar /springbatch-sample.jar

ENTRYPOINT ["java","-Dbatch.properties.path=file:/properties/batch.properties",
"-Djob.name=alimentationJob","-jar",
"/springbatch-sample.jar","input.file.path=/input/alimentation.csv"]
  • Copie du package au format jar dans l’image docker
  • ENTRYPOINT décrit la commande à exécuter au démarrage du container

Restapp Dockerfile (voir repo spring-boot-sample)

FROM java:8

COPY target/spring-boot-sample*.jar /spring-boot-sample.jar
COPY healthcheck.sh /healthcheck.sh

EXPOSE 8080

HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD /healthcheck.sh

ENTRYPOINT bash -c "java -jar /spring-boot-sample.jar --spring.config.location=file:/properties/application.properties 2>&1 | tee /var/log/restapp.log"
  • Copie du package au format jar dans l’image docker
  • Copie du healthcheck dans l’image
  • Exposition du port 8080 aux autres containers
  • Déclaration du healthchek à exécuter au démarrage du container
    • curl sur le port 8080
  • ENTRYPOINT décrit la commande à exécuter au démarrage du container

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

  • Lancement container docker mysql-sample
  • Attente du succès du healthcheck mysql
  • Lancement container batch-sample
  • Lancement container spring-boot-sample
  • Attente du succès du healthcheck de la restapp

Script docker-run.sh

work=$1
e2e=$2

cd $work && rm -rf log && mkdir log

docker run --name mysqldb -d -e MYSQL_ROOT_PASSWORD=dba -p 3306:3306 mysql-sample

$e2e/pre-integration/wait/wait.sh mysqldb

docker run --rm  -v $work/configuration/properties:/properties -v $e2e/pre-integration/input:/input springbatch-sample > $work/log/batch.log	

docker run --name webapp -d -v $work/log/:/var/log -v $work/configuration/properties:/properties -p 8080:8080 spring-boot-sample

$e2e/pre-integration/wait/wait.sh webapp

Script wait.sh

while [ $(docker inspect --format='{{.State.Health.Status}}' $1) != "healthy" ];
 do sleep 1; echo -n ".";
done
  • Boucle d’attente sur la commande docker inspect
  • State.Health.Status correspond au path json à filtrer du résultat de la commande inspect
  • State.Health.Status est égal à « healthy » quand le résultat du script déclaré dans HEALTHCHECK est en succès

Test des ressources REST avec CURL

Post-intégration

  • Arrêt et suppression des containers docker
  • Suppressions des images
docker rmi springbatch-sample
docker stop webapp && docker rm webapp && docker rmi spring-boot-sample
docker stop mysqldb && docker rm mysqldb && docker rmi mysql-sample
  • Chaque rebuild d’image avec le même tag latest et la copie d’un jar « snapshot » différent va générer la présence d’une nouvelle image dans l’emplacement local des images (docker images -a)
    • l’exécution de la commande docker rmi s’impose donc sous peine de voir l’espace disque local rapidement saturé !
  • Docker for Mac stocke les images docker dans le fichier ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2
  • Voici un lien utile sur l’utilisation de l’outil qmenu-img afin de récupérer de l’espace disque après suppression des images (docker rmi ne suffit pas pour récupérer l’espace !)

Conclusion

  • Nous avons vu un moyen d’exploiter les commandes standard de docker au lieu d’écrire des scripts bash
  • La notion d’image exécutable vs package jar à déployer
  • Cette approche peut être complétée par l’utilisation d’un plugin maven pour docker

 

Tests unitaires View Backbone.js avec Mocha, Chai et Sinon.js

Dans un précédent article nous avons vu comment écrire des tests unitaires JS Model & Collection sur la todo liste backbone.

On va poursuivre ici par les tests des composants Backbone de type View.

Mocha est le framework de test utilisé, complété par Chai pour les assertions et Sinon.JS pour les doublures (spies, stub, mock).

L’environnement d’exécution nécessite seulement Node.js, pas besoin de browser pour lancer les tests unitaires, nous allons voir comment exécuter des assertions sur le dom dans Node.js.

L’exemple complet est disponible sur Github sur la branche webdriverio-it-tests (voir le README pour exécuter les tests unitaires).

Configuration

La configuration grunt (voir article TU model) est responsable d’exécuter le helper global main.js

Mise en place de Chai et Sinon dans le helper main.js

 var chai = require('chai');
 var sinon = require('sinon');
 var sinonChai = require('sinon-chai');
 chai.use(sinonChai);

 global.sinon = sinon;
 global.expect = chai.expect;
...

Mise en place de jsdom dans le helper main.js

 var jsdom = require('jsdom');
 var fs = require('fs');
 var markup = fs.readFileSync('app/index.html');

 global.window = jsdom.jsdom(markup).defaultView;

jsdom est une implémentation javascript du dom qui permet de se passer d’un browser.

Tests unitaires du composant Todo View

  • Les appels imbriqués de describe permettent d’organiser les tests et améliorer la lisibilité du reporting
  • Le découpage des tests est fait selon les listeners puis les events
  • Les assertions se font sur le dom de l’élément de la view
  • Les stubs Sinon.JS sont utilisés pour substituer une implémentation vide aux méthodes du model afin de ne pas déclencher d’appels à l’api REST
  • Les spy Sinon.JS sont utilisés pour vérifier l’appel des méthodes du model
  • Les tests paramétrés permettent de couvrir tous les cas de la méthode isHidden en évitant la duplication

 

Tests unitaires du composant App View

  • Une seule instance de AppView pour tous les tests
  • La collection Todos est ré initialisée à chaque test
  • Les stubs Sinon.JS sont utilisés pour substituer une implémentation du model qui met seulement à jour le model sans déclencher d’appels à l’api REST
  • Les spy Sinon.JS sont utilisés pour vérifier l’appel des méthodes de la collection avec les paramètres attendus

 

Les TU Views + TU Model & Collection permettent donc de couvrir toutes les lignes / branches du code de l’application todo liste en fournissant un effort minimum pour chaque méthode de chaque composant.

L’ajout de tests Web d’intégration en plus des TU permet de construire une stratégie de test complète et une répartition de l’effort de test équilibrée.

 

 

Tests unitaires Model & Collection Backbone.js avec Mocha et Chai

Cet article montre comment écrire des tests unitaires JS en prenant comme application exemple la todo liste backbone codée avec le framework Backbone.js et le chargeur de modules RequireJS.

On se focalise ici sur les tests des composants Backbone de type Model et Collection.

Mocha est le framework de test que l’on utilise, complété par Chai pour les assertions.

L’environnement d’exécution nécessite seulement Node.js, il n’y a donc pas besoin de browser pour lancer les tests unitaires.

L’exemple complet est disponible sur Github sur la branche webdriverio-it-tests (voir le README pour exécuter les tests unitaires).

Configuration

La configuration des tests est faite par grunt et la task grunt-mocha-test pour exécuter les tests mocha dans Node.js, lance notamment le helper global main.js.

Extrait du Gruntfile.js

...
  mochaTest: {
    test: {
        options: {
            reporter: 'spec',
            timeout: 1000,
            require: 'test/spec/helpers/main.js'
        },
        src: ['test/spec/*.js']
    }
  }
...

Mise en place de Chai et des dépendances de modules dans le helper main.js

 var chai = require('chai');
 global.expect = chai.expect;
...

Tests unitaires du composant Model Todo

  • Les méthodes Mocha describe et it décrivent le comportement attendu du composant Todo comme s’il s’agissait d’une spécification
  • La méthode beforeEach de Mocha fournit une nouvelle instance de Todo à chaque test
  • Les assertions sont exécutées avec Chai dans le style expect

Tests unitaires du composant Collection Todos

  • Une seule instance de la collection est ré initialisée à chaque test par la méthode beforeEach
  • Chaque méthode de test contient des sauts de ligne pour séparer
    • le setup particulier au test
    • l’action testée
    •  et les assertions

 

Et voila, le modèle de l’application est couvert par les tests indépendamment du code des Backbone Views.

Nous allons justement voir dans cet article comment implémenter les tests unitaires des Views.

 

 

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

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

WebdriverIO est aussi présenté en tant qu’alternative à WebDriverJs.

On obtient donc la stack de test :

  • WebDriverJs + Cucumber-js sur Node.js + protocole WebDriver pour communiquer avec le browser
  • ou bien WebdriverIO + Cucumber-js sur Node.js + protocole WebDriver

L’exemple complet en Cucumber-js + WebDriverJs est disponible sur  Github sur la branche seleniumjs-it-tests (voir README pour l’exécution des tests).

Composant Page Object avec WebDriverJs

[gist https://gist.github.com/95746c4f8858ceb8e49c /]
  • Contient tous les appels à l’API WebDriverJs
  • 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
  • les appels à WebDriverJs retournent des Promises

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

[gist https://gist.github.com/46c4fd89d52fe9d9241c /]
  •  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’extension Chai as Promised permet de vérifier les Promises de WebDriverJs
  • Q.all permet d’attendre la résolution de plusieurs Promises

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

[gist https://gist.github.com/7c9f9a43d1b23192763e /]
  •  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 et l’extension Chai as Promised pour traiter les Promises issues de WebDriverJs

Limitations de WebDriverJs

  • Beaucoup plus lent que CasperJS (sachant Zombie.js est encore plus rapide que CasperJS).

Cas d’usage possible

  • Tests End to End multi-browser (y compris en mode headless avec PhantomJS)
  • API très riche pour reproduire ce que fait l’utilisateur final
  • Attention à l’abus de selenium ! Voir à ce sujet l’article sur la pyramide des tests.

 

Alternative WebdriverIO pour l’implémentation du Page Object

L’exemple complet en Cucumber-js + WebdriverIO est disponible sur Github sur la branche webdriverio-it-tests (voir README pour l’exécution des tests).

Composant Page Object avec WebdriverIO

[gist https://gist.github.com/2e5a6276c7c26cc3c5ec /]

Par rapport à WebDriverJs l’avantage est que l’API est plus concise et lisible.

En conclusion

Un dernier mot sur l’activité des frameworks de tests d’intégration et fonctionnels :

  • CasperJS n’apporte plus de nouveautés depuis un certain temps, mais la richesse de son API et sa documentation pourraient encourager d’autres contributeurs ! A suivre…
  • Concernant Selenium on est en présence d’un écosystème très vivant avec de multiples portages et notamment plusieurs implémentations en JS.
  • Cucumber est également très vivant et populaire et bénéficie de portage en plusieurs langages.