Automatiser ses tests de web services grâce à soapUI

le 15/11/2010 par Sofian Djamaa
Tags: Software Engineering

Pour tester des web services (REST/SOAP), je me suis demandé si je devais développer mon framework : des tests de contrats (tests des requêtes XML via un framework de test unitaire) et des  tests d’intégration (via Fitnesse/GreenPepper). Pas forcément compliqué à mettre en place, mais rébarbatif et pouvant être sujet à erreurs (donc, d’éventuelles contraintes supplémentaires de maintenance).

Mes besoins : trouver un outil rapide à prendre en main et gratuit pour automatiser mes tests de web service. On m'avait parlé de soapUI, je voulais m'en faire une idée.

Dans cet article je vais, au travers d’un exemple, utiliser soapUI pour effectuer toutes les étapes nécessaires à l’élaboration de tests automatisés : tests unitaires de requêtes, tests fonctionnels, tests en intégration continue. Nous allons donc mettre à l'épreuve soapUI dans le but de créer une suite de tests automatisés.

Note : il existe bien sûr d'autres outils permettant le test de web services (Rational Tester, SOATest, Visual Studio...) mais cet art icle ne se focalise que sur soapUI qui semblait se rapprocher de mes besoins (cités plus haut).

Notre exemple sera le développement d'un portail de banque en ligne. Nous allons nous intéresser aux fonctionnalités suivantes :

  • Identification
  • Récupération de la liste des comptes
  • Diverses opérations sur un compte : dernières opérations, caractéristiques du compte...

On a un serveur web communiquant avec une interface web distante. L'identification à l'interface par l'utilisateur se fait en deux phases :

  • L'utilisateur entre son identifiant et son adresse e-mail.
  • L'utilisateur entre ensuite un code via un pavé numérique généré.

L'utilisateur doit entrer un code dans les espaces restant correspondant à son mot de passe. Les positions définies sont générées aléatoirement.

Voici la cinématique entre le client et le serveur :

Commençons notre projet soapUI :

Pour démarrer c'est très simple : il suffit de créer un projet soapUI et d'indiquer l'URL du WSDL (contrat) de notre première requête. SoapUI se charge de créer un squelette de requête selon la définition du contrat. Un pré-requis tout de même, le serveur exposant les WSDL doit être démarré.

La logique d'organisation de soapUI est similaire à celle d'un projet de développement : un projet contient plusieurs end-points (point d'accès à un service exposé) qui contiennent chacun une liste de requêtes.

Un test, puis un autre

Pour notre exemple, il y a 2 end-points et 2 requêtes : login et (validate) pin. Démarrons avec la première requête : elle contient un identifiant et un e-mail. SoapUI charge la définition de la requête à partir du WSDL publié. On peut entrer nos valeurs pour voir la réponse du serveur.

On veut maintenant s'assurer que pour de faux identifiants, le noeud “Result” doit être à faux. SoapUI propose plusieurs types d'assertions sur les réponses : tester la valeur d'un noeud selon XPath, valider le schéma selon le WSDL...

Maintenant que nous avons nos tests sur la requête de login, nous pouvons faire la même chose sur la requête de validation du code pin.

On teste de manière unitaire les 2 requêtes. C'est intéressant pour détecter des régressions sur les contrats mais pas suffisant pour une couverture de test fonctionnel significative.

Tester un enchaînement d'actions utilisateur

L’enchaînement de requêtes va permettre de simuler un utilisateur. Un Test Case soapUI (cas de test) exécute chaque requête qu'il contient. Plus généralement, un Test Case soapUI exécute chaque élément, appelés “étapes” (Step), séquentiellement. Une requête est une étape, une injection de propriété également :

Dans certains cas, une requête dépend de la réponse à la requête précédente. Pour illustrer, prenons l'exemple de l'achat d'un produit. La première requête est une recherche d'un produit. La deuxième requête est l'achat d'un produit. Pour enchaîner ces requêtes, nous devons récupérer l'identifiant du produit de la recherche pour l'utiliser dans la deuxième requête.

Certaines steps de soapUI permettent l'injection de paramètres entre les requêtes. Il est possible de sauvegarder des paramètres utilisables par des requêtes différentes (properties) et de passer des paramètres d'une requête à l'autre (property transfer).

Dans notre cas, l'utilisateur doit passer par les deux étapes d'identification. Le souci est que la deuxième étape dépend du résultat de la première. Pour rappel, les positions pré-remplies du code pin -venant de la réponse de la première requête- sont générées aléatoirement.

Les “property step” ne permettent malheureusement pas de transformer des positions en code pin ! Pour résoudre ce problème, il faut retrousser ses manches et écrire des scripts Groovy permettant de manipuler les requêtes.

Ainsi, on peut initialiser une série de test par un script Groovy lisant du fichier plat (à tout hasard, au format CSV) puis injectant les données récupérées dans les requêtes. On peut également variabiliser un end-point, des inputs... Tout pour éviter les paramètres en dur, freins à une automatisation efficace.

Avec un script Groovy permettant de traduire un ensemble de positions en portion de code pin, l'enchaînement des étapes se fait naturellement :

def tc = testRunner.testCase;
def groovyUtils = new com.eviware.soapui.support.GroovyUtils( context )

// Récupérer le noeud de la réponse du login
def holder = groovyUtils.getXmlHolder( "Login - Login request OK#Response" )

// Récupérer la position du pin depuis la réponse
holder.namespaces["def"] = 'http://octo.com/exemple/definitions'
def pinPosition = holder.getNodeValue( "//def:Pinposition" ).split(',')

// Définition du code pin en dur
def pinNumberTable = [1, 0, 2, 0, 5, 4 ]

// Construction du pin à envoyer
def pin = ''
for ( position in pinPosition ) {
	pin += pinNumberTable.get(position.toInteger() - 1)
}

// Injecter le pin dans la requête au service PinPosition
holder = groovyUtils.getXmlHolder( "Pin - Pin OK#Request" )
holder.namespaces["def"] = 'http://octo.com/exemple/definitions'
holder.setNodeValue( "//def:Pin", pin )
holder.updateProperty()

Notre test case est maintenant un enchaînement de requêtes que l'on peut exécuter manuellement.

Tu me passes ton cas de test ?

Une étape de plus vers le gain de productivité : la réutilisation de cas de test.

Cette fois, on entre dans la logique de design de nos cas de tests. Tout comme du code, les cas de tests soapUI doivent suivre une architecture modulaire pour éviter les duplications.

Reprenons notre exemple. Nous avons donc nos services d'identification (login + validation du pin). Nous voulons maintenant tester la récupération de la liste des comptes ainsi que la récupération du détail d'un des comptes (type de compte, plafond de retrait, autorisation de découvert, etc...). Suivant le même modèle, on crée un cas de test ayant les requêtes suivantes :

  • Login
  • Pin
  • GetListeDeComptes
  • GetCompteDetail

On garde entre les deux premières requête notre script Groovy. On utilise aussi une Property Step pour récupérer, via XPath, un numéro de compte parmi ceux de la liste (GetListeDeComptes) qu'on injecte à la requête GetCompteDetail.

Maintenant, on veut consulter les dernières opérations d'un compte :

  • Login
  • Pin
  • GetListeDeComptes
  • GetDernieresOperations

Idem pour la liste des cartes associées a un compte... Bref, on voit que la duplication des 3 premières requêtes se fait sentir !

Plutôt que de réécrire les trois premières requêtes (login, pin, liste des comptes + la Property Step injectant le numéro d'un compte) pour les deux nouveaux cas de tests, créons un cas de test « récupérer un numéro compte depuis une liste ».

Ce cas de test sera ensuite réutilisé dans les cas « DetailCompte » et « DernieresOperations ». SoapUI permet de faire cela de deux manières :

  • Graphiquement : en utilisant l’étape « Run TestCase ». Cette étape permet de démarrer un autre cas de test présent dans le projet, de lui affecter des valeurs par défaut et de récupérer des valeurs de sorties. Personnellement, je n’ai pas réussi à faire cela dans la version gratuite…
  • Avec un script Groovy : en invoquant un cas de test du projet et en lui donnant des propriétés (Property Step)

On a rendu notre suite de tests modulaire !

Et maintenant, sans les mains !

L'étape supérieure dans l'automatisation est la suppression de l'exécution manuelle des scénarios de test. soapUI est livré avec un script, testrunner, pour exécuter les tests d'un projet soapUI en ligne de commande. En environnement .NET, on peut donc faire sa tâche MSBuild de test de web-services dans son intégration continue qui lance le projet soapUI avec testrunner et parse l'output.

Côté JAVA, il existe un plugin Maven pour soapUI. Démonstration par ce morceau de pom :

<plugin>
    <groupid>eviware</groupid>
    <artifactid>maven-soapui-plugin</artifactid>
    <version>3.5.1</version>
    <configuration>
        <projectfile>../../../Blog/BlogOcto-soapui-project.xml</projectfile>
        <printreport>true</printreport>
    </configuration>
    <executions>
        <execution>
            <id>soapui-test</id>
            <phase>integration-test</phase>
            <goals>
                <goal>test</goal>
            </goals>
        </execution>
        </executions>
</plugin>

Ceci est à compléter par des tâches pre/post phase servant à démarrer votre serveur d’application préféré pour tester les services.

Pour aller plus loin

En plus des tests de recette, il est possible de faire du test de charge. On peut choisir un use-case à dérouler selon un nombre défini d'utilisateurs simultanés (possibilité d'inclure de la latence).

Si l'on travaille en contract-first (on fixe les contrats WSDL puis on implémente), plutôt que de partager uniquement les WSDL, il est possible de mocker les réponses. Cela permet également de partager un projet soapUI (très utile en approche contract-first).

On peut même imaginer une interface web type Fitnesse pour définir des jeux de données d'entrée aux cas de tests en modifiant le projet soapUI (les projets sont sauvegardés en format XML) ou en générant des fichiers parsés par des scripts Groovy dans le projet soapUI.

Version pro ou version gratuite ?

En projet, j’ai pu constater que la version gratuite me suffisait largement.

Les différences entre la version pro et la version basique se font principalement sur l’aide à l’utilisateur. En effet, la version pro apporte en plus des outils d’aide aux tests : aide XPath, templates de code Groovy, gestion des données externes via IHM (au lieu d’utiliser des scripts Groovy)…

Tout ce qu’apporte la version pro est faisable en version gratuite mais en plus « geek » !

Verdict ?

Pour conclure, soapUI m'a été d'une grande aide pour :

  • Enchaîner des requêtes.
  • Tester la validité des requêtes selon les WSDL.
  • Tester la non-régression en intégration continue.

L'utilisation des scripts Groovy est un plus pour appliquer des règles de gestion entre des requêtes.

Tout cet article est applicable sorti de l’exemple « Hello World ! ». En effet, l'exemple cité était la version simple de ce que j'ai croisé en réalité : les positions et le code pin devaient être encryptés, les deux étapes d'identifications permettaient de générer un hash servant de token d'authentification pour les autres requêtes.

La grande question : est-ce que j’aurai pu faire sans ? Dans mon écosystème geek, la réponse est oui. J’ai effectivement gagné beaucoup de temps mais j’ai perdu en intégration avec mon environnement de développement local (en JAVA notamment, même si soapUI s'intègre avec quelques IDE). J’ai utilisé soapUI en complément de tests unitaires

Dans un contexte plus réaliste, avec des MOA et/ou partenaires impliqués sur le projet (c’était évidemment mon cas), il est très difficile de s’en passer pour les raisons suivantes :

  • Partager les suites de test entre les développeurs/projets/MOA/[insérez votre interlocuteur].
  • Mocker les réponses pour tester les contrats pour le cas de développement avec un partenaire.
  • Tester indépendamment d’un Eclipse ou d’un Visual Studio (casser la dépendance MSUnit/jUnit/[insérez votre framework de test]

Deux liens pour continuer vous-même :