D'après Python, petit guide à l'usage du développeur agile, de Tarek Ziadé.
Chaque fonction du programme effectue une tâche donnée. Pendant sa conception, des cas d'utilisation sont présents à l'esprit du programmeur. On peut, avant ou pendant la conception de cette fonctionnalité, écrire en parallèle des tests correspondant auxdits cas d'utilisation.
Le code est alors couvert par une batterie de tests, et une campagne de tests, chargée de tester l'intégralité du code, peut être menée, pour garantir en particulier la non-régression de son code. On est alors assuré que l'ajout d'une nouvelle fonctionnalité n'en cassera pas d'autres.
On distingue les tests unitaires des tests fonctionnels :
Ces tests permettent un réel gain de temps au niveau du déboguage.
Le module unittest est une implémentation sous Python de JUnit, bibliothèque de tests unitaires sous Java, devenue une référence.
Ce module offre une classe de base TestCase, qui peut être utilisée pour concevoir des tests. Chaque méthode
sera considérée comme un test unitaire, dont un commencement de test_toto.py :
import unittest class TotoTest(unittest.TestCase): def test1(self): ... def test2(self): ...
La classe TestCase fournit, entre autres, les méthodes suivantes, permettant de lever des exceptions correspondant à l'echec de tests classiques :
Dans tous les cas, on peut aussi passer un message en argument, afin d'expliciter les raisons pour lesquelles le test échoue.
Par exemple, si le module d'origine (toto.py) possède une fonction division, le début de tests/test_toto.py pourrait ressembler à :
import unittest import os import sys dirname = os.path.dirname(__file__) if dirname == '': dirname = '.' dirname = os.path.realpath(dirname) updir = os.path.split(dirname)[0] if updir not in sys.path: sys.path.append(updir) from toto import division class TotoTest(unittest.TestCase): def test_division(self): self.assertEquals(division(10,5),2) self.assertRaises(ZeroDivisionError, division, 10, 0)
Dans ce qui précède, le répertoire parent est ajouté à sys.path, pour permettre l'importation de toto : en effet, les modules de tests sont, on le rappelle, traditionnellement placés dans un sous-répertoire.
Les classes de tests sont ensuite regroupées dans des séquences, les tests suites, utilisées par les campagnes de tests comme suit :
import unittest import os import sys dirname = os.path.dirname(__file__) if dirname == '': dirname = '.' dirname = os.path.realpath(dirname) updir = os.path.split(dirname)[0] if updir not in sys.path: sys.path.append(updir) from toto import division class TotoTest(unittest.TestCase): def test_division(self): self.assertEquals(division(10,5),2) self.assertRaises(ZeroDivisionError, division, 10, 0) def test_suite(): tests = [ unittest.makeSuite(TotoTest) ] return unittest.TestSuite(tests) if __name__ == _main_: unittest.main(defaultTest = 'test_suite')
Pour tester le module toto.py, reste à faire :
$ python test_toto.py
Les méthodes setUp() et tearDown() de TestCase seront appelées, si elles existent, avant et après chaque test, faisant office de constructeur et de destructeur de la classe TestCase.
class TotoTest(unittest.TestCase): """ Classe de tests """ def setUp(self): self._x = 2 def test_division(self): self.assertEquals(division(10,5),self._x) self.assertRaises(ZeroDivisionError, division, 10, 0) def test_nul(self): print self._x
Dans ce qui précède, on a ajouté un attribut à la classe TotoTest, que l'on peut utiliser ensuite.
Pour une campagne de tests, il reste à exécuter à la suite tous les modules de tests unitaires, à l'aide d'un script.
Les doctests sont une alternative aux tests unitaires. Le développeur insère dans ses programmes des exemples de code, qui sont ensuite exécutés comme des tests : le module doctest extrait des programmes les exemples, et les exécutent pour vérifier qu'ils fonctionnent réelement.