Mais d'abord, qu'est ce qu'un mock ?

Contrairement à ce que l'on pourrait croire, "mock" n'est pas une insulte klingon et n'a rien à voir avec le bork, même si les deux mots ont un air de famille certain et qu'un mock peut devenir aussi délicat à gérer qu'une marionnette du Muppet Show.

Plus sérieusement, selon Wikipedia, en programmation orientée objet et dans le cadre des tests unitaires, un mock est un objet virtuel qui permet au développeur de simuler un objet réel (Si vous avez mal au crâne à partir de ce point, c'est parfaitement normal, et je vous informe que cela ne va pas s'améliorer).

Nous savons maintenant ce qu'est un "mock".

Mais quel en est l'intêret ?

Une solution à un problème de prédictabilité et de contexte

Bien souvent, le résultat fourni par une méthode de classe dépend de son contexte d'éxécution et/ou de l'état dans lequel se trouve d'autres objets qu'elle met en oeuvre.

Ainsi, si la méthode dépend d'un horaire, son résultat peut dépendre de l'heure à laquelle elle est éxécutée, ce qui n'est pas prédictible, puisque la méthode peut être utilisée à tout moment.

Il en va de même si la méthode met en oeuvre un système de fichier.

Dans ce cas, le résultat de l'éxécution de la méthode sera différent en fonction de l'espace disque disponible sur le système de fichier, ou bien des droits du fichier à manipuler.

Or, il est délicat pour un développeur de remplir son espace disque pour vérifier que sa méthode se comporte correctement dans cette configuration, à moins qu'il n'accepte de s'exposer à quelques problèmes...

Il n'est donc pas possible de tester efficacement certains comportement, puisque le contexte dans lequel ils surviennent n'est soit pas prédictible, soit non reproductible.

Or, dans l'idéal, les tests unitaires doivent couvrir l'ensemble des comportements possibles.

De plus, ils doivent s'effectuer le plus rapidement possible, puisqu'ils sont destinés à être éxécuté très souvent.

Les mocks sont une solution à ces deux problèmes.

Simuler n'est pas forcément mal

Afin d'illustrer mon propos, je vais supposer que nous souhaitons développer un objet permettant de dialoguer avec un serveur.

Afin de tester cet objet, il nous faut donc avoir à notre disposition un serveur.

Nous avons donc, à première vue, deux solutions :

  1. Nous installons le serveur sur notre machine de développement.
  2. Nous utilisons un serveur distant correspondant à notre besoin.

Sauf que ces deux solutions posent plus de problèmes qu'elles n'en résolvent.

  • Rien ne nous permet de supposer que tous les développeurs qui seront amenés à travailler sur notre code dans le futur pourront/voudront/sauront installer le serveur localement.
  • Nous n'avons aucune garantie que notre serveur, qu'il soit local ou distant, répondra toujours présent lors de l'éxécution des tests.
  • L'utilisation d'un serveur réel introduira forcément de la latence dans nos tests, qui prendront donc plus de temps pour s'éxécuter, surtout si le serveur est distant.
  • Nous ne pourrons pas faire répondre ce que nous voulons au serveur, puisque nous ne le contrôlons pas, ce qui implique que nous ne pourrons pas tester, par exemple, la gestion de toutes les erreurs qu'il peut potentiellement nous envoyer.

La solution ? Simuler la connexion au serveur à l'aide d'un mock sur lequel nous avons un total contrôle.

Il sera toujours disponible, fiable, et nous pourrons lui faire dire ce que nous voulons, indépendament des compétences du développeur.

C'est bien joli, ton mock, mais comment ca marche ?

D'abord, cela ne marche pas, cela fonctionne.

Et ensuite, un mock hérite de la classe qu'il permet de simuler.

Il possède donc la même interface, mais chacune de ses méthodes sont en réalité des coquilles vides que le développeur peut remplir et faire réagir à sa guise.

Ce dernier peut ainsi définir, pour chaque méthode de la classe, et en fonction des arguments qui seront passés à ces dernières :

  • La valeur de retour de la méthode.
  • L'exception qui devra être lachée par la méthode.
  • L'erreur qui devra être générée.

De plus, tout cela peut être défini en fonction du nombre de fois ou la méthode a été appelée, etc.

Il peut également être utile de préciser qu'une classe peut être mockée partiellement ou complétement.

Il n'y a donc aucun inconvénient ?

Dans l'absolu, il n'y en a aucun.

Dans la pratique, il y en a un, et il est de taille, puisqu'il s'agit du développeur.

En effet, si mettre en oeuvre un mock est assez facile dans le cadre d'un test qui utilise peu d'objets ou dans un contexte d'éxécution simple, cela peut vite devenir compliqué dans le cas contraire.

Dans ce cas, le développeur doit définir des mocks en grande quantité, ainsi que leur comportement, à l'aide de plusieurs dizaines de lignes de code, pour tester une méthode qui peut ne contenir que quelques lignes.

Le développeur peut très bien avoir alors l'impression d'enculer les mouches de perdre son temps et renoncer.

Il faut donc qu'il ait du courage et de la volonté pour les mettre en oeuvre, car il peut être frustrant d'écrire du code de test dans ces conditions.

Il faut de plus qu'il comprenne précisément le contexte d'éxécution qu'il veut créer, afin qu'il puisse le définir correctement.

Et si cela est simple lorsque le développement met en oeuvre, par exemple, un système de fichier classique, cela peut vite devenir compliqué s'il utilise par exemple un protocole réseau.

En effet, pour simuler, par exemple, les réponses d'un serveur SMTP, il faut connaître précisément ce protocole, ce qui implique d'y passer du temps et de se documenter...

La mise en oeuvre des mocks demande donc un investissement humain important.

Cependant, le jeu en vaut clairement la chandelle :

  • Les tests sont plus complets, reproductibles indépendament de leur environnement d'éxécution, et plus rapides.
  • Le développeur acquiert de nouvelles compétences puisqu'il doit s'investir plus profondément pour réaliser son développement.
  • Le développeur possède une grande confiance dans son code car il n'y a plus de zone d'ombre dans ses tests.

Une petite astuce, pour en terminer avec les avantages, les mocks étant des simulateurs, il n'est pas obligatoire de disposer d'une classe parente réelle pour les utiliser.

Ainsi, si vous travailler en collaboration avec un partenaire qui doit vous fournir du code dont vous avez besoin pour votre développement, et que ce dernier a du retard, vous n'êtes pas obligé d'attendre sa livraison pour commencer votre travail.

Il vous suffit de définir l'interface du code que doit vous livrer votre partenaire, et d'utiliser le mock correspondant.

Alors, convaincu ?

Oui ? Non ? Peut être ? je vous ennuie ?

Laurent, lui, a semblé percevoir quelques applications concrètes des mocks à l'issue de notre discussion.

Alors, peut être qu'après la DI et l'IOC, les mocks feront leur apparition dans Jelix.