Karate, le testing e2e automatisé human-readable

  • Posted by: Mathieu Durand

Quand on revient de conférences, il arrive souvent qu’on ait une liste de choses à voir longue comme le bras. Et de mon côté, après avoir vu la conférence “Architecture Hexagonale Level 2 : Comment bien écrire ses tests.” au Breizhcamp 2019 de Julien Topçu et Jordan Nourry et bien j’avais noté dans un coin de ma tête Karate.

Karate c’est quoi ?

Karate est un framework Open-source qui affiche comme promesse : “API test-automation, mocks and performance-testing into a single, unified framework”. Dans un premier temps, on va s'intéresser uniquement à ce qu’il offre pour les tests d’API automatisés.

Étant un développeur respectueux de la pyramide de tests, je teste mes API en mode end-2-end. Et là où Karate diffère des classiques du genre (http://rest-assured.io/) c’est dans la façon d’écrire ces tests.
En effet, quand on utilise ce framework, on ne code pas (ou peu) mais on écrit des fichiers en simili-Gherkin (https://cucumber.io/docs/gherkin/) ce qui nous donne des tests human-readable (pour peu que l'on soit à l'aise avec la langue de shakespeare).

Mise en place et tests de notre API

Maven

Pour utiliser Karate dans notre projet il faut ajouter deux librairies :

<dependency>
    <groupId>com.intuit.karate</groupId>
    <artifactId>karate-apache</artifactId>
    <version>0.9.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.intuit.karate</groupId>
    <artifactId>karate-junit4</artifactId>
    <version>0.9.3</version>
    <scope>test</scope>
</dependency>

A noter que Karate offre peut aussi être utilisé avec l’implémentation du client HTTP Jersey (karate-jersey) et avec junit5 (karate-junit5).

Configuration

Afin de ne pas se répéter et de disposer de flexibilité quant à l’URL de l’API que nous allons utiliser, Karate permet l’ajout d’un fichier de configuration à ajouter dans le classpath de tests se nommant karate-config.js.

Voici son contenu :

function fn() {
   var env = karate.env; // get java system property 'karate.env'
   karate.log('karate.env system property was:', env);
   if (!env) {
       env = 'dev'; // a custom 'intelligent' default
   }
   var config = { // base config JSON
       baseUrl: 'http://localhost:8445/api'
   };
   if (env === 'stage') {
       // override only those that need to be
       config.baseUrl = 'https://stage-host/api';
   } else if (env === 'e2e') {
       config.baseUrl = 'https://e2e-host/api';
   }
   // don't waste time waiting for a connection or if servers don't respond within 5 seconds
   karate.configure('connectTimeout', 5000);
   karate.configure('readTimeout', 5000);
   return config;
}

Elle nous permet de définir selon les profils des adresses différentes pour notre API ainsi que la gestion des timeouts.
Pour plus d’infos c’est par ici : https://github.com/intuit/karate#configuration

Tests

Maintenant, place à l’écriture des tests e2e. Pour ce faire, un classique Runner Junit est proposé par Karate, il suffit donc de créer une classe (en Kotlin ici) comme suit :

import com.intuit.karate.junit4.Karate
import org.junit.runner.RunWith

@RunWith(Karate::class)
class OctopusApiIT

Imaginons que notre API REST Octopus 🐙 propose ces opérations :

Ecrivons donc un simple test Karate nous permettant de parcourir toutes ces API.
Par défaut, lors du lancement de notre test OctopusApiIT, Karate ira chercher tous les fichiers *.feature au même niveau. Ok, c'est parti !

Voilà le contenu de notre fichier octopus-api.feature

Feature: Octopus management API

 Background:
   * url baseUrl

 Scenario: Octopus creation and deletion

   # Octopus creation
   Given path 'octopus'
   And request { color : "blue", traits : ["cool"]}
   When method post
   Then status 201
   And match response == { id: '#uuid', color : "blue", traits : ["cool"] }
   And def createdOctopusId = response.id

   # Octopus retrieval
   Given path 'octopus', createdOctopusId
   When method get
   Then status 200
   And match response == { id: '#(createdOctopusId)' }

   # Octopus retrieval on global path
   Given path 'octopus'
   When method get
   Then status 200
   And match response[*].id contains == createdAuthRequestId

   # Octopus deletion
   Given path 'octopus', createdOctopusId
   When method delete
   Then status 204

   # Check Octopus has been deleted
   Given path 'octopus', createdOctopusId
   When method get
   Then status 404

En moins de 40 lignes de textes, nous allons donc effectuer 5 requêtes sur notre API avec des contrôles :

  • sur les codes HTTP (exemple : Then status 204)
  • sur le contenu du body JSON de retour And match response == { id: '#uuid', color : "blue", traits : ["cool"] }
  • sur ce point Karate dispose d’une syntaxe d’assertions en mode pattern matching plutôt simple et efficace. Voir https://github.com/intuit/karate#match

Désormais nous n’avons plus qu’à lancer le test OctopusApiIT et profiter !

Aller plus loin dans nos Katas

Bon jusque là, ça à l’air sympa et rapide mais vous allez me dire “Comment je fais pour gérer mon IAM avec tout ça ?”.
Et bien heureusement Karate nous permet de faire du reuse de feature. Ainsi, on peut ajouter avant chaque test (section Background), un appel à notre feature gérant l’authentification.

Feature: which makes a 'call' to another re-usable feature

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

Scenario: some scenario
  # main test steps

Et la feature se chargeant de l’authentification

Feature: here are the contents of 'my-signin.feature'

Scenario:
  Given url loginUrlBase
  And request { userId: '#(username)', userPass: '#(password)' }
  When method post
  Then status 200
  And def authToken = response

Parfait pour l’intégration dans nos projets Keycloak 🙂

Pour conclure

Si ce rapide billet sur Karate ne vous a pas convaincu, nous oui !

La grandissante popularité du framework est plutôt méritée, l’outil semble déjà plutôt mature et fournit déjà un éventail de fonctionnalités conséquent. Il apparait d’ailleurs désormais sur le fameux radar Toughtworks en Assess https://www.thoughtworks.com/radar/languages-and-frameworks.

Bref, cela vaut le coup d’oeil !

Author: Mathieu Durand

Laisser un commentaire

2 commentaires

  • Olivier PRESENTINI

    Hello,
    Présentation de qualité, simple et efficace 😉
    est-ce que tu as aussi un rex sur le coût d’entrée, coût d’intégration par hasard ?

    • Le coût initial est vraiment très faible. Le courbe d’apprentissage est faible et la mise en place prends quelques minutes. Pour moi c’est vraiment un quick win pour déjà faire des enchaînements de cas droits sans efforts