Écrire des tests unitaires professionnels en Python

Les tests constituent la base d'un développement logiciel solide. Il existe de nombreux types de tests, mais le type le plus important est le test unitaire. Les tests unitaires vous donnent la certitude que vous pouvez utiliser des éléments bien testés en tant que primitives et que vous les utilisez lorsque vous les composez pour créer votre programme. Ils augmentent votre inventaire de code de confiance au-delà des éléments intégrés et de la bibliothèque standard de votre langue. De plus, Python fournit un excellent support pour l'écriture de tests unitaires.

Exemple en cours d'exécution

Avant de plonger dans tous les principes, heuristiques et directives, voyons un test unitaire représentatif en action. le SelfDrivingCar class est une mise en œuvre partielle de la logique de conduite d’une voiture autonome. Il s’agit principalement de contrôler la vitesse de la voiture. Il est conscient des objets qui se trouvent devant lui, de la limitation de vitesse et de son arrivée ou non à destination.. 

classe SelfDrivingCar (objet): def __init __ (self): self.speed = 0 self.destination = None def _accelerate (self): self.speed + = 1 def _decelerate (self): si self.speed> 0: self.speed - = 1 def _advance_to_destination (self): distance = self._calculate_distance_to_object_in_front () si distance < 10: self.stop() elif distance < self.speed / 2: self._decelerate() elif self.speed < self._get_speed_limit(): self._accelerate() def _has_arrived(self): pass def _calculate_distance_to_object_in_front(self): pass def _get_speed_limit(self): pass def stop(self): self.speed = 0 def drive(self, destination): self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop() def __init__(self): self.speed = 0 self.destination = None def _accelerate(self): self.speed += 1 def _decelerate(self): if self.speed > 0: self.speed - = 1 def _advance_to_destination (self): distance = self._calculate_distance_to_object_in_front () si distance < 10: self.stop() elif distance < self.speed / 2: self._decelerate() elif self.speed < self._get_speed_limit(): self._accelerate() def _has_arrived(self): pass def _calculate_distance_to_object_in_front(self): pass def _get_speed_limit(self): pass def stop(self): self.speed = 0 def drive(self, destination): self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop() 

Voici un test unitaire pour le Arrêtez() méthode pour aiguiser votre appétit. Je vais entrer dans les détails plus tard. 

from unittest import TestCase classe SelfDrivingCarTest (TestCase): def setUp (self): self.car = SelfDrivingCar () def test_stop (self): self.car.speed = 5 self.car.stop () # Vérifiez que la vitesse est 0 après arrêt de self.assertEqual (0, self.car.speed) # Vérifiez que vous pouvez vous arrêter à nouveau si la voiture est déjà arrêtée. self.car.stop () self.assertEqual (0, self.car.speed)

Directives de test unitaire

Commettre

Écrire de bons tests unitaires est un travail difficile. Écrire des tests unitaires prend du temps. Lorsque vous apportez des modifications à votre code, vous devrez généralement modifier également vos tests. Parfois, votre code de test contient des bogues. Cela signifie que vous devez être vraiment engagé. Les avantages sont énormes, même pour de petits projets, mais ils ne sont pas gratuits.

Être discipliné

Vous devez être discipliné. Être cohérent. Assurez-vous que les tests réussissent toujours. Ne laissez pas les tests être cassés parce que vous "savez" que le code est correct.

Automatiser

Pour vous aider à être discipliné, vous devez automatiser vos tests unitaires. Les tests doivent s'exécuter automatiquement à des moments significatifs, tels que la pré-validation ou le pré-déploiement. Idéalement, votre système de gestion de contrôle de source rejette le code qui n'a pas passé tous les tests..

Le code non testé est cassé par définition

Si vous ne l'avez pas testé, vous ne pouvez pas dire que cela fonctionne. Cela signifie que vous devriez le considérer cassé. S'il s'agit d'un code critique, ne le déployez pas en production.

Contexte

Qu'est-ce qu'une unité??

Une unité aux fins des tests unitaires est un fichier / module contenant un ensemble de fonctions associées ou une classe. Si vous avez un fichier avec plusieurs classes, vous devez écrire un test unitaire pour chaque classe..

TDD ou ne pas TDD

Le développement piloté par les tests est une pratique dans laquelle vous écrivez les tests avant d'écrire le code. Cette approche présente plusieurs avantages, mais je vous recommande de l'éviter si vous avez la discipline nécessaire pour écrire les tests appropriés plus tard.. 

La raison est que je conçois avec du code. J'écris du code, je le regarde, je le réécris, je le regarde encore et je l'écris encore très rapidement. L'écriture de tests me limite et me ralentit. 

Une fois la conception initiale terminée, je rédigerai les tests immédiatement avant de les intégrer au reste du système. Cela dit, c’est un excellent moyen de vous présenter aux tests unitaires et d’assurer que tout votre code comportera des tests..

Le module Unittest

Le module unittest est livré avec la bibliothèque standard de Python. Il fournit une classe appelée Cas de test, dont vous pouvez tirer votre classe. Ensuite, vous pouvez remplacer un installer() méthode permettant de préparer un dispositif d’essai avant chaque essai et / ou une classSetUp () Méthode de classe pour préparer un montage de test pour tous les tests (non réinitialisé entre les tests individuels). Il y a des correspondants abattre() et classTearDown () méthodes que vous pouvez remplacer ainsi.

Voici les parties pertinentes de notre SelfDrivingCarTest classe. Je n'utilise que le installer() méthode. Je crée un frais SelfDrivingCar par exemple et le stocker dans auto.car il est donc disponible pour tous les tests.

from unittest import TestCase classe SelfDrivingCarTest (TestCase): définition de def (auto): self.car = SelfDrivingCar ()

La prochaine étape consiste à écrire des méthodes de test spécifiques pour tester ce code sous test-the SelfDrivingCar Dans ce cas, la classe fait ce qu’elle est censée faire. La structure d'une méthode de test est assez standard:

  • Préparer l'environnement (facultatif).
  • Préparer le résultat attendu.
  • Appeler le code à tester.
  • Affirmer que le résultat réel correspond au résultat attendu.

Notez que le résultat ne doit pas nécessairement être le résultat d'une méthode. Il peut s'agir d'un changement d'état d'une classe, d'un effet secondaire tel que l'ajout d'une nouvelle ligne dans une base de données, l'écriture d'un fichier ou l'envoi d'un courrier électronique..

Par exemple, le Arrêtez() méthode du SelfDrivingCar La classe ne renvoie rien, mais elle modifie l'état interne en définissant la vitesse sur 0. Le paramètre assertEqual () méthode fournie par le Cas de test la classe de base est utilisée ici pour vérifier que l'appel Arrêtez() travaillé comme prévu.

def test_stop (self): self.car.speed = 5 self.car.stop () # Vérifiez que la vitesse est 0 après l’arrêt de self.assertEqual (0, self.car.speed) # Vérifiez qu’il est correct de s’arrêter à nouveau si car est déjà arrêté self.car.stop () self.assertEqual (0, self.car.speed)

Il y a en fait deux tests ici. Le premier test consiste à s’assurer que si la vitesse de la voiture est 5 et Arrêtez() est appelé, la vitesse devient 0. Ensuite, un autre test consiste à s’assurer que tout se passe bien si vous appelez Arrêtez() à nouveau quand la voiture est déjà arrêtée.

Plus tard, je présenterai plusieurs autres tests pour des fonctionnalités supplémentaires.

Le module Doctest

Le module doctest est très intéressant. Il vous permet d’utiliser des exemples de code interactif dans votre docstring et de vérifier les résultats, y compris les exceptions déclenchées.. 

Je n'utilise ni ne recommande doctest pour les systèmes à grande échelle. Des tests unitaires corrects demandent beaucoup de travail. Le code de test est généralement beaucoup plus volumineux que le code testé. Les docstrings ne sont tout simplement pas le bon support pour écrire des tests complets. Ils sont cool, cependant. Voici ce qu'un factoriel la fonction avec les tests doc ressemble à ceci:

import math def factorial (n): "" "Renvoie la factorielle de n, un entier exact> = 0. Si le résultat est suffisamment petit pour tenir dans un int, retourne un int. Sinon retourne un long. >>> [factorial (n) pour n dans la plage (6)] [1, 1, 2, 6, 24, 120] >>> [factorielle (long (n)) pour n dans la plage (6)] [1, 1, 2, 6, 24, 120] >>> factorielle (30) 265252859812191058636308480000000L >>> factorielle (30L) 265252859812191058630304848000000L >>> factorielle (-1) Traceback (appel le plus récent):… ValueError: n doit être> = 0 Factorials of floats sont corrects, mais le float doit être un entier exact: >>> factoriel (30.1) Traceback (dernier appel passé):… ValueError: n doit être un entier exact >>> factorial (30.0) 265252859812191058636308480000000L Il ne doit pas non plus être ridiculement grand : >>> factorial (1e100) Traceback (dernier appel passé):… OverflowError: n trop grand "" "sinon n> = 0: soulève ValueError (" n doit être> = 0 ") si math.floor (n )! = n: augmenter ValueError ("n doit être un entier exact") si n + 1 == n: # attraper une valeur telle que 1e300 augmenter OverflowE rror ("n too large") résultat = 1 facteur = 2 alors que facteur <= n: result *= factor factor += 1 return result if __name__ == "__main__": import doctest doctest.testmod()

Comme vous pouvez le constater, la docstring est beaucoup plus grosse que le code de la fonction. Cela ne favorise pas la lisibilité.

Tests en cours

D'ACCORD. Vous avez écrit vos tests unitaires. Pour un système de grande taille, vous aurez des dizaines, des centaines, des milliers de modules et de classes dans plusieurs répertoires. Comment faites-vous tous ces tests?

Le module unittest fournit diverses fonctionnalités pour regrouper des tests et les exécuter par programme. Découvrez les tests de chargement et d'exécution. Mais le moyen le plus simple est la découverte de test. Cette option a été ajoutée uniquement dans Python 2.7. Avant la version 2.7, vous pourriez utiliser le nez pour découvrir et exécuter des tests. Nose présente quelques autres avantages, tels que l'exécution de fonctions de test sans avoir à créer de classe pour vos scénarios de test. Mais pour les besoins de cet article, restons-en avec unittest.

Pour découvrir et exécuter vos tests basés sur unittest, tapez simplement sur la ligne de commande:

python -m unittest découvrir

unittest analysera tous les fichiers et sous-répertoires, exécutera tous les tests trouvés et fournira un bon rapport ainsi que le temps d'exécution. Si vous voulez voir quels tests il est en cours d'exécution, vous pouvez ajouter l'indicateur -v:

python -m unittest découvrir -v

Il y a plusieurs drapeaux qui contrôlent l'opération:

python -m unittest -h Utilisation: python -m unittest [options] [tests] Options: -h, --help Afficher ce message -v, --verbose Sortie verbeuse -q, --quiet Sortie minimale -f, - failfast Arrête la première défaillance -c, --catch Contrôle le contrôle-C et affiche les résultats -b, --buffer Buffer stdout et stderr pendant l'exécution du test Exemples: python -m unittest test_module - exécute des tests à partir de test_module python -m unittest module.TestClass - exécuter des tests à partir de module.TestClass python -m unittest module.Class.test_method - exécuter une méthode de test spécifiée [tests] peut être une liste d'un nombre quelconque de modules de test, de classes et de méthodes de test. Utilisation alternative: python -m unittest discover [options] Options: -v, --verbose Sortie détaillée -f, --failfast Arrêt lors du premier échec -c, --catch Contrôle le contrôle-C et affichage des résultats -b, --buffer Tampon stdout et stderr pendant le test exécute le répertoire -s Répertoire de démarrage de la découverte ('.' Par défaut) -p modèle Modèle correspondant aux fichiers de test ('test * .py' par défaut) -t répertoire Répertoire supérieur du projet (répertoire de démarrage par défaut) ) Pour la découverte de test, tous les modules de test doivent pouvoir être importés à partir du répertoire de niveau supérieur du projet..

Couverture de test

La couverture de test est un domaine souvent négligé. La couverture signifie combien de votre code est réellement testé par vos tests. Par exemple, si vous avez une fonction avec un sinon déclaration et vous testez seulement la si branche, alors vous ne savez pas si le autre branche fonctionne ou pas. Dans l'exemple de code suivant, la fonction ajouter() vérifie le type de ses arguments. Si les deux sont des entiers, il les ajoute. 

Si les deux sont des chaînes, il essaie de les convertir en entiers et les ajoute. Sinon, il soulève une exception. le test_add () fonction teste le ajouter() function avec des arguments qui sont à la fois des entiers et des arguments qui flottent et vérifient le comportement correct dans chaque cas. Mais la couverture de test est incomplète. Le cas des arguments de chaîne n'a pas été testé. En conséquence, le test réussit, mais la typo de la branche où les arguments sont les deux chaînes n'a pas été découverte (voyez-vous le 'intg' là-bas?).

import unittest def add (a, b): "" "Cette fonction ajoute deux nombres a, b et renvoie leur somme a et b peuvent être des nombres entiers" "" si isinstance (a, int) et isinstance (b, int): retourne a + b elseif isinstance (a, str) et isinstance (b, str): renvoyer int (a) + intg (b) else: raise Exception ('arguments non valides') class test (unittest.TestCase): def test_add (self) : self.assertEqual (5, add (2, 3)) self.assertEqual (15, add (-6, 21)) self.assertRaises (Exception, add, 4.0, 5.0) unittest.main () 

Voici la sortie:

---------------------------------------------------------------------- A couru 1 test en 0.000s OK Processus terminé avec le code de sortie 0

Tests unitaires pratiques

Écrire des tests unitaires de niveau industriel n’est ni facile ni simple. Il y a plusieurs choses à considérer et des compromis à faire.

Conception pour la testabilité

Si votre code est ce que l'on appelle formellement un code spaghetti ou une grosse boule de boue où différents niveaux d'abstraction sont mélangés et où chaque élément de code dépend de chaque autre élément de code, vous aurez du mal à le tester. En outre, chaque fois que vous modifiez quelque chose, vous devrez également mettre à jour plusieurs tests..

La bonne nouvelle est que la conception logicielle appropriée à usage général est exactement ce dont vous avez besoin pour la testabilité. En particulier, un code modulaire bien factorisé, dans lequel chaque composant a une responsabilité claire et interagit avec d'autres composants via des interfaces bien définies, fera de l'écriture de bons tests unitaires un plaisir.

Par exemple, notre SelfDrivingCar La classe est responsable du fonctionnement de haut niveau de la voiture: allez, arrêtez, naviguez. Il a un Calculate_distance_to_object_in_front () méthode qui n'a pas encore été implémentée. Cette fonctionnalité devrait probablement être implémentée par un sous-système totalement séparé. Cela peut comprendre la lecture de données provenant de divers capteurs, l’interaction avec d’autres voitures autonomes, une pile de vision industrielle complète pour analyser des images provenant de plusieurs caméras..

Voyons comment cela fonctionne dans la pratique. le SelfDrivingCar acceptera un argument appelé objet_detector qui a une méthode appelée Calculate_distance_to_object_in_front (), et il déléguera cette fonctionnalité à cet objet. Maintenant, il n’est pas nécessaire de faire des tests unitaires car le objet_detector est responsable (et devrait être testé) pour cela. Vous voulez toujours tester le fait que vous utilisez le objet_detector correctement.

class SelfDrivingCar (objet): def __init __ (self, objet_detector): self.object_detector self.speed = 0 self.destination = Aucun def _calculate_distance_to_object_in_front (self): retourne self.object_detector.calculate_distance_to_object_in_front ()

Coût / bénéfice

La quantité d'effort que vous mettez dans les tests doit être corrélée au coût de l'échec, à la stabilité du code et à la facilité de résolution des problèmes détectés ultérieurement..

Par exemple, notre classe de voitures autonomes est extrêmement critique. Si la Arrêtez() Cette méthode ne fonctionne pas correctement, notre voiture autonome pourrait tuer des gens, détruire des biens et faire dérailler tout le marché des voitures autonomes. Si vous développez une voiture autonome, je soupçonne que vos tests unitaires pour le Arrêtez() la méthode sera un peu plus rigoureuse que la mienne. 

Par contre, si un seul bouton de votre application Web sur une page enfouie trois niveaux au-dessous de votre page principale scintille légèrement lorsque quelqu'un le clique dessus, vous pouvez le réparer, mais vous n'ajouterez probablement pas de test unitaire dédié à ce cas. La situation économique ne le justifie pas. 

Tester l'état d'esprit

Tester l'état d'esprit est important. Un principe que j'utilise est que chaque morceau de code a au moins deux utilisateurs: l'autre code qui l'utilise et le test qui le teste. Cette règle simple aide beaucoup avec la conception et les dépendances. Si vous vous souvenez que vous devez écrire un test pour votre code, vous n'ajouterez pas beaucoup de dépendances difficiles à reconstruire lors des tests..

Par exemple, supposons que votre code ait besoin de calculer quelque chose. Pour ce faire, il doit charger certaines données d'une base de données, lire un fichier de configuration et consulter dynamiquement une API REST pour obtenir des informations à jour. Tout cela peut être nécessaire pour diverses raisons, mais le faire en une seule fonction rendra le test unitaire assez difficile. Il est toujours possible de se moquer, mais il est bien mieux de structurer votre code correctement.

Fonctions pures

Le code le plus facile à tester est celui des fonctions pures. Les fonctions pures sont des fonctions qui n’accèdent qu’aux valeurs de leurs paramètres, n’ont aucun effet secondaire et renvoient le même résultat chaque fois qu’elles sont appelées avec les mêmes arguments. Ils ne changent pas l'état de votre programme, n'accèdent pas au système de fichiers ni au réseau. Leurs avantages sont trop nombreux pour compter ici. 

Pourquoi sont-ils faciles à tester? Parce qu'il n'est pas nécessaire de définir un environnement spécial à tester. Vous venez de passer des arguments et de tester le résultat. Vous savez également que tant que le code testé ne change pas, votre test n'a pas à changer.. 

Comparez-le à une fonction qui lit un fichier de configuration XML. Votre test devra créer un fichier XML et transmettre son nom de fichier au code testé. Pas grave. Mais supposons que quelqu'un ait décidé que XML est abominable et que tous les fichiers de configuration doivent être au format JSON. Ils exercent leurs activités et convertissent tous les fichiers de configuration au format JSON. Ils exécutent tous les tests, y compris vos tests et ils tout passer! 

Pourquoi? Parce que le code n'a pas changé. Il attend toujours un fichier de configuration XML et votre test construit toujours un fichier XML pour celui-ci. Mais en production, votre code obtiendra un fichier JSON qu’il ne pourra pas analyser..

Tester le traitement des erreurs

La gestion des erreurs est une autre chose essentielle à tester. Cela fait également partie du design. Qui est responsable de l'exactitude des entrées? Chaque fonction et méthode devraient être claires à ce sujet. Si cela relève de la responsabilité de la fonction, celle-ci devrait vérifier ses entrées, mais si cela appartient à l'appelant, la fonction peut tout simplement s'occuper de ses affaires et supposer que les entrées sont correctes. L'exactitude globale du système sera assurée par des tests permettant à l'appelant de vérifier qu'il ne fait que saisir correctement l'entrée de votre fonction..

En règle générale, vous souhaitez vérifier les entrées de l'interface publique dans votre code, car vous ne savez pas nécessairement qui va appeler votre code. Regardons le conduire() méthode de la voiture autonome. Cette méthode attend un paramètre 'destination'. Le paramètre 'destination' sera utilisé ultérieurement dans la navigation, mais la méthode de conduite ne permet pas de vérifier son exactitude.. 

Supposons que la destination est supposée être un tuple de latitude et de longitude. Toutes sortes de tests peuvent être effectués pour vérifier sa validité (par exemple, si la destination se trouve au milieu de la mer). Aux fins des présentes, assurons-nous simplement qu'il s'agit d'un tuple de flotteurs compris entre 0.0 et 90.0 pour la latitude et entre -180.0 et 180.0 pour la longitude..

Voici la mise à jour SelfDrivingCar classe. J'ai implémenté trivialement certaines des méthodes non implémentées parce que le conduire() méthode appelle certaines de ces méthodes directement ou indirectement.

class SelfDrivingCar (objet): def __init __ (self, object_detector): self.object_detector = object_detector self.speed = 0 self.destination = None def _accelerate (self): self.speed + = 1 def _decelerate (self): si self. vitesse> 0: self.speed - = 1 def _advance_to_destination (self): distance = self._calculate_distance_to_object_in_front () si distance < 10: self.stop() elif distance < self.speed / 2: self._decelerate() elif self.speed < self._get_speed_limit(): self._accelerate() def _has_arrived(self): return True def _calculate_distance_to_object_in_front(self): return self.object_detector.calculate_distance_to_object_in_front() def _get_speed_limit(self): return 65 def stop(self): self.speed = 0 def drive(self, destination): self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop()

Pour tester la gestion des erreurs dans le test, je vais passer des arguments non valides et vérifier qu'ils sont correctement rejetés. Vous pouvez le faire en utilisant le self.assertRaises () méthode de unittest.TestCase. Cette méthode réussit si le code sous test lève effectivement une exception.

Voyons cela en action. le test_drive () la méthode passe la latitude et la longitude en dehors de la plage valide et attend le conduire() méthode pour lever une exception.

from unittest import TestCase from self_driving_car import SelfDrivingCar classe MockObjectDetector (objet): def calcul_distance_to_object_in_front (self): retourne la classe 20 SelfDrivingCarTest (TestCase): def setUp (self): self.car = SelfDrivingCar (MockObjectDeProd)) self.car.speed = 5 self.car.stop () # Vérifiez que la vitesse est 0 après avoir arrêté self.assertEqual (0, self.car.speed) # Vérifiez qu'il est correct de vous arrêter si la voiture est déjà arrêtée. car.stop () self.assertEqual (0, self.car.speed) def test_drive (self): # Destination valide self.car.drive ((55.0, 66.0)) # Destination non valide plage incorrecte. self.assertRaises (Exception, self .car.drive, (-55.0, 200.0))

Le test échoue car le conduire() La méthode ne vérifie pas la validité de ses arguments et ne génère pas d'exception. Vous obtenez un bon rapport avec des informations complètes sur ce qui a échoué, où et pourquoi.

python -m unittest discover -v test_drive (untitled.test_self_driving_car.SelfDrivingCarTest)… FAIL test_stop (untitled.test_self_driving_car.SelfDrivingCarTest)… ok ======================= ============================================ FAIL: test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) ------------------------------------------- --------------------------- Traceback (l'appel le plus récent en dernier): Fichier "/Users/gigi/PycharmProjects/untitled/test_self_driving_car.py" , ligne 29, dans test_drive self.assertRaises (Exception, self.car.drive, (-55.0, 200.0)) AssertionError: exception non déclenchée -------------------- -------------------------------------------------- A couru 2 tests en 0.000s ÉCHEC (échecs = 1)

Pour résoudre ce problème, mettons à jour le conduire() méthode pour vérifier réellement la plage de ses arguments:

def drive (auto, destination): lat, lon = destination sinon (0.0 <= lat <= 90.0): raise Exception('Latitude out of range') if not (-180.0 <= lon <= 180.0): raise Exception('Latitude out of range') self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop()

Maintenant, tous les tests réussissent.

python -m unittest discover -v test_drive (untitled.test_self_driving_car.SelfDrivingCarTest)… ok test_stop (untitled.test_self_driving_car.SelfDrivingCarTest)… ok ------------------------- ----------------------------------------------- A couru 2 tests dans 0.000s OK 

Test de méthodes privées

Devez-vous tester chaque fonction et méthode? En particulier, devriez-vous tester des méthodes privées appelées uniquement par votre code? La réponse généralement insatisfaisante est: "Cela dépend". 

Je vais essayer d'être utile ici et de vous dire ce que cela dépend. Vous savez exactement qui appelle votre méthode privée - c'est votre propre code. Si vos tests pour les méthodes publiques appelant votre méthode privée sont complets, vous devez déjà tester vos méthodes privées de manière exhaustive. Mais si une méthode privée est très compliquée, vous pouvez l’essayer indépendamment. Utilisez votre jugement.

Comment organiser vos tests unitaires

Dans un grand système, la manière d'organiser vos tests n'est pas toujours claire. Devez-vous avoir un gros fichier avec tous les tests pour un paquet, ou un fichier de test pour chaque classe? Les tests doivent-ils être dans le même fichier que le code testé ou dans le même répertoire??

Voici le système que j'utilise. Les tests doivent être totalement séparés du code sous test (par conséquent, je n'utilise pas doctest). Idéalement, votre code devrait être dans un package. Les tests de chaque package doivent figurer dans un répertoire frère de votre package. Dans le répertoire tests, il devrait y avoir un fichier pour chaque module de votre paquet nommé tester_

Par exemple, si vous avez trois modules dans votre package: module_1.py, module_2.py et module_3.py, vous devriez avoir trois fichiers de test: test_module_1.py, test_module_2.py et test_module_3.py sous le répertoire des tests. 

Cette convention présente plusieurs avantages. Il suffit de parcourir les répertoires pour bien comprendre que vous n’avez pas oublié de tester complètement certains modules. Cela aide également à organiser les tests en morceaux de taille raisonnable. En supposant que vos modules soient raisonnablement dimensionnés, le code de test de chaque module sera dans son propre fichier, qui peut être un peu plus gros que le module testé, mais qui peut quand même convenir parfaitement. 

Conclusion

Les tests unitaires sont la base d'un code solide. Dans ce tutoriel, j'ai exploré quelques principes et directives pour les tests unitaires et expliqué le raisonnement sous-jacent à plusieurs meilleures pratiques. Plus le système que vous construisez est grand, plus les tests unitaires deviennent importants. Mais les tests unitaires ne suffisent pas. D'autres types de tests sont également nécessaires pour les systèmes à grande échelle: tests d'intégration, tests de performance, tests de charge, tests de pénétration, tests de réception, etc..