Il m'arrive fréquemment d'avoir à écrire du code qui doit parler avec un serveur HTTP externe, par exemple, sur mon projet actuel, nous interagissons avec :

  • un serveur de paiement
  • un serveur de publicités
  • un streamer vidéo
  • un serveur d'options client

J'aime bien écrire mon code de test avant d'écrire mon code de production, et j'imagine que vous en faites autant. C'est ainsi que nous avons développé au fil du temps une librairie de tests qui nous permet de simuler un serveur HTTP. Cet librairie lance un véritable serveur qui écoute sur une socket. Nous pouvons lui donner des réponses standard (réponse 200 avec contenu OK, réponse 40x ou 50x, redirect etc) mais également lui donner un comportement plus avancé en lui passant un TraiteurDeReponse. Cela nous permet de simuler tous les intervenants extérieurs à notre produit pour nos tests unitaires mais aussi pour nos tests d'acceptance.

Voyons tout de suite un exemple de test qui utilise ce MockHttpServer. Notez que l'ouverture/fermeture d'une socket prend du temps, aussi le serveur n'est lancé une fois en début de test (@BeforeClass) et fermé après tous les tests de la classe (@AfterClass)
Dans cet exemple, je ne vérifie que l'URL appelée, mais je peux aussi vérifier les headers passés, le contenu que je lui ai envoyé. Ainsi, je fige dans mes tests le contrat d'interface entre notre produit et les produits externes. Le serveur de test répond par défaut 200/OK :

mais on peut lui passer d'autres types de réponses. Les plus communes sont 200/OK, 500/KO, 30x/redirect, aussi une factory nous permet d'accéder rapidement à ce type de réponses (et de les factoriser) :

Ainsi, on peut tester comment notre application réagit quand le service en face ne répond pas ce qui est défini dans le contrat d'interface ou si il met trop de temps à répondre. Par exemple, pour tracer les défaillances des services externes et expliquer comment cela impacte notre qualité de service, nous testons nos logs avec notre LoggueurEspion (qui fera l'objet d'un article à paraître prochainement). Oui, c'est la technique dite du parapluie.  Enfin, il est possible de créer soi-même un type de réponse très spécifique et qui dépend des requêtes reçues. On peut écrire un serveur avec un peu de mémoire en implémentant un autre TraiteurDeReponse dont voici une partie du code :

Ces classes sont si faciles à utiliser que nos tests d'acceptance (qui tournent sous Fitnesse) les utilisent aussi. Comme pour les tests unitaires, nous démarrons et arrêtons les serveur HTTP en début et en fin de suite : non seulement cela permet d'exécuter plus rapidement les tests, mais cela s'approche au plus de la réalité de ce que nous simulons dans laquelle ces services sont déjà présents et tournent en permanence. Pour réaliser cela avec Fitnesse, nous utilisons le setUp/tearDown de suite qui appele notre DemarreurDeSuite :

Enfin, nous pouvons packager ces classes créées pour nos tests d'acceptance pour lancer des bouchons HTTP sur nos environnements d'intégration. Le code a donc été utilisé 3 fois :

  • dans les tests unitaires
  • dans les tests d'acceptance
  • sur les environnement d'intégration

La différence avec une approche où l'on mockerait les appels tient en trois points :

  • le code est réutilisable à de multiples niveaux (unitaire, acceptance, intégration)
  • le test est non intrusif et n'influence pas l'implémentation. Ainsi, vous pouvez changer de client HTTP (passer de l'API standard Sun à celle proposée par HttpClient d'Apache) sans avoir à toucher vos tests (oui, bien souvent l'approche "mock" des appels fige le design)
  • il est possible de tester des cas d'erreur très difficiles à traiter autrement (un read timeout par exemple)

Voilà ! J'espère que cet article vous a ouvert de nouvelles perspectives de tests. Nous n'avons pas de soucis de socket qui reste ouverte et qu'on ne peut réutiliser, mais avant d'en arriver là, il nous a fallu peaufiner et peaufiner encore notre code.

Comme pour la simulation du temps dans les tests, nous voudrions ouvrir le code de cette librairie, mais il faut que nous voyions cela auprès de notre employeur. Restez branchés !

Et vous, comment testez-vous vos interractions avec le monde HTTP ?


ps : Pascal Grange a également écrit sur le sujet et propose une solution très différente mais également intéressante :  Tests unitaires, HTTP et Java