Bien qu'il ne constitue pas la seule alternative possible, Python vient avec un module de testing assez complet et pratique nommé unittest.
unittest
permet de définir des cas de tests, avec notamment un jeu d'assertions assez complet et pratique.
Un exemple vaut mieux que mille mots : voyons comment utiliser unittest
sur un cas simple.
Code
import unittest
def is_even(nbr):
"""
Cette fonction teste si un nombre est pair.
"""
return nbr % 2 == 0
class MyTest(unittest.TestCase):
def test_is_even(self):
self.assertTrue(is_even(2))
self.assertFalse(is_even(1))
self.assertEqual(is_even(0), True)
if __name__ == '__main__':
unittest.main()
Exécution
.
--------------------
Ran 1 test in 0.001s
OK
Comme le montre cet exemple, un test case (qui est en fait plutôt, au fond, un use case qui peut regrouper plusieurs test cases…) est une classe qui hérite de la classe TestCase
définie dans le module unittest
que nous avons importé pour l'occasion.
Une classe de test (TestCase
) est composée de méthodes définissant chacune un test. Chacune de ces méthodes définit à son tour différentes assertions, ou vérifications. Notre classe MyTest
comporte donc un « test », qui réalise 3 vérifications.
Bien entendu, en Python comme dans beaucoup d'autres langages, on définit généralement les classes de test dans des fichiers différents des classes métiers ou des scripts eux-mêmes. L'exemple présenté ci-dessus regroupe le code et les tests dans le même fichier pour des besoins de simplification.
Python possède un outil très appréciable, souple, pratique et agréable pour inclure des tests simples dans des méthodes ou fonctions sans avoir à définir de classes de test particulières. Cet outil standard s'appelle doctest.
Grâce à doctest
, le programmeur peut inclure des tests unitaires directement dans la documentation des fonctions et méthodes !
Ici encore, voyons comment cela fonctionne sur un exemple : reprenons notre exemple précédent en intégrant les tests directement dans la documentation de la fonction.
Code
def is_even(nbr):
"""
Cette fonction teste si un nombre est pair.
>>> is_even(2)
True
>>> is_even(1)
False
>>> is_even(0)
True
"""
return nbr % 2 == 0
if __name__ == '__main__':
import doctest
doctest.testmod()
Exécution
Et voilà ! Notre fonction simple est testée ! À l'exécution, rien n'est affiché : normal, il n'y a pas d'erreur… Introduisons une coquille pour voir ce qui se passera :
Code
def is_even(nbr):
"""
Cette fonction teste si un nombre est pair.
>>> is_even(2)
True
>>> is_even(1)
False
>>> is_even(0)
True
"""
return nbr % 2 == 1 # <= Ce code est buggé !
if __name__ == '__main__':
import doctest
doctest.testmod()
Exécution
**********************************
File "./petit_pepaire.py", line 7, in __main__.is_even
Failed example:
is_even(2)
Expected:
True
Got:
False
**********************************
File "./petit_pepaire.py", line 9, in __main__.is_even
Failed example:
is_even(1)
Expected:
False
Got:
True
**********************************
File "./petit_pepaire.py", line 11, in __main__.is_even
Failed example:
is_even(0)
Expected:
True
Got:
False
**********************************
1 items had failures:
3 of 3 in __main__.is_even
***Test Failed*** 3 failures.
Nous allons à présent montrer l'utilisation d'une classe de test pour implémenter des tests unitaires.
Soit une fonction Python permettant de nettoyer un texte pour le faire respecter un maximum les règles de typographie française :
Code applicatif
def clean_text(text):
"""
Nettoyage d'une pavé de texte pour respecter la typographie française.
"""
ret = re.sub(r'^(\s*)(.*?)(\s*)$', r'\2', text) # Suppression des espaces au début et à la fin du texte
ret = re.sub(r'\s*\!{2,}', u'!', ret)
ret = re.sub(r'\s*\?{2,}', u'?', ret)
ret = re.sub(r"\s*\,+", u',', ret)
ret = re.sub(r'\,([^\s])', r', \1', ret)
ret = re.sub(r'\s*\.{3,}', u'…', ret) # Remplacement de "(espace)...(x fois)" par "…"
ret = re.sub(r'\s\.', u'.', ret) # Remplacement de " ." par "."
ret = re.sub(r'\.{2}', u'…', ret) # Remplacement de ".." par "…"
ret = re.sub(r'([^0-9])\1{3,}', r'\1', ret) # Suppression des multiples répétitions de caractères (plus de 3) sauf pour les chiffres
ret = re.sub(r'([^\s])([?!:])', r'\1 \2', ret) # Remplacement de "blabla!" par "blabla !"
ret = re.sub(r'([?!])([^\s:\)])', r'\1 \2', ret) # Remplacement de "?blabla" par "? blabla"
return ret
Code de test
import unittest
class TestText(unittest.TestCase):
def setUp(self):
self.text_checks = (
(u'Un texte avec pluuuuuuusieurs', u'Un texte avec plusieurs'),
(u'Collage!', u'Collage !'),
(u'!Collage', u'! Collage'),
(u'Espaces en trop', u'Espaces en trop'),
(u"Trop d'exclamation!!!! ou d'interrogation ??", u"Trop d\'exclamation ! ou d\'interrogation ?"),
(u' Espaces debut ou fin ', u'Espaces debut ou fin'),
(u'Smileys :), ;):)', u'Smileys :), ;) :)'),
(u'(Une ponctuation avant une parenthese !)', u'(Une ponctuation avant une parenthese !)'),
(u'Quelques paquerettes . .', u'Quelques paquerettes…'),
(u'[color=red][Edit : merci de respecter les regles et de proposer des titres explicites.][/color]', u'[color=red][Edit : merci de respecter les regles et de proposer des titres explicites.][/color]'),
(u'60000 ans', u'60000 ans'),
(u'Une , virgule', u'Une, virgule'),
(u'Deux,, virgules', u'Deux, virgules'),
(u'Virgule,collée', u'Virgule, collée'),
)
def test_clean_text(self):
for check in self.text_checks:
self.assertEqual(clean_text(check[0]), check[1])
Est-ce que les tests unitaires sont plus importants que les tests d'intégration ?
Quel est l'intérêt des doctests en Python ?
Où peut-on définir des classes de test d'une classe C définie dans un fichier F en Python ?
À quoi sert la méthode setUp()
d'une classe de test case unittest Python ?
Soit le code suivant. Que se passe-t-il quand on exécute le script ?
importunittestclassGinette:defdire_bonjour(self,nom):return"Bonjour {nom}, je suis Ginette".format(nom=nom)classGinetteTest(unittest.TestCase):defsetUp(self):self.ginou=Ginette()deftest_bonjour_quelquun(self):self.assertEqual(self.ginou.dire_bonjour('Simone'),"Bonjour Berthe, je suis Ginette")if__name__=='__main__':unittest.main()