🚧
Aucun test n’est parfait, mais cela permet quand même d’écarter de nombreuses erreurs.
Des logiciels permettent de calculer la couverture de tests d’une application, c’est-à-dire approximativement le nombre de fonctions testées sur le nombre total de fonctions.
Cette couverture de tests est un indicateur de qualité. Cependant, elle donne une tendance, plutôt qu’une valeur fiable. En effet, il est facile de tester toutes les méthodes élémentaires pour augmenter mécaniquement sa couverture et de mettre de côté les tests sur les méthodes plus compliquées.
Préférez faire peu de tests, mais des tests utiles !
Il existe de très nombreux tests différents, voici les principaux :
Nous allons utiliser le framework unittest
pour réaliser nos tests en Python.
Commençons par un exemple : vous souhaitez tester la méthode suivante :
class OperationMathematiques:
def diviser_cinq_par(self, nombre) -> float:
if nombre != 0:
return 5 / nombre
else:
return None
Pour cela, vous allez créer une classe de test.
Le premier test auquel on pense est le cas nominal : on choisit un nombre en entrée, par exemple 2, puis on vérifie que diviser_cinq_par(2) retourne bien la valeur attendue : 2.5
from unittest import TestCase, TextTestRunner, TestLoader
from mathematiques.operation_mathematique import OperationMathematiques
class TestOperationMathematiques(TestCase):
def test_diviser_cinq_par_nombre_non_nul(self):
## GIVEN
nombre = 2
## WHEN
resultat = OperationMathematiques().diviser_cinq_par(nombre)
## THEN
self.assertEqual(resultat, 2.5)
Mais ce n’est pas suffisant !
La méthode a également un autre retour possible : None
➡️ il faut aussi tester que la méthode renvoie bien None si le paramètre en entrée est 0
from unittest import TestCase, TextTestRunner, TestLoader
from mathematiques.operation_mathematique import OperationMathematiques
class TestOperationMathematiques(TestCase):
def test_diviser_cinq_par_nombre_non_nul(self):
## GIVEN
nombre = 2
## WHEN
resultat = OperationMathematiques().diviser_cinq_par(nombre)
## THEN
self.assertEqual(resultat, 2.5)
def test_diviser_cinq_par_zero(self):
## GIVEN
nombre = 0
## WHEN
resultat = OperationMathematiques().diviser_cinq_par(nombre)
## THEN
self.assertIsNone(resultat)
if __name__ == "__main__":
## Run the tests
result = TextTestRunner(verbosity=2).run(
TestLoader().loadTestsFromTestCase(TestOperationMathematiques)
)
Voici le test complet avec à la fin un main optionnel qui permet de lancer la classe de test comme n’importe quel programme Python avec le bouton ▶️
Et si… on appelle la méthode avec ce paramètre : diviser_cinq_par("a")
?
Vous pouvez aussi écrire un test pour vérifier que votre méthode renvoie bien une exception TypeError
dans ce cas.
def test_diviser_cinq_string(self):
## GIVEN
nombre = "a"
## WHEN / THEN
with self.assertRaises(TypeError):
OperationMathematiques().diviser_cinq_par(nombre)
Mais il est quand même préférable de vérifier dans votre méthode que le paramètre est bien de type numérique et de décider quoi faire si ce n’est pas le cas.
💡 Ce qu’il faut retenir sur le test unitaire :
mocker
le comportement des autres méthodesQuand tester ?
…
La meilleure pratique lorsque l’on veut créer une fonction est de d’abord créer les tests qui permettront de vérifier que la fonction fait ce qu’elle doit faire.
Cela peut paraître un peu étrange, mais en fait pas tant que ça. Lorsque vous codez une fonction, vous savez avant de commencer :
La pratique du TDD a le gros avantage que cela nous force à écrire des tests et de prendre le temps de bien faire les choses. Pour adhérer au TDD il faut vraiment se faire violence au début, mais au final cette pratique est très bénéfique.
Sinon, si l’on écrit la fonction en premier, une fois que l’on a terminé, il y a 9 chances sur 10 que l’on se dise : “c’est bon ça marche, pas la peine de tester…”. Et ça c’est pas bien !!!