Bonne journée à tous!
De notre table à la vôtre ... Autrement dit, de notre cours de développeur Python, malgré la nouvelle année qui approche à grands pas, nous avons préparé pour vous une traduction intéressante sur les différentes méthodes de test en Python.
Ce guide est destiné à ceux qui ont déjà écrit une application Python sympa mais qui n'ont pas encore écrit pour
les tests.
Les tests en Python sont un sujet étendu avec un tas de subtilités, mais il n'est pas nécessaire de compliquer les choses. En quelques étapes simples, vous pouvez créer des tests simples pour l'application, augmentant progressivement la complexité basée sur eux.
Dans ce guide, vous apprendrez comment créer un test de base, l'exécuter et trouver tous les bugs avant que les utilisateurs ne le fassent! Vous découvrirez les outils disponibles pour écrire et exécuter des tests, vérifier les performances des applications et même étudier les problèmes de sécurité.
Test de codeVous pouvez tester le code de plusieurs manières. Dans ce guide, vous découvrirez les méthodes des plus simples aux plus avancées.
Automatisé vs Test manuel
Bonne nouvelle! Vous avez probablement déjà fait le test, mais vous ne l'avez pas encore réalisé. Rappelez-vous comment vous avez commencé l'application et l'avez utilisée? Avez-vous testé les fonctions et les avez-vous expérimentées? Ce processus est appelé test exploratoire et c'est une forme de test manuel.
Tests de recherche - tests effectués sans plan. Lors des tests de recherche, vous recherchez l'application.
Pour créer une liste complète de tests manuels, il suffit de faire une liste de toutes les fonctions de l'application, des différents types d'entrées qu'elle accepte et des résultats attendus. Maintenant, chaque fois que vous modifiez quelque chose dans le code, vous devez revérifier chacun des éléments de cette liste.
Cela semble sombre, non?
Par conséquent, des tests automatiques sont nécessaires. Test automatique - l'exécution du plan de test (parties de l'application qui nécessitent des tests, l'ordre des tests et les résultats attendus) à l'aide d'un script, et non par des mains humaines. Python dispose déjà d'un ensemble d'outils et de bibliothèques pour vous aider à créer des tests automatisés pour votre application. Regardons ces outils et bibliothèques dans notre tutoriel.
Tests unitaires VS. Tests d'intégrationLe monde des tests est plein de termes, et maintenant, connaissant la différence entre les tests manuels et automatisés, nous allons descendre d'un niveau plus profond.
Pensez à comment tester les phares d'une voiture? Vous allumez les phares (appelons cela l'étape de test), sortez de la voiture vous-même ou demandez à un ami de vérifier que les phares sont allumés (et c'est une proposition de test). Le test de plusieurs composants est appelé test d'intégration.
Pensez à toutes les choses qui devraient fonctionner correctement pour qu'une tâche simple produise le résultat correct. Ces composants sont similaires à des parties de votre application: toutes ces classes, fonctions, modules que vous avez écrits.
La principale difficulté des tests d'intégration survient lorsque le test d'intégration ne donne pas le résultat correct. Il est difficile d'évaluer le problème, ne pouvant isoler la partie cassée du système. Si les phares ne sont pas allumés, les ampoules peuvent être cassées. Ou peut-être que la batterie est faible? Ou peut-être que le problème vient du générateur? Ou même un crash dans l'ordinateur de la machine?
Les voitures modernes elles-mêmes vous informeront d'une ampoule cassée. Ceci est déterminé à l'aide d'un test unitaire.
Le test unitaire (test unitaire) est un petit test qui vérifie le bon fonctionnement d'un composant individuel. Le test unitaire permet d'isoler la panne et de la réparer plus rapidement.
Nous avons parlé de deux types de tests:
- Un test d'intégration qui vérifie les composants du système et leur interaction les uns avec les autres;
- Un test unitaire qui teste un seul composant d'une application.
- Vous pouvez créer les deux tests en Python. Pour écrire un test pour la fonction sum () intégrée, vous devez comparer la sortie de sum () avec des valeurs connues.
Par exemple, vous pouvez ainsi vérifier que la somme des nombres (1, 2, 3) est 6:
>>> assert sum([1, 2, 3]) == 6, "Should be 6"
Les valeurs sont correctes, donc rien ne sera émis vers REPL. Si le résultat de
sum()
incorrect, une
AssertionError
sera
AssertionError
avec le message «devrait être 6». Vérifiez à nouveau l'instruction, mais maintenant avec des valeurs non valides pour obtenir une
AssertionError
:
>>> assert sum([1, 1, 1]) == 6, "Should be 6" Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError: Should be 6
Dans REPL, vous verrez
AssertionError
puisque la valeur
sum()
n'est pas 6.
Au lieu de REPL, placez-le dans un nouveau fichier Python appelé
test_sum.py
et exécutez-le à nouveau:
def test_sum(): assert sum([1, 2, 3]) == 6, "Should be 6" if __name__ == "__main__": test_sum() print("Everything passed")
Vous disposez maintenant d'un scénario de test écrit (scénario de test), d'une instruction et d'un point d'entrée (ligne de commande). Maintenant, cela peut être fait sur la ligne de commande:
$ python test_sum.py Everything passed
Vous voyez le résultat réussi, "Tout est passé".
sum()
en Python accepte tout itérable comme premier argument. Vous avez vérifié la liste. Essayons de tester le tuple. Créez un nouveau fichier appelé
test_sum_2.py
avec le code suivant:
def test_sum(): assert sum([1, 2, 3]) == 6, "Should be 6" def test_sum_tuple(): assert sum((1, 2, 2)) == 6, "Should be 6" if __name__ == "__main__": test_sum() test_sum_tuple() print("Everything passed")
test_sum_2.py
, le script
test_sum_2.py
erreur, car s
um() (1, 2, 2)
doit ĂŞtre
test_sum_2.py
5, et non 6. Par conséquent, le script affiche un message d'erreur, une ligne de code et un retraçage:
$ python test_sum_2.py Traceback (most recent call last): File "test_sum_2.py", line 9, in <module> test_sum_tuple() File "test_sum_2.py", line 5, in test_sum_tuple assert sum((1, 2, 2)) == 6, "Should be 6" AssertionError: Should be 6
Vous pouvez voir comment une erreur dans le code provoque une erreur dans la console avec des informations sur son emplacement et le résultat attendu.
De tels tests conviennent à une vérification simple, mais qu'en est-il s'il y a plus d'erreurs que dans un seul? Les coureurs d'essai viennent à la rescousse. Test Executor est une application spéciale conçue pour effectuer des tests, vérifier les données de sortie et fournir des outils de débogage et de diagnostic des tests et des applications.
Choix d'un exécuteur de testIl existe de nombreux exécuteurs de test disponibles pour Python. Par exemple, unittest est intégré à la bibliothèque standard Python. Dans ce guide, nous utiliserons des cas de test et des exécuteurs de test les plus simples. Les principes de fonctionnement les plus simples sont facilement adaptables à d'autres cadres. Nous listons les exécuteurs de test les plus populaires:
- unittest;
- nez ou nez2;
- pytest.
Il est important de choisir un entrepreneur d'essai qui répond à vos exigences et à votre expérience.
unittestunittest est intégré à la bibliothèque standard Python depuis la version 2.1. Vous le rencontrerez probablement dans des applications commerciales Python et des projets open source.
Unittest a un framework de test et un lanceur de test. Lors de l'écriture et de l'exécution de tests, vous devez respecter certaines exigences importantes.
unittest nécessite:
- Mettez les tests dans les classes comme méthodes;
- Utilisez des méthodes d'approbation spéciales. La classe TestCase au lieu de l'expression assert intégrée habituelle.
Pour transformer un exemple précédemment écrit en scénario de test le plus simple, vous devez:
- Importez unittest depuis la bibliothèque standard;
- Créez une classe appelée
TestSum
qui héritera de la classe TestCase
; - Convertissez les fonctions de test en méthodes en ajoutant
self
comme premier argument; - Modifiez les instructions en ajoutant l'utilisation de la méthode
self.assertEqual()
dans la classe TestCase
; - Modifiez le point d'entrée sur la ligne de commande pour appeler
unittest.main()
.
En suivant ces étapes, créez un nouveau fichier test_sum_unittest.py avec ce code:
import unittest class TestSum(unittest.TestCase): def test_sum(self): self.assertEqual(sum([1, 2, 3]), 6, "Should be 6") def test_sum_tuple(self): self.assertEqual(sum((1, 2, 2)), 6, "Should be 6") if __name__ == '__main__': unittest.main()
En faisant cela sur la ligne de commande, vous obtiendrez un succès (indiqué par.) Et un échec (indiqué par F):
$ python test_sum_unittest.py .F ====================================================================== FAIL: test_sum_tuple (__main__.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_sum_unittest.py", line 9, in test_sum_tuple self.assertEqual(sum((1, 2, 2)), 6, "Should be 6") AssertionError: Should be 6 ---------------------------------------------------------------------- Ran 2 tests in 0.001s FAILED (failures=1)
Ainsi, vous avez effectué deux tests à l'aide du lanceur de test le plus complet.
Remarque: Si vous écrivez des cas de test pour Python 2 et 3, soyez prudent. Dans les versions de Python 2.7 et inférieures, unittest est appelé unittest 2. Lorsque vous importez depuis unittest, vous obtiendrez différentes versions avec différentes fonctions dans Python 2 et Python 3.Pour en savoir plus sur unittest, lisez la
documentation unittest .
nezAu fil du temps, après avoir écrit des centaines voire des milliers de tests pour une application, il devient de plus en plus difficile de comprendre et d'utiliser les données de sortie les plus instables.
nose est compatible avec tous les tests écrits avec le framework le plus simple, et peut remplacer son exécuteur de test. Le développement de nose, en tant qu'application open source, a commencé à ralentir, et nose2 a été créé. Si vous partez de zéro, il est recommandé d'utiliser nose2.
Pour commencer avec nose2, vous devez l'installer à partir de PyPl et l'exécuter sur la ligne de commande. nose2 essaiera de trouver tous les scripts de
test*.py
avec
test*.py
dans le nom et tous les cas de test hérités de unittest.TestCase dans votre répertoire actuel:
$ pip install nose2 $ python -m nose2 .F ====================================================================== FAIL: test_sum_tuple (__main__.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_sum_unittest.py", line 9, in test_sum_tuple self.assertEqual(sum((1, 2, 2)), 6, "Should be 6") AssertionError: Should be 6 ---------------------------------------------------------------------- Ran 2 tests in 0.001s FAILED (failures=1)
C'est ainsi que le test créé dans
test_sum_unittest.py
partir du lanceur de test nose2. nose2 fournit de nombreux drapeaux de ligne de commande pour filtrer les tests exécutables. Pour plus d'informations, consultez la
documentation Nose 2 .
pytestpytest prend en charge les cas de test les plus simples. Mais le véritable avantage de pytest réside dans ses cas de test. Les cas de test pytest sont une série de fonctions dans un fichier Python avec test_ au début du nom.
Il contient d'autres fonctionnalités utiles:
- Prise en charge des expressions d'assertion intégrées au lieu d'utiliser des méthodes spéciales self.assert * ();
- Prise en charge du filtrage des cas de test;
- La possibilité de redémarrer à partir du dernier test ayant échoué;
- Un écosystème de centaines de plugins qui étendent les fonctionnalités.
Un exemple de scénario de test TestSum pour pytest ressemblera à ceci:
def test_sum(): assert sum([1, 2, 3]) == 6, "Should be 6" def test_sum_tuple(): assert sum((1, 2, 2)) == 6, "Should be 6"
Vous vous êtes débarrassé de TestCase, en utilisant des classes et des points d'entrée de ligne de commande.
Plus d'informations peuvent être trouvées sur le
site de documentation Pytest .
Écrire le premier testCombinez tout ce que nous avons déjà appris, et au lieu de la fonction
sum()
intégrée, nous testons une implémentation simple avec les mêmes exigences.
Créez un nouveau dossier pour le projet, à l'intérieur duquel créez un nouveau dossier appelé my_sum. Dans my_sum, créez un fichier vide appelé
_init_.py
. La présence de ce fichier signifie que le dossier my_sum peut être importé en tant que module à partir du répertoire parent.
La structure des dossiers ressemblera Ă ceci:
project/
│
└── my_sum/
└── __init__.py
Ouvrez
my_sum/__init__.py
et créez une nouvelle fonction appelée
sum()
, qui prend les entrées
my_sum/__init__.py
(liste, tuple, set) et ajoute les valeurs.
def sum(arg): total = 0 for val in arg: total += val return total
Cet exemple crée une variable appelée
total
, itère sur toutes les valeurs de
arg
et ajoute au
total
. Ensuite, à la fin de l'itération, le résultat est renvoyé.
Où écrire un testVous pouvez commencer à écrire un test en créant un fichier
test.py
qui contiendra votre premier
test.py
test. Pour les tests, le fichier devrait pouvoir importer votre application, alors mettez
test.py
dans le dossier au-dessus du package. L'arborescence des répertoires ressemblera à ceci:
project/
│
├── my_sum/
│ └── __init__.py
|
└── test.py
Vous remarquerez que lorsque vous ajoutez de nouveaux tests, votre fichier devient plus lourd et difficile à maintenir, nous vous recommandons donc de créer le dossier
tests/
et de diviser les tests en plusieurs fichiers. Assurez-vous que les noms de tous les fichiers commencent par
test_
, afin que les
test_
test comprennent que les fichiers Python contiennent des tests qui doivent être exécutés. Sur les grands projets, les tests sont divisés en plusieurs répertoires, selon leur fonction ou leur utilisation.
Remarque: Et quelle est votre application est un script unique?
Vous pouvez importer n'importe quel attribut de script: classes, fonctions ou variables, à l'aide de la fonction intégrée __import__()
. Au lieu de la from my_sum import sum
écrivez ce qui suit: target = __import__("my_sum.py") sum = target.sum
Lorsque vous utilisez __import__()
vous n'avez pas à transformer le dossier de projet en package et vous pouvez spécifier le nom du fichier. Cela est utile si le nom de fichier entre en conflit avec les noms des bibliothèques de packages standard. Par exemple, si math.py
conflit avec le module mathématique.Comment structurer un test simpleAvant d'écrire des tests, vous devez résoudre quelques questions:
- Que voulez-vous tester?
- Ecrivez-vous un test unitaire ou un test d'intégration?
Vous testez actuellement
sum()
. Vous pouvez tester différents comportements pour cela, par exemple:
- Est-il possible de résumer une liste d'entiers?
- Est-il possible de résumer un tuple ou un ensemble?
- Puis-je résumer une liste de nombres à virgule flottante?
- Que se passe-t-il si vous donnez une mauvaise valeur à l'entrée: un seul entier ou une chaîne?
- Que se passe-t-il si l'une des valeurs est négative?
Le moyen le plus simple de tester est une liste d'entiers. Créez un fichier
test.py
avec le code suivant:
import unittest from my_sum import sum class TestSum(unittest.TestCase): def test_list_int(self): """ Test that it can sum a list of integers """ data = [1, 2, 3] result = sum(data) self.assertEqual(result, 6) if __name__ == '__main__': unittest.main()
Le code dans cet exemple:
- Importe
sum()
du package my_sum()
que vous avez créé; - Définit une nouvelle classe de cas de test appelée TestSum qui hérite de
unittest.TestCase
; - Définit une
.test_list_int()
test .test_list_int()
pour tester une liste entière. La méthode .test_list_int()
fera ce qui suit
:
- Déclare une variable de
data
avec une liste de valeurs (1, 2, 3)
; my_sum.sum(data)
valeur my_sum.sum(data)
result
variable;- Détermine que la valeur du résultat est 6 à l'aide de la méthode
.assertEqual()
sur la classe unittest.TestCase
.
- Définit un point d'entrée de ligne de commande qui lance le
.main()
test .main()
.
Si vous ne savez pas ce qu'est self ou comment est défini
.assertEqual()
, vous pouvez actualiser vos connaissances en programmation orientée objet avec la programmation orientée objet
Python 3 .
Comment écrire des déclarationsLa dernière étape de l'écriture d'un test consiste à vérifier que la sortie correspond aux valeurs connues. C'est ce qu'on appelle une assertion. Il existe plusieurs directives générales pour la rédaction de déclarations:
- Vérifiez que les tests sont reproductibles et exécutez-les plusieurs fois pour vous assurer qu'ils donnent les mêmes résultats à chaque fois;
- Vérifiez et confirmez les résultats qui s'appliquent à votre saisie - vérifiez que le résultat correspond bien à la somme des valeurs de l'exemple
sum()
.
Unittest dispose de nombreuses méthodes pour confirmer les valeurs, les types et l'existence de variables. Voici quelques-unes des méthodes les plus couramment utilisées:
La méthode | Équivalent |
---|
.assertEqual (a, b) | a == b |
.assertTrue (x) | bool (x) est True |
.assertFalse (x) | bool (x) est False |
.assertIs (a, b) | a est b |
.assertIsNone (x) | x est Aucun |
.assertIn (a, b) | a en b |
.assertIsInstance (a, b) | isinstance (a, b) |
.assertIs()
,
.assertIsNone()
,
.assertIn()
et
.assertIsInstance()
ont des méthodes opposées appelées
.assertIsNot()
et ainsi de suite.
Effets secondairesÉcrire des tests est plus difficile que de simplement regarder la valeur de retour d'une fonction. Souvent, l'exécution de code modifie d'autres parties de l'environnement: attributs de classe, fichiers du système de fichiers, valeurs dans la base de données. Il s'agit d'une partie importante des tests appelés effets secondaires. Décidez si vous testez un effet secondaire avant de l'inclure dans votre liste de revendications.
Si vous constatez qu'il y a beaucoup d'effets secondaires dans le bloc de code que vous souhaitez tester, alors vous violez le
principe de responsabilité unique . La violation du principe de la responsabilité unique signifie qu'un morceau de code fait trop de choses et nécessite une refactorisation. Suivre le principe de la responsabilité exclusive est un excellent moyen de concevoir du code pour lequel il ne sera pas difficile d'écrire des tests unitaires simples et reproductibles et, finalement, de créer des applications fiables.
Lancement du premier testVous avez créé le premier test et vous devez maintenant essayer de l'exécuter. Il est clair qu'il sera réussi, mais avant de créer des tests plus complexes, vous devez vous assurer que même de tels tests réussissent.
Exécution d'exécuteurs de testTest Executor - Une application Python qui exécute le code de test, valide les assertions et affiche les résultats des tests dans la console. À la fin de test.py, ajoutez ce petit morceau de code:
if __name__ == '__main__': unittest.main()
Il s'agit du point d'entrée de ligne de commande. Si vous exécutez ce script en exécutant python
test.py
sur la ligne de commande, il appellera
unittest.main()
. Cela démarre le testeur
unittest.TestCase
détectant toutes les classes de ce fichier qui héritent de
unittest.TestCase
.
C'est l'une des nombreuses façons d'exécuter le lanceur de test le plus complet. Si vous avez un seul fichier de test appelé
test.py
, appeler python test.py est un excellent moyen de commencer.
Une autre façon consiste à utiliser la ligne de commande la plus uniforme. Essayons:
$ python -m unittest test
Cela exécutera le même module de test (appelé
test
) via la ligne de commande. Vous pouvez ajouter des paramètres supplémentaires pour modifier la sortie. L'un d'eux est -v pour verbeux. Essayons ce qui suit:
$ python -m unittest -v test test_list_int (test.TestSum) ... ok ---------------------------------------------------------------------- Ran 1 tests in 0.000s
Nous avons exécuté un test à partir de test.py et publié les résultats sur la console. Le mode détaillé répertorie les noms des tests effectués et les résultats de chacun d'eux.
Au lieu de fournir le nom du module contenant les tests, vous pouvez demander la découverte automatique à l'aide de ce qui suit:
$ python -m unittest discover
Cette commande recherchera dans le répertoire courant les fichiers avec
test*.py
dans le nom pour les tester.
Si vous disposez de plusieurs fichiers de test et que vous suivez le modèle de dénomination
test*.py
, vous pouvez transmettre le nom du répertoire à l'aide de l'indicateur -s et du nom du dossier.
$ python -m unittest discover -s tests
unittest exécutera tous les tests dans un seul plan de test et produira les résultats.
Enfin, si votre code source n'est pas dans le répertoire racine, mais dans un sous-répertoire, par exemple, dans un dossier appelé src /, vous pouvez dire à unittest où exécuter les tests à l'aide de l'indicateur -t pour importer correctement les modules:
$ python -m unittest discover -s tests -t src
unittest trouvera tous
test*.py
fichiers
test*.py
dans le répertoire
src/
intérieur des
tests
, puis les exécutera.
Comprendre les résultats des tests
C'était un exemple très simple où tout s'est bien passé, essayons donc de comprendre la sortie d'un test qui a échoué.
sum()
doit accepter d'autres listes de type numérique, par exemple des fractions.
Au début du code dans
test.py
ajoutez une expression pour importer le type
Fraction
depuis le module
fractions
de la bibliothèque standard.
from fractions import Fraction
Ajoutez maintenant un test avec une instruction, en attendant une valeur incorrecte. Dans notre cas, nous nous attendons à ce que la somme de ¼, ¼ et ⅖ soit égale à 1:
import unittest from my_sum import sum class TestSum(unittest.TestCase): def test_list_int(self): """ Test that it can sum a list of integers """ data = [1, 2, 3] result = sum(data) self.assertEqual(result, 6) def test_list_fraction(self): """ Test that it can sum a list of fractions """ data = [Fraction(1, 4), Fraction(1, 4), Fraction(2, 5)] result = sum(data) self.assertEqual(result, 1) if __name__ == '__main__': unittest.main()
Si vous exécutez à nouveau les tests avec le test python -m unittest, obtenez les éléments suivants:
$ python -m unittest test F. ====================================================================== FAIL: test_list_fraction (test.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "test.py", line 21, in test_list_fraction self.assertEqual(result, 1) AssertionError: Fraction(9, 10) != 1 ---------------------------------------------------------------------- Ran 2 tests in 0.001s FAILED (failures=1)
Dans cette sortie, vous voyez ce qui suit:
- La première ligne montre les résultats de tous les tests: un échoué (F), un réussi (.);
- FAIL montre quelques détails de l'échec du test:
- Le nom de la méthode de test (
test_list_fraction
); - Module de
test
( test
) et cas de test ( TestSum
); - Chaînes de trace avec une erreur;
- Détails de la déclaration avec le résultat attendu (1) et le résultat réel (fraction (9, 10))
N'oubliez pas que vous pouvez ajouter des informations supplémentaires à la sortie de test à l'aide de l'indicateur -v à la
python -m unittest
.
Exécution de tests de PyCharmSi vous utilisez l'IDE PyCharm, vous pouvez exécuter unittest ou pytest en procédant comme suit:
- Dans la fenêtre de l'outil Projet, sélectionnez le répertoire des tests.
- Dans le menu contextuel, sélectionnez la commande d'exécution la plus unitaire. Par exemple, «Unittests in my Tests ...».
Cela exécutera unittest dans la fenêtre de test et retournera les résultats dans PyCharm:

Plus d'informations sont disponibles sur
le site Web de PyCharm .
Exécution de tests à partir du code Visual StudioSi vous utilisez l'IDE de code Microsoft Visual Studio, la prise en charge de unittest, nose et pytest est déjà intégrée au plug-in Python.
Si vous l'avez installé, vous pouvez configurer la configuration du test en ouvrant la palette de commandes avec Ctrl + Maj + P et en écrivant «Test Python». Vous verrez une liste d'options:

Sélectionnez Déboguer tous les tests unitaires, après quoi VSCode enverra une demande de configuration du cadre de test. Cliquez sur l'engrenage pour sélectionner le lanceur de test (unittest) et le répertoire personnel (.).
Une fois la configuration terminée, vous verrez l'état des tests en bas de l'écran et vous pouvez accéder rapidement aux journaux de test et redémarrer les tests en cliquant sur les icônes:

Nous constatons que les tests sont en cours, mais certains ont échoué.
LA FIN
Dans la prochaine partie de l'article, nous examinerons les tests de frameworks tels que Django et Flask.
Nous attendons vos questions et commentaires ici et, comme toujours, vous pouvez vous rendre Ă Stanislav lors d'une
journée portes ouvertes .
Deuxième partie