Skip to content

Écrire des tests automatisés (ou pas)

5 septembre 2014

Les tests automatisés sont des tests qui sont exécutés par la machine régulièrement. Par example, à chaque fois qu’un commit est réalisé dans un système de gestion de version (comme subversion, mercurial, git, voire CVS pour les plus nostalgiques). L’utilisation de tests automatisés permet d’espérer des gains de temps et de sérénité.

L’article vise à montrer les avantages et les limites des tests automatisés ainsi que des pistes pour que des personnes (ou équipes) sans expérience dans le domaine puissent s’y mettre.

I. Pourquoi ne PAS faire de tests automatisés

Il existe des cas où ne pas avoir de tests automatisés peut être un choix pertinent.

1. Nouveauté

Lorsque l’on découvre quelque chose d’autre de nouveau, le besoin d’expérimenter et de comprendre le fonctionnement est prioritaire. Se contraindre à ajouter des tests en même temps peut gêner l’expérimentation. Dans ce cas, il est préférable de ne pas en faire pour se concentrer sur la nouveauté. Il est plus simple de n’apprendre qu’une chose à la fois donc ce n’est pas la peine d’avoir à apprendre une nouveauté et d’apprendre à tester cette nouveauté en même temps.
Ce cas s’applique lors de l’utilisation d’un nouveau langage ou d’un nouveau framework par exemple.

2. Besoin très simple

Si le besoin est très simple, il est possible que ne pas avoir de tests soit plus rapide car faire les tests à la main ira plus vite (partie gauche de la droite rouge sur le schéma).

Rendements de l'utilisation de tests

Plus le produit est complexe, plus il devient fastidieux de retester toutes les fonctionnalités et le temps passé devient très important (partie droite de la droite rouge sur le schéma). Dans les faits, seules les dernières modifications sont testées et le reste du logiciel est supposé ne pas être influencé par les modifications. C’est vrai la plupart du temps mais cela laisse la porte ouverte à des régressions.

3. Code jetable

Dans le cas d’un programme qui a un usage unique (extraction de données particulières, migration d’un schéma de base de données, traitement d’un cas client particulier), avoir des cas à vérifier peut être une solution plus rapide et aussi fiable que des tests automatisés.

Par contre, si l’on prévoit de garder le script pour le réutiliser plus tard dans des conditions similaires, le même effet va se reproduire : avoir des tests sera plus efficace à long terme.

Le problème n’est pas d’écrire du code jetable, c’est de ne pas le jeter.

Pour tout le reste, il y a probablement des tests qui vous rendraient service.

II. Beaucoup de tests possibles

Il existe de nombreux types de tests (tests unitaires, tests d’intégration, tests de performance, …), chacun d’entre eux répondant à des besoins différents. Certains articles portant sur les tests les listent de manière exhaustive, donnant une impression de travail à fournir gigantesque.

Pourtant, pour bénéficier des avantages des tests automatisés :

  • il n’y a pas besoin de tous les faire ;
  • il n’y a pas besoin de tout tester.

En ayant l’impression que la tâche est énorme, il y a un risque de procrastination. Le coût de mise en place sera considéré comme très élevé et sera continuellement repoussé. Les tests ne seront donc jamais fait.

Il me semble préférable de commencer par des tests faciles (un algorithme de tri par exemple) ou là où le besoin se fait sentir. Sur du code préexistant, il peut être plus simple de commencer par des tests vérifiant un fonctionnement assez général (par exemple, à une requête web, le serveur renvoie une page avec un code 200).
Une fois le mouvement initié, il sera plus aisé d’augmenter la couverture au fur et à mesure. De même, on pourra augmenter la complexité des éléments à tester (comme vérifier les données enregistrées dans une base de données, l’interfaçage avec une API externe, etc.). Il existe des solutions classiques pour ces cas mais ne pas commencer par là permet d’esquiver ces problèmes au début.

Le plus probable est de débuter avec des tests unitaires mais ce n’est pas une obligation. Le plus important étant d’avoir des tests réellement automatisés pour pouvoir bénéficier des avantages qu’ils procurent, ce qui devrait aider à les maintenir et à améliorer la couverture du code.

III. Pré-requis

Évidemment, il faut pouvoir exécuter les tests et l’application hors de la production, au mininum soit sur la machine locale des développeurs, soit dans un environnement d’intégration.

Si les développement sont validés directement en production, la priorité est d’avoir au moins un environnement de développement spécifique, pas d’écrire des tests automatisés. L’écriture des tests est secondaire et sera réalisée par la suite.

IV. Des bogues subsisteront

Les tests ne garantissent pas l’inexistence d’anomalies. Pour cela, il faudrait utiliser des méthodes formelles mais le coût de mise au point est tellement coûteux…

Les tests ne protègent que :

  • des erreurs de syntaxe des fichiers inclus (même si le code lui-même est non testé) ;
  • du non-respect des assertions qu’ils incluent.

Les tests sont donc une solution imparfaite mais :

  • plus la couverture est élevé et plus on est sûr de couvrir de fonctionnalités et de cas particuliers ;
  • c’est une garantie contre les régressions futures ;
  • le fait qu’ils soient automatisés permet d’avoir facilement une idée de l’état du code à n’importe quel moment. Il permet donc de détecter immédiatement lorsque une régression survient.

Avoir des tests automatisés est donc une solution préférable à aucun test. Les gains obtenus par les tests automatisés surpassent les inconvénients dans le cas général.

V. TDD ?

Faire du développement dirigé par les tests (TDD, pour Test Driven Development) est souvent cité en même temps que l’importance de faire des tests. Au-delà des avantages de TTD, les aficionados affirment aussi que c’est plus simple une fois que l’on y on est habitué. Pourquoi ?

1. Processus de développement avec TDD

Le processus de développement est itératif (comme sans TDD). La boucle commence avec le noeud en vert. L’étape d’amélioration du code (noeud refacto dans le schéma) doit être faite si le nécessaire. Les autres étapes sont toutes obligatoires  :

Processus de développement avec TDD

2. Processus de développement sans TDD

Pour obtenir la même garantie, voici le processus à suivre :
Processus de développement sans TDD

L’étape de désactivation du code pour vérifier que le test ne passe plus est nécessaire car sans elle, il n’est pas possible de valider que le test vérifie correctement le code. Cette désactivation est généralement réalisable trivialement en commentant le code ajouté.

Il est possible d’avoir des tests automatisés sans TDD :

  • si le développeur le fait selon le processus présenté ci-dessus ;
  • si ce sont des équipes différentes qui écrivent le code et ceux qui écrivent les tests.

Il me semble regrettable de ne pas en faire, mais il n’est pas nécessaire d’écrire ses premiers tests automatisés avec TDD : si c’est déjà une nouveauté, pas besoin d’en ajouter une deuxième.

Pour ceux qui voudraient un guide pratique en Python (principalement sur les tests unitaires mais les réflexions s’appliquent aux autres langages), en trois parties sur sametmax :
Partie 1, Partie 2, Partie 3

Annexe : Code source des graphiques

Le graphique a été créé avec la bibliothèque python matplotlib v.1.1.1 (paquet python-matplotlib dans Debian). Voici le script qui a permis de le produire :

# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(range(10), range(10)) #sans test
ax.text(7.5, 7, 'sans tests', style='italic', color="blue")

ax.plot([0, 9], [2, 5]) #avec test
ax.text(7.5, 4.1, 'avec tests', style='italic', color="green")

ax.plot([6, 6], [0, 9]) # début de la rentabilité des tests

ax.annotate(u"le code avec tests devient\nplus rapide à\ndévelopper/maintenir",
    (3, 3),
    xytext=(-110, +50),
    arrowprops={"arrowstyle": '->'},
    textcoords="offset points",
    bbox=dict(boxstyle="round", fc="0.8"),)

ax.annotate(u"début de la\nrentabilité",
    (6, 1.5), 
    xytext=(+40, +25), 
    arrowprops={"arrowstyle": '->', "connectionstyle": "angle,angleA=-90,angleB=180,rad=10"},
    textcoords="offset points",
    bbox=dict(boxstyle="round", fc="0.8"),)

plt.xlabel(u"Complexité")
plt.ylabel(u"Temps passé")

ax.spines["right"].set_visible(False)
ax.spines["top"].set_visible(False)
ax.tick_params(axis='both', which="both",
     bottom=False, top=False, right=False, left=False,
     labelbottom=False, labelleft=False)

plt.savefig("./rendements_tests.png")

Les graphes ont été réalisés avec dot (contenu dans le paquet graphviz dans Debian).
Avec TDD:
$ dot -Tpng tests_avec_tdd.gv -o tests_avec_tdd.png
Contenu du fichier tests_avec_tdd.gv

digraph G {
        "écriture du test" [color=darkgreen, fontcolor=darkgreen, shape=rectangle];
        "(refacto)" [color=darksalmon, style=filled, shape=polygon, sides=7];

        "écriture du test" -> "le test ne passe pas";
        "le test ne passe pas" -> "écriture du code";
        "écriture du code" -> "le test passe";
        "le test passe" -> "(refacto)";
        "(refacto)" -> "écriture du test";
}

Sans TDD:
$ dot -Tpng tests_sans_tdd.gv -o tests_sans_tdd.png
Contenu du fichier tests_sans_tdd.gv

digraph G {
        "écriture du code" [color=darkgreen, fontcolor=darkgreen, shape=rectangle];
        "(refacto)" [color=darksalmon, style=filled, shape=polygon, sides=7];

        "écriture du code" -> "le code fonctionne";
        "le code fonctionne" -> "écriture du test";
        "écriture du test" -> "le test passe";
        "le test passe" -> "désactivation du code";
        "désactivation du code" -> "le test ne passe plus";
        "le test ne passe plus" -> "réactivation du code";
        "réactivation du code" -> "(refacto)";
        "(refacto)" -> "écriture du code";
}

From → Autre

Laisser un commentaire

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :