Contenu

Mise en place de tests fonctionnels avec karate

Introduction

Présentation

Les tests fonctionnels sont une étape particulièrement importante dans le développement d’une application, permettant de vérifier son bon fonctionnement général et de s’assurer également qu’aucune régression n’a lieu. Certains de ces tests peuvent être automatisés grâce à l’outil Karate et même être intégrés en tant qu’étape d’un pipeline Gitlab-CI. Nous allons présenter ici la démarche à adopter.

Karate se base sur Cucumber et le langage Gherkin, qui sont des outils permettant de créer des tests fonctionnels sans être pour autant développeur ou savoir coder. Le test (ou scénario) peut être simple (simple appel API) comme beaucoup plus complexe (création, interrogation, modification, suppression d’une entité etc.). Karate peut également réaliser des tests automatisés au niveau des IHMs.

Pré-requis

Karate nécessite d’avoir dans son environnement de travail Java (> Java 8, installer un JDK ainsi qu’un JRE) ainsi que Maven. Une fois Java installé on crée la variable d’environnement système JAVA_HOME avec une valeur de type C:\Program Files\OpenJDK\jdk-11.0.8.10-hotspot\. On installe ensuite Maven et on ajoute au path de la machine une variable d’environnement de type D:\wwwroot\apache-maven-3.8.5\bin. Ceci permettra d’avoir accès à la commande mvn depuis tout emplacement sur le PC.

Mise en place du projet de test

Initialisation du projet

Pour initialiser le projet de test on se place dans le dossier dans lequel on veut placer les tests et on ouvre une invite de commande. La commande suivante va générer le projet dans le dossier ciblé :

1
2
3
4
5
6
7
mvn archetype:generate
    -DarchetypeGroupId=com.intuit.karate
    -DarchetypeArtifactId=karate-archetype
    -DarchetypeVersion=1.1.0
    -DgroupId=com.testingdocs.karate.demo
    -DartifactId=TestingDocsKarateDemo
    -Dpackage=com.testingdocs.karate.automation

Dans cette commande on modifie les valeurs DgroupId, DartifactId (nom du projet) et Dpackage. La validation de la commande entraîne la génération du projet de tests. Lorsqu’on ouvre le projet, sa structure est la suivante :

Image1tfk.png

Dans un terminal (ouvert à la racine du projet) dans Visual Studio Code on peut jouer la commande mvn clean test. La commande mvn appelle l’exécutable de Maven, clean supprime la totalité du code Java compilé précédemment sur le projet, test lance l’exécution des tests. Les tests du projet de démonstration créé vont alors être lancés et la synthèse des résultats des tests va apparaître dans le terminal. On aura une ligne dans le terminal indiquant le nombre de tests ayant fonctionné et le nombre de tests ayant échoué.

Au moment de l’exécution de la commande vue plus haut un dossier target va être généré dans le projet. Il contiendra un fichier summary.html (le rapport de test) ainsi que l’ensemble des éléments permettant d’ajouter les éléments de style au rapport généré (Bootstrap, JQuery etc.). Ce rapport peut être consulté via un navigateur web et se présente ainsi :

Image2tfk.png

On peut voir ici sur le côté gauche de la capture d’écran le nombre de réussites et le nombre d’échecs sur les tests. Les indications entre crochets sont le numéro du test dans le fichier de feature suivi de la ligne à laquelle débute le test dans le fichier. Si une étape de test échoue la ligne concernée apparaît en rouge et en cliquant sur cette étape on accède aux erreurs provoquées par cette étape. Le lien Summary en haut à gauche du rapport permet de se rendre au sommaire du rapport, dans lequel on verra les tests regroupés par feature.

Création des tests

Les tests sont rédigés en utilisant la syntaxe Gherkin. Karate est basé sur les normes de Cucumber. Cette syntaxe permet à des non-codeurs de créer des scénarios de tests de manière simple. Karate a incorporé la syntaxe Gherkin en ajoutant également ses propres mots-clés comme url, path etc. La figure ci-dessous compare le déroulement d’un test en français à son déroulement selon la syntaxe de Gherkin :

Français Syntaxe de Gherkin
Etant donné l’URL http://… Given URL http://…
Et le paramètre xxx = yyy And param xxx = yyy
Avec la méthode GET When method get
Alors le statut renvoyé est 200 Then status 200
Et l’id contenu dans la réponse = xxxx And response.id = xxxx

Cette figure ne présente pas l’ensemble des mots-clés utilisés par Karate, se rendre à l’adresse web du repository Git (https://github.com/karatelabs/karate) pour une liste exhaustive des fonctionnalités de l’outil. Le test présenté ici est un test simple, on cherche simplement à joindre une URL avec un paramètre, puis on vérifie que la requête est passée et que la réponse contient le bon id.

On peut réaliser tout un ensemble d’étapes et faire des vérifications à chaque étape du processus (POST, GET sur un ID, PATCH, GET, DELETE). Que le test soit simple ou complexe on parle de scénario. Il sera défini par une ligne de type scenario : récupération d’une application avec son id en GET qui servira d’intitulé au test dans le rapport de test généré à l’issue de l’exécution des tests.

Détails techniques

Fichiers .feature

Les tests sont écrits dans des fichiers .feature, qui vont avoir la structure suivante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Background:
  * configure headers = read('classpath:my-headers.js')
  * def signIn = call read('classpath:my-signin.feature') { username: 'john', password: 'secret' }
  * def authToken = signIn.authToken

Feature: Functional tests on all "GET" methods in ObjetsController
Scenario: Get API status
  Given url urlAPIBaseV1 + "/status"
  When method get
  Then status 200

Scenario: Get component by id (v1)
  Given url urlObjetsBaseV1 + "/4378"
  When method get
  Then status 200
  And match response.name == <name>

  Examples:
  | name |
  | Jean |
  | Paul |
  | Jules |
  
Scenario: Get component by name (v1)
  Given url urlObjetsBaseV1
  And param name = <name>
  When method get
  Then status 200
  And response.id = 4378

Les informations notées en regard des mots-clés Feature et Scenario permettront à Karate de présenter clairement les tests, en affichant en haut du rapport sur quelle feature on se trouve et quels tests on aura joué. Chaque ligne de chaque test apparaîtra sur fond vert si la ligne s’est exécutée sans erreur, en rouge si la ligne a échoué à l’exécution, en orange si elle se situe après une ligne en échec.

La section Background du fichier contient des étapes qui seront effectuées avant chaque scénario de test du fichier .feature. Les variables définies ici seront des variables globales pour tous les scénarii et seront réinitialisées avant l’exécution de chaque test.

La section Examples d’un test permet de définir la valeur d’une ou de plusieurs variables afin de jouer un même test pour un ensemble de valeurs différentes. La variable utilisée dans le cas de test est alors indiquée par son nom entre chevrons, dans le cas présent <name>.

Fichier karate-config.js

Lorsqu’on met en place des tests fonctionnels sur des APIs on rencontre rapidement une problématique : l’utilisation de racines d’URLs communes à un grand nombre de tests qui peuvent être dispersés dans plusieurs fichiers .feature.

C’est ici que l’on aura recours au fichier karate-config.js dans lequel on pourra définir des variables utilisées ensuite au sein des tests. La structure du fichier est la suivante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function fn() {
  var env = karate.env || 'int';
  var urlAPIV1 = "https://monapplication-" + env + ".com.monapplication/wsmonapplicationapi/api/v1";
  var urlAPIV2 = "https://monapplication-" + env + ".com.monapplication/wsmonapplicationapi/api/v1";

  var config = {
      urlAPIBaseV1: urlAPIV1,
      urlAPIBaseV2: urlAPIV2,
      urlComponentsBaseV1: urlAPIV1 + "/objets",
      urlComponentsBaseV2: urlAPIV2 + "/objets",
      urlComponentRelationsBase: urlAPIV1 + "/objetsrelations",
      urlTechnologiesBase: urlAPIV1 + "/technologies"
  };

  karate.configure('ssl', true);
  karate.configure('connectTimeout', 5000);

  if (karate.properties["karate.proxy"] != null) {
      karate.configure('proxy', karate.properties["karate.proxy"]);
  }

  return config;
}

Le fichier karate-config.js définit des variables globales ainsi que des variables construites à partir des variables globales, qui serviront par exemple à définir la base d’une API en fonction de sa version puis à déterminer les URLs de base des différents contrôleurs du projet. On notera ici que les variables globales ne peuvent pas être utilisées directement mais que l’usage d’une variable intermédiaire doit être utilisée afin de déterminer les URLs utilisées ensuite. L’utilisation de urlAPIV1 ou urlAPIV2 dans la construction de urlComponentsBaseV1 génère des erreurs avant même la réalisation des tests lorsqu’on joue la commande mvn clean test.

Fichier TestRunner

Le fichier Java TestRunner est quant à lui le chef d’orchestre de l’exécution des tests présents dans le projet Karate, c’est lui qui va appeler l’ensemble des cas de tests.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import com.intuit.karate.Results;
import com.intuit.karate.Runner;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

class TestRunner {
    @Test
    void testParallel() {
        Results resultsComponents = Runner.path("classpath:.")
                .parallel(5);
        assertEquals(0, resultsComponents.getFailCount(), resultsComponents.getErrorMessages());
    }
}

Il n’y a qu’un seul fichier Runner au sein du projet. On voit ici qu’il est configuré pour faire tourner l’ensemble des tests grâce au paramètre classpath :.. On pourrait limiter le lot de tests à ceux concernant uniquement le ComponentsController en modifiant ce paramètre et en indiquant un classpath :objet. Le chiffre placé dans la parenthèse de .parallel() indique le nombre de tests à mener en parallèle.

Il est à noter que dans le cadre de mon utilisation de Karate pour des tests d’APIs l’utilisation de tests en parallèle provoquait des erreurs au niveau des APIs, de nombreux appels attendant un retour 200 réceptionnant un retour 500 (erreur serveur). J’ai donc modifié le paramètre initialisé lors de la génération du projet à 5 à une valeur de 1, revenant à ne pas paralléliser les tests. Le nombre de tests étant relativement faible ceci ne pose pas de problèmes de performances.

Intégration des tests au sein d’un pipeline Gitlab-CI

Le passage des tests fonctionnels est un bon moyen de vérifier que l’intégrité du projet n’est pas mise en péril par la réalisation d’un développement. On peut passer ces tests de manière manuelle mais cela ajoute une complexité supplémentaire au processus de développement. Ce qui peut être réalisé manuellement peut (souvent) être automatisé et cette automatisation passe par l’intégration d’une étape de tests fonctionnels au sein d’un pipeline Gitlab-CI, après le déploiement du projet sur un environnement par exemple. Dans le cas de figure qui nous intéresse, le projet de test est intégré au pipeline du projet Deploy attaché à Mon Application. Le pipeline Mon Application est constitué des stages suivantes :

Image3tfk.png

L’étape de déploiement est manuelle. En effet l’environnement visé n’est pas toujours le même, et on ne déploie généralement pas simultanément sur les deux environnements en question ici, intégration et qualification. On peut noter ici que l’environnement visé pourrait être spécifié dans le fichier gitlab-ci.yml du pipeline Mon Application.

Ajout de Karate Test au projet Deploy

Ici, le pipeline du projet Deploy a été modifié, il comporte maintenant une étape karate-test décrite comme suit dans le gitlab-ci.yml :

1
2
3
4
5
6
karate-test:
  stage: karate-test
  extends: .env-test-limitation
  trigger: tools/monapplication/karatetest
  variables: 
    ENV_NAME: $ENV

Cette étape étend .env-test-limitation, qui définit les environnements dans lesquels le projet de test doit être lancé :

1
2
3
4
5
.env-test-limitation:
  only:
    variables:
      - $ENV == "int"
      - $ENV == "qa"

Cet extrait de code indique que le projet de test ne doit être exécuté que lorsque la variable $ENV est égale à int ou qa. Dans le cas où $ENV a la bonne valeur, le stage déclenchera un pipeline sur le projet monequipe/monapplication/karatetest. La variable ENV_NAME à laquelle on assigne la valeur de $ENV sera passée au pipeline du projet Karate Test.

Karate Test

Le fichier gitlab-ci.yml du projet KarateTest est constitué d’une seule étape d’exécution décomposée de la manière suivante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
stages:
    - exec

image: docker.monapplication/maven:3.6-jdk-8-alpine

variables:
    ENV_NAME: qa"

exec:
    stage: exec
    before_script:
        - apk add git

On définit l’image Docker sur laquelle on va travailler et on fixe la valeur de ENV_NAME Avant d’exécuter le script au cœur du pipeline et de faire tourner les tests on installe Git sur l’image Docker utilisée. On voit ici qu’on peut fixer la variable ENV_NAME avec une valeur en « dur » qa Par la suite on utilisera la valeur $ENV afin de récupérer l’environnement visé par les tests grâce au projet appelant, le Deploy.

On exécute le projet Karate Test en lui passant la variable ENV_NAME, qui permettra de determiner l’environnement sur lequel on travaille et quelles URLs on va interroger.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
after_script:
  - echo "Publication des rapports Karate sur les pages GitLab du dépôt https://gitlab.monapplication/monapplication/monapplication.Doc.git"
  - git clone https://oauth2:${GITLAB_ACCESS_TOKEN}@gitlab.monapplication/monapplication.Doc.git reports_repository
  - git config --global user.email "monadresse@mail.fr"
  - git config --global user.name "MonUserName"
  - cd reports_repository
  - REPORTS_PATH="Testing/Karate"
  - git checkout mise-en_place_tests_automatises_apis
  - cp -r $CI_PROJECT_DIR/target/karate-reports/* $REPORTS_PATH/
  - echo $REPORTS_PATH
  - echo $CI_PROJECT_DIR
  - git add .
  - git commit -m "Report| Last execution of Karate tests"
  - for i in $(seq 1 10); do git push && break || git pull --rebase; done
  - echo "Karate Tests completed"

  - if [ -z "$(git status --porcelain)" ]; then
      echo The report has not changed since last commit
  - else
      git add .
  - git commit -m "Report| Update Report Karate Tests"
  - for i in $(seq 1 10); do git push && break || git pull --rebase; done
  - fi

Ici on a la partie du script qui permet de sauvegarder les résultats des tests fonctionnels réalisés sur le pipeline au sein du projet monapplicationDoc. En résumé, on réalise les étapes suivantes :

  • créer un répertoire dans lequel on clone le projet monapplicationDoc
  • configurer les identifiants de l’utilisateur git
  • se placer dans le dossier dans lequel les rapports de tests doivent être stockés
  • copier l’ensemble du dossier target généré par Karate
  • effectuer les opérations de git commit / git push nécessaires afin de stocker les rapports dans le repository monapplicationDoc.

Conclusion

Les tests fonctionnels ont été réalisés avec l’outil Karate. Ils nous permettent de vérifier l’intégrité du projet lorsqu’on effectue des modifications dessus. De même que pour des tests unitaires, certaines évolutions nécessiteront probablement de modifier ces tests.

On a vu qu’il est possible d’intégrer ce projet en tant que stage au sein du processus du pipeline Gitlab-CI et de gérer l’envoi de variables permettant de cibler des environnements différents sans pour autant devoir modifier le projet en profondeur. On a vu qu’il est possible d’historiser les rapports de test en faisant appel à un autre projet cloné grâce à Git auquel on ajoute l’ensemble des fichiers du rapport de test.

Pour ce qui concerne le projet monapplicationDoc cette US est une première ébauche, le but étant la systématisation de ces tests et l’ajout de tests des IHM de l’application.

Liens intéressants