Tests unitaires

Les plus beaux et complexes ouvrages sont composés de pièces individuellement fiables. Cette page vous permettra de comprendre la nature et l'intérêt des tests unitaires en génie logiciel.

Définition : qu'est-ce qu'un test unitaire ?

Comme beaucoup de concepts en informatique, le concept de test unitaire n'est pas simple à définir. Non pas qu'il soit réellement complexe, mais plutôt parceque beaucoup de développeur se font leur propre idée de ce qu'est un test unitaire…

Définition Un test unitaire est un procédé permettant de s'assurer du bon fonctionnement d'une unité de programme.

OK, cette définition est assez générale, il ne prend pas de risque vous direz-vous peut-être. Certes, mais il est vrai que le terme « unitaire » peut lui-même renvoyer à ces réalités assez différentes. Une unité de programme est-elle une fonction ? Une classe ? Une instruction ?

Au fond, à quoi sert un test unitaire ?

Au delà des définitions, une bonne manière de comprendre le testing unitaire est d'en comprendre le but, de façon pragmatique.

Il s’agit simplement de vérifier, en fonction de certaines données fournies en entrée d’un module de code (on parle parfois d'unité fonctionnelle testée, ou SUT pour system under test), que les données qui en sortent ou les actions qui en découlent sont conformes aux spécifications du module.

En d'autres termes, il s'agit de vérifier le respect du contrat de service du module.

Testing unitaire : un exemple

Considérons un système informatique composé de deux gros modules interdépendants. Dans chacun de ces modules, nous trouvons différents composants, eux-même dépendants entre eux.

Test unitaire : présentation du système

Sur notre exemples, le module D accèpte en entrée des données qui lui sont fournies par le composant A, et produit des données en résultat qui sont utilisées par le composant B. OK ?

Imaginons maintenant que le développeur Arnaud soit responsable du développement et de la maintenance du composant A, que Brigitte soit responsable du composant B, et que Daniel soit responsable du composant D.

Respecter son contrat…

Si Arnaud fait mal son boulot en déployant un composant A buggué qui n'implémente pas correctement la spécification qui lui a été fournie, il y a fort à parier que le composant D du pauvre Daniel plantera lamentablement, même s'il est correctement développé. C'est alors que commencent de longues et pénibles discussions, du genre « Non, mais c'est ton code qui est faux, non c'est le tien, bla, bla, bla… ».

Une solution simple pour s'assurer que chacun fait correctement son travail est tout simplement d'implémenter des tests unitaires sur chacun des composants. Cette tâche revient à chaque développeur : Arnaud écrira les tests unitaires de A, Brigitte écrira les tests unitaires de B, Daniel écrira les tests unitaires de D, etc.

Le cas de Daniel

Dans le cas de Daniel, il s'agit de s'assurer que si le composant D reçoit les données qu'il attend (i.e. conformes à la spec), alors il produit les résultats qu'il est censé produire (i.e. conformes à la spec). Ni plus, ni moins.

Les tests unitaires de D auront donc grosso-modo cette forme :

Test unitaire : respecter son contrat entrées sorties

En d'autres termes : si je fais entrer le jeu de données x1 dans la moulinette D, alors il sort y1. Si je fais entrer le jeu de données x2 dans la moulinette D, alors il sort y2.

Pré-requis pour tester unitairement

Il y a en fait peu de pré-requis pour pouvoir tester son code unitairement. Nous le verrons plus loin, quasiment tous les langages de programmation et tous les frameworks de développement dignes de ce nom proposent des outils de testing unitaire.

Le principal pré-requis au testing est la modularité du code. Une idée simple : diviser pour mieux régner. Un code modulaire est bien plus robuste qu'un imbroglio de règles de gestion et de traitements. Plus il est modulaire, plus un code est facile à tester unitairement…

Conclusion : chacun respecte son contrat et tout le monde est content !

À retenir – Le testing unitaire repose au fond sur un principe très simple : un système aussi gros et complexe qu'il soit est toujours composé différentes parties plus petites que lui. Il est indispensable (mais pas suffisant) que chacune de ces parties remplisse correctement sa fonction pour que l'ensemble soit fonctionnel et fiable.

Ouais mais y'a pas de code source ?

Attendez ! Avant de coder, il faut comprendre les concepts. Et c'est généralement une mauvaise idée d'apprendre les concept en même temps que leur concrétisation. Nous verrons des exemples concrets dans les sections suivantes…

Testez vos connaissances

Qu'est-ce qu'un test unitaire ?
  • Un test qui se focalise sur un composant de petite taille
  • Un test qui n'est exécuté qu'une fois
  • Un test qu'il est possible de rejouer à volonté
  • Un élément important dans une stratégie d'intégration continue

La mise en place d'une stratégie d'intégration continue repose généralement, en premier lieu, sur une solide politique de testing unitaire. Ainsi, il est possible de déclencher aisément (via des hooks) l'exécution des tests unitaires avant ou après un commit, avant ou après un déploiement, etc.

En quoi la notion de contrat de service permet-elle d'aider à l'écriture de tests unitaires ?
  • Un bon contrat de service permet d'attester qu'un composant est développé correctement.
  • Le contrat de service d'un composant décrit ce qu'il est censé faire : quand on sait ce qu'est censé faire quelque chose, c'est plus facile de tester si ce qu'il fait correspond à ce qu'il est censé faire.
  • L'écriture du contrat de service après l'écriture des tests permet de vérifier que les tests sont cohérents.

Concrètement, le contrat de service est généralement la documentation technique d'un composant (imaginez par exemple la JavaDoc d'une classe Java…).

Soient deux composants A et B, avec A qui alimente B en données. Le créateur de B accuse A de lui fournir des données pourries. Que doit faire le créateur de A ?
  • Lui présenter le rapport de tests unitaires de A, démontrant que son composant produit les données attendues en fonction de ce qu'on lui fournit en entrée.
  • Mettre une dérouillée au créateur de B à la sortie du bureau.
  • Mettre en place d'urgence des tests unitaires de son module A si ce n'est pas encore fait.
  • Contre attaquer en répondant que c'est son composant B qui n’interprète pas correctement les données en entrée.

Pour les composants logiciels de type « boite noire », les tests unitaires sont la meilleure manière de démontrer que le composant réalise son travail correctement.

Livres intéressants sur le testing unitaire

Effective Unit Testing : A guide for Java developers

Effective Unit Testing : A guide for Java developers

Voir

Pragmatic Unit Testing in Java with Junit

Pragmatic Unit Testing in Java with Junit

Voir

The Art of Software Testing

The Art of Software Testing

Voir