May 20, 2024

Wiki

Python

Aide

edit SideBar

Search

Developpement Dirige Par Les Tests


D'après Python, petit guide à l'usage du développeur agile, de Tarek Ziadé.

Présentation

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.

Deux types de tests

On distingue les tests unitaires des tests fonctionnels :

  • Un test unitaire est chargé de valider, de manière isolée du reste du programme, le fonctionnement d'une classe, ou d'une fonction.
    Par convention, chaque module de code toto.py est associé à un module de tests unitaires test_toto.py, dans un sous-répertoire tests.
  • Les tests fonctionnels prennent, quant à eux, l'application comme une boîte noire, et la manipule comme le ferait l'utilisateur final.
    Ils sont plus difficiles à mettre en oeuvre. On n'en parlera pas ici.

Ces tests permettent un réel gain de temps au niveau du déboguage.

Modules de tests

Unittest

Le module unittest est une implémentation sous Python de JUnit, bibliothèque de tests unitaires sous Java, devenue une référence.

La classe TestCase

Ce module offre une classe de base TestCase, qui peut être utilisée pour concevoir des tests. Chaque méthode

  • implantée dans une classe dérivée de TestCase,
  • dont le nom commence par test

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):
          ...

Les méthodes de TestCase

La classe TestCase fournit, entre autres, les méthodes suivantes, permettant de lever des exceptions correspondant à l'echec de tests classiques :

assertEquals(x,y)
Lève une exception si $x \neq y$.
assertNotEquals(x,y)
Lève une exception si $x = y$.
assertRaises(Excp, fun, arg1, arg2, ...)
S'assure que la fonction fun lève bien une exception de type Excp quand on lui passe les arguments arg1, arg2,...
assert_(expr)
Vérifie que l'expression expr est vraie.
failIf(expr)
Vérifie que l'expression expr est fausse.
assertAlmostEquals(x, y, arrondis)
Vérifie que $x$ est égal à $y$, à l'arrondis près.
assertAlmostNotEquals(x, y, arrondis)
Vérifie que $x$ n'est pas égal à $y$, à l'arrondis près.
fail()
Produit une erreur, quoi qu'il arrive.

Dans tous les cas, on peut aussi passer un message en argument, afin d'expliciter les raisons pour lesquelles le test échoue.

Illustration

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.

Réalisation des tests

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

setUp() et tearDown()

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.

Vers une campagne de tests

Pour une campagne de tests, il reste à exécuter à la suite tous les modules de tests unitaires, à l'aide d'un script.

Doctest

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.

Page Actions

Recent Changes

Group & Page

Back Links