Tests unitaires

Un programme informatique, tant qu’il ne fait qu’imprimer « Hello World! » à l’écran, ça a l’air tout bête. Mais dès qu’on essaie d’en faire un peu plus, ça devient plus délicat… Alors vous imaginez ce qu’il en est lorsqu’on développe un programme informatique totalisant des milliers de lignes de code ! Par exemple voir le cas de BLAST pour les languages C/C++ ou Biopython pour le language Python.

A cela s’ajoute une difficulté de taille: le code évolue, tout le temps. De ce point de vue, un programme informatique est très proche, conceptuellement parlant, d’un organisme vivant. Il manipule de l’information, remplit une fonction bien précise et doit être performant dans un environnement donné. Malheureusement, à chaque fois qu’un développeur (le type qui pianote sur son clavier toute la journée…) met son nez dans le code pour y ajouter ou modifier quelque chose, il risque de casser autre chose (une mutation délétère en quelque sorte), et la plupart du temps sans s’en rendre compte.

crash test

Dans ces conditions, soit vous êtes comme Donald Knuth et vous savez écrire un programme dans les règles de l’Art qui compile du premier coup, soit vous êtes comme tout le monde et vous testez votre code. Les informaticiens étant des gens très pointilleux, vous imaginez bien qu’ils ont peaufiné la chose: une bonne façon de développer est de le faire en Test-Driven Development (TDD).

Pour ce faire, supposons que l’on veuille impimer « METHINKS IT IS LIKE A WEASEL » à l’écran (lire cet article pour comprendre l’origine de la phrase…). Disons que l’on utilise la programmation orientée objet pour développer et qu’on écrit le code en TDD. Pratiquement, on aura une classe « Example » ayant un attribut « message » correspondant à la phrase donnée ci-dessus et ayant une méthode « getMessage() » qui servira à renvoyer le message en attribut.

En bon élève, on commence par écrire le test. Voici ce que ça donne en Python:

import unittest
import Example
class Test_Example( unittest.TestCase ):
    def test_getMessage( self ):
        expected = "METHINKS IT IS LIKE A WEASEL"
        myObject = Example.Example();
        observed = myObject.getMessage();
        self.assertEqual( observed, expected )
test_suite = unittest.TestSuite()
test_suite.addTest( unittest.makeSuite( Test_Example ) )
if __name__ == '__main__':
    unittest.TextTestRunner(verbosity=2).run( test_suite )

On exécute ce bout de code et bien sûr le test ne passe pas puisqu’on n’a pas encore écrit la classe à tester. Ce qu’on s’empresse de faire:

class Example:
    def __init__( self ):
        self.message = "METHINKS IT IS LIKE A WEASEL"
    def getMessage( self ):
        return self.message
if __name__ == '__main__':
    main()

On relance le code de test et maintenant ça passe: il renvoie « ok ». On sait donc que notre méthode « getMessage() » fait bien ce qu’on lui demande de faire.

$ python Test_Example.py
test_getMessage (__main__.Test_Example) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK

Vu comme ça, ce billet de blog paraît un peu léger, je m’en doute. Mais il faut savoir que cette approche très « professionnelle » de la programmation (ExtremeProgramming, méthodes Agiles) est bien souvent absente des labos de bioinformatique. Un chercheur peut alors se retrouver à télécharger le dernier programme publié dans Bioinformatics sans que celui-ci ne fonctionne (en gros, on installe, on lance et ça plante). Mais le pire arrive lorsque le programme téléchargé tourne sans bug apparent mais ne fait pas exactement ce que ses auteurs prétendent qu’il fasse…

Pour éviter ça et faire évoluer votre code de manière modulaire et adaptive, faites des tests unitaires😉. Vous verrez, on s’y met très vite, d’autant plus que cette façon de travailler est grandement simplifiée grâce aux bibliothèques de tests unitaires déjà existantes: unittest en Python et cppunit en C++. En C++, c’est moins évident de comprendre du premier coup comment intégrer cppunit dans son code. Si vous voulez un exemple qui marche (on en trouve plein sur le web mais à qui il manque toujours qqch pour que ça compile comme il faut), demandez-moi, je peux vous en fournir un…

2 commentaires pour Tests unitaires

  1. Thomas dit :

    Je viens de solliciter les qq neurones qui me restent…
    Je crois que quelques explications de vive voix ne seront pas de trop, en
    particulier je ne suis pas très clair sur les syntaxes qui sont reservés et
    celles qui sont ad libidum.
    Et puis je me demande comment tu traites un cas plus complexe où la fonction
    te renvoie une valeur que tu ne connais pas a priori (ou qui dépendent de
    l’imbrication de n fonctions en amont).
    En tout cas, merci pour la vulgarisation🙂

    • walrus dit :

      Pour les questions de syntaxe, plus d’infos ici.
      Sinon, concernant le modus operandi à proprement parler, on écrit tous les cas de tests possibles. On a donc toujours une entrée et une sortie attendue, et on compare la sortie observée avec la sortie attendue. Comme tu peux l’imaginer, si on se trompe dans un test ou qu’on oublie un cas, la fonction testée peut avoir un comportement qui nous échappe… D’où le TDD (test-driven development): passer du temps à bien réfléchir à ce qu’on veut que la fonction fasse et comment on peut s’assurer qu’elle le fait effectivement bien. Puis écrire tous les tests nécessaires. Finalement écrire la fonction elle-même.
      Tout ça, en utilisant en plus le package coverage, permet de vérifier que tout se passe comme on veut et que l’intégralité du code de la fonction testée est bien parcouru (et donc testé).

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 :