Dans le domaine des tests automatiques, vous pouvez trouver divers outils, par exemple, py.test est l'une des solutions les plus populaires pour écrire des auto-tests en Python.
Ayant parcouru de nombreuses ressources liées à pytest et ayant étudié la documentation du site officiel du projet, je n'ai pas pu trouver de description directe de la solution pour l'une des tâches principales - exécuter des tests avec des données de test stockées dans un fichier séparé. Sinon, on peut dire, le chargement des paramètres dans les fonctions de test depuis le (s) fichier (s) ou le paramétrage directement depuis le fichier. Une telle procédure n'est décrite nulle part dans les subtilités et la seule mention de cette fonctionnalité se trouve dans une seule ligne de la documentation Pytest.
Dans cet article, je vais parler de ma solution à ce problème.
Défi
La tâche principale consiste à générer des cas de test sous la forme des paramètres test_input
et test_input
dans chaque fonction de test individuelle à partir des noms de fonction de fichier correspondants.
Tâches supplémentaires:
- choisissez une mise en forme lisible par l'homme des fichiers avec des cas de test;
- laisser la possibilité de prendre en charge des cas de test codés en dur;
- afficher des identifiants clairs pour chaque cas.
Boîte à outils
Dans l'article, j'utilise Python 3 (2.7 convient également), pyyaml et pytest
(versions 5+ pour Python 3, ou 4.6 pour Python 2.7) sans utiliser de plugins tiers. De plus, la bibliothèque os
standard sera utilisée.
Le fichier lui-même à partir duquel nous prendrons des cas de test doit être structuré à l'aide d'un langage de balisage pratique pour une personne. Dans mon cas, YAML a été choisi (car il résout la tâche supplémentaire de choisir un format lisible par l'homme) . En fait, le type de langage de balisage pour les fichiers avec les ensembles de données dont vous avez besoin dépend des exigences présentées dans le projet.
Implémentation
Puisque le principal pilier de l'univers en programmation est l'accord, nous devrons en introduire plusieurs pour notre solution.
Interception
Pour commencer, cette solution utilise la fonction d'interception pytest_generate_tests
( wiki ), qui commence au stade de la génération des cas de test, et son argument metafunc
, qui nous permet de paramétrer la fonction. À ce stade, pytest parcourt chaque fonction de test et exécute le code de génération suivant pour celle-ci.
Arguments
Vous devez définir une liste exhaustive de paramètres pour les fonctions de test. Dans mon cas, le dictionnaire est test_input
et tout type de données (le plus souvent une chaîne ou un entier) dans expected_result
. Nous avons besoin de ces paramètres pour les utiliser dans metafunc.parametrize(...)
.
Paramétrisation
Cette fonction répète complètement le fonctionnement du @pytest.mark.parametrize
paramétrage @pytest.mark.parametrize
, qui prend comme premier argument une chaîne répertoriant les arguments de la fonction de test (dans notre cas "test_input, expected_result"
) et une liste de données par lesquelles il va itérer pour créer nos cas de test (par exemple, [(1, 2), (2, 4), (3, 6)]
).
Au combat, cela ressemblera à ceci:
@pytest.mark.parametrize("test_input, expected_result", [(1, 2), (2, 4), (3, 6)]) def test_multiplication(test_input, expected_result): assert test_input * 2 == expected_result
Et dans notre cas, nous l'indiquerons à l'avance:
Filtrage
D'ici suit également l'allocation de ces fonctions de test où les données d'un fichier sont nécessaires, de celles qui utilisent des données statiques / dynamiques. Nous appliquerons ce filtrage avant d'analyser les informations du fichier.
Les filtres eux-mêmes peuvent être quelconques, par exemple:
- Marqueur de fonction nommé
yaml
:
Sinon, le même filtre peut être implémenté comme ceci:
- L'argument de la fonction
test_input
:
Cette option me convenait le mieux.
Résultat
Nous devons ajouter uniquement la partie où nous analysons les données du fichier. Ce ne sera pas difficile dans le cas de yaml (ainsi que json, xml, etc.) , nous collectons donc tout sur le tas.
Nous écrivons un script de test comme celui-ci:
Un fichier de données:
# test_multiplication.yaml - !!python/tuple [1,2] - !!python/tuple [1,3] - !!python/tuple [1,5] - !!python/tuple [2,4] - !!python/tuple [3,4] - !!python/tuple [5,4]
Nous obtenons la liste suivante de cas de test:
pytest /test_script.py --collect-only ======================== test session starts ======================== platform linux -- Python 3.7.4, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 rootdir: /pytest_habr collected 6 items <Module test_script.py> <Function test_multiplication[1-2]> <Function test_multiplication[1-3]> <Function test_multiplication[1-5]> <Function test_multiplication[2-4]> <Function test_multiplication[3-4]> <Function test_multiplication[5-4]> ======================== no tests ran in 0.04s ========================
Et en exécutant le script, ce résultat: 4 failed, 2 passed, 1 warnings in 0.11s
Ajouter. affectations
Cela pourrait terminer l'article, mais pour des raisons de complexité, j'ajouterai des identifiants plus pratiques à notre fonction, une autre analyse des données et un marquage de chaque cas de test individuel.
Alors, tout de suite, le code:
En conséquence, nous modifions l'apparence de notre fichier YAML:
# test_multiplication.yaml - test_data: [1, 2] id: 'one_two' - test_data: [1,3] marks: ['xfail'] - test_data: [1,5] marks: ['skip'] - test_data: [2,4] id: "it's good" marks: ['xfail'] - test_data: [3,4] marks: ['negative'] - test_data: [5,4] marks: ['more_than']
Ensuite, la description changera en:
<Module test_script.py> <Function test_multiplication[one_two]> <Function test_multiplication[1_3]> <Function test_multiplication[1_5]> <Function test_multiplication[it's good]> <Function test_multiplication[3_4]> <Function test_multiplication[5_4]>
Et le lancement sera: 2 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 2 warnings in 0.12s
PS: avertissements - parce que les marqueurs auto-écrits ne sont pas enregistrés dans pytest.ini
En développement du sujet
Prêt à discuter dans les commentaires des questions sur le type:
- quelle est la meilleure façon d'écrire un fichier yaml?
- Dans quel format est-il plus pratique de stocker les données de test?
- Quel cas de test supplémentaire est nécessaire au stade de la génération?
- Ai-je besoin d'identifiants pour chaque cas?