La structure de répertoires très commune, même pour un module Python simple, semble être de séparer les tests unitaires dans leur propre répertoire test
:
new_project/
antigravity/
antigravity.py
test/
test_antigravity.py
setup.py
etc.
par exemple, voyez ce projet Python howto .
Ma question est simplement Quelle est la manière habituelle d’exécuter les tests? Je suppose que cela est évident pour tout le monde sauf moi, mais vous ne pouvez pas simplement exécuter python test_antigravity.py
à partir du répertoire de test car son import antigravity
échouera car le module ne se trouve pas sur le chemin.
Je sais que je pourrais modifier PYTHONPATH et d'autres astuces liées au chemin de recherche, mais je ne peux pas croire que ce soit le moyen le plus simple. C'est bien si vous êtes le développeur, mais vous ne pouvez pas vous attendre à ce que vos utilisateurs l'utilisent s'ils souhaitent simplement vérifier les tests. qui passe.
L’autre solution consiste simplement à copier le fichier de test dans l’autre répertoire, mais cela semble un peu idiot et ne contient pas l’intérêt de les avoir dans un répertoire séparé.
Donc, si vous veniez de télécharger le code source de mon nouveau projet, comment exécuteriez-vous les tests unitaires? Je préférerais une réponse qui me permettrait de dire à mes utilisateurs: "Pour exécuter les tests unitaires, faites X."
La meilleure solution à mon avis consiste à utiliser unittest
interface de ligne de commande qui ajoutera le répertoire au sys.path
afin que vous n'ayez pas à le faire (dans la classe TestLoader
).
Par exemple pour une structure de répertoire comme celle-ci:
new_project
├── antigravity.py
└── test_antigravity.py
Vous pouvez juste courir:
$ cd new_project
$ python -m unittest test_antigravity
Pour une structure de répertoire semblable au vôtre:
new_project
├── antigravity
│ ├── __init__.py # make it a package
│ └── antigravity.py
└── test
├── __init__.py # also make test a package
└── test_antigravity.py
Et dans les modules de test du package test
, vous pouvez importer le package antigravity
et ses modules comme d'habitude:
# import the package
import antigravity
# import the antigravity module
from antigravity import antigravity
# or an object inside the antigravity module
from antigravity.antigravity import my_object
Exécution d'un seul module de test:
Pour exécuter un seul module de test, dans ce cas, test_antigravity.py
:
$ cd new_project
$ python -m unittest test.test_antigravity
Référencez simplement le module de test de la même manière que vous l'importez.
Exécution d'un seul cas de test ou méthode de test:
Vous pouvez également exécuter une seule variable TestCase
ou une seule méthode de test:
$ python -m unittest test.test_antigravity.GravityTestCase
$ python -m unittest test.test_antigravity.GravityTestCase.test_method
Lancer tous les tests:
Vous pouvez également utiliser découverte du test qui détectera et exécutera tous les tests pour vous. Ils doivent être des modules ou des packages nommés test*.py
(pouvant être modifiés avec l'indicateur -p, --pattern
):
$ cd new_project
$ python -m unittest discover
Cela exécutera tous les modules test*.py
dans le package test
.
La solution la plus simple pour vos utilisateurs consiste à fournir un script exécutable (runtests.py
ou autre) amorçant l'environnement de test nécessaire, y compris, si nécessaire, l'ajout temporaire du répertoire de votre projet racine à sys.path. Cela ne nécessite pas que les utilisateurs définissent des variables d'environnement, quelque chose comme celle-ci fonctionne bien dans un script d'amorçage:
import sys, os
sys.path.insert(0, os.path.dirname(__file__))
Ensuite, vos instructions à vos utilisateurs peuvent être aussi simples que "python runtests.py".
Bien sûr, si le chemin dont vous avez besoin est réellement os.path.dirname(__file__)
, vous n'avez pas besoin de l'ajouter à sys.path
; Python place toujours le répertoire du script en cours d'exécution au début de sys.path
. Par conséquent, selon votre structure de répertoires, il suffit de localiser votre runtests.py
au bon endroit.
De plus, le module unittest dans Python 2.7+ (qui est reporté sous le nom unittest2 pour Python 2.6 et versions antérieures) a désormais la fonction test discovery , vous n'avez donc plus besoin de nez Découverte de test automatique: vos instructions utilisateur peuvent être aussi simples que "python -m unittest discover".
Je crée généralement un script "exécuter les tests" dans le répertoire du projet (celui qui est commun au répertoire source et à test
) qui charge ma suite "Tous les tests". C’est généralement du code standard, je peux donc le réutiliser d’un projet à l’autre.
run_tests.py:
import unittest
import test.all_tests
testSuite = test.all_tests.create_test_suite()
text_runner = unittest.TextTestRunner().run(testSuite)
test/all_tests.py (from Comment exécuter tous les tests unitaires Python dans un répertoire? )
import glob
import unittest
def create_test_suite():
test_file_strings = glob.glob('test/test_*.py')
module_strings = ['test.'+str[5:len(str)-3] for str in test_file_strings]
suites = [unittest.defaultTestLoader.loadTestsFromName(name) \
for name in module_strings]
testSuite = unittest.TestSuite(suites)
return testSuite
Avec cette configuration, vous ne pouvez en effet que include antigravity
dans vos modules de test. L'inconvénient est que vous auriez besoin de plus de code de support pour exécuter un test particulier ... Je les exécute tous à chaque fois.
De l'article que vous avez lié à:
Créez un fichier test_modulename.py et mettez vos tests les plus inconditionnels dedans. Puisque les modules de test sont dans un .__ séparé. répertoire de votre code, vous aurez peut-être besoin de pour ajouter le répertoire parent de votre module à votre PYTHONPATH pour pouvoir exécuter leur:
$ cd /path/to/googlemaps $ export PYTHONPATH=$PYTHONPATH:/path/to/googlemaps/googlemaps $ python test/test_googlemaps.py
Enfin, il y a un plus populaire framework de tests unitaires pour Python (c’est très important!), nez. nez permet de simplifier et d’étendre la fonction intégrée unittest framework (il peut, par exemple, trouver automatiquement votre test coder et configurer votre PYTHONPATH pour vous.), mais il n’est pas inclus dans le distribution standard Python.
Peut-être devriez-vous regarder le nez comme cela suggère?
si vous exécutez "python setup.py develop", le package sera dans le chemin. Mais vous ne voudrez peut-être pas le faire, car vous pourriez infecter votre installation système python. C'est pourquoi des outils tels que virtualenv et buildout existent.
J'ai eu le même problème, avec un dossier de tests unitaires séparé. Parmi les suggestions mentionnées, j’ajoute le chemin source absolute à sys.path
.
L'avantage de la solution suivante est que l'on peut exécuter le fichier test/test_yourmodule.py
sans d'abord modifier le répertoire test:
import sys, os
testdir = os.path.dirname(__file__)
srcdir = '../antigravity'
sys.path.insert(0, os.path.abspath(os.path.join(testdir, srcdir)))
import antigravity
import unittest
Si vous utilisez VS Code et que vos tests se situent au même niveau que votre projet, alors exécuter et déboguer votre code ne fonctionne pas immédiatement. Ce que vous pouvez faire est de changer votre fichier launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python",
"type": "python",
"request": "launch",
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"program": "${file}",
"cwd": "${workspaceRoot}",
"env": {},
"envFile": "${workspaceRoot}/.env",
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput"
]
}
]
}
La ligne clé ici est envFile
"envFile": "${workspaceRoot}/.env",
Dans la racine de votre projet, ajoutez un fichier .env
À l'intérieur de votre fichier .env, ajoutez un chemin d'accès à la racine de votre projet. Cela va ajouter temporairement
PYTHONPATH = C:\VOTRE\PYTHON\PROJECT\ROOT_DIRECTORY
chemin de votre projet et vous pourrez utiliser les tests unitaires de débogage de VS Code
Solution/Exemple pour le module unittest Python
Compte tenu de la structure de projet suivante:
ProjectName
├── project_name
| ├── models
| | └── thing_1.py
| └── __main__.py
└── test
├── models
| └── test_thing_1.py
└── __main__.py
Vous pouvez exécuter votre projet à partir du répertoire racine avec python project_name
, qui appelle ProjectName/project_name/__main__.py
.
Pour exécuter vos tests avec python test
, exécutant effectivement ProjectName/test/__main__.py
, vous devez procéder comme suit:
1) Transformez votre répertoire test/models
en un package en ajoutant un fichier __init__.py
. Cela rend les cas de test du sous-répertoire accessibles à partir du répertoire parent test
.
# ProjectName/test/models/__init__.py
from .test_thing_1 import Thing1TestCase
2) Modifiez votre chemin système dans test/__main__.py
pour inclure le répertoire project_name
.
# ProjectName/test/__main__.py
import sys
import unittest
sys.path.append('../project_name')
loader = unittest.TestLoader()
testSuite = loader.discover('test')
testRunner = unittest.TextTestRunner(verbosity=2)
testRunner.run(testSuite)
Vous pouvez maintenant importer des éléments de project_name
dans vos tests.
# ProjectName/test/models/test_thing_1.py
import unittest
from project_name.models import Thing1 # this doesn't work without 'sys.path.append' per step 2 above
class Thing1TestCase(unittest.TestCase):
def test_thing_1_init(self):
thing_id = 'ABC'
thing1 = Thing1(thing_id)
self.assertEqual(thing_id, thing.id)
Utilisez setup.py develop
pour que votre répertoire de travail fasse partie de l'environnement Python installé, puis exécutez les tests.
Il est possible d'utiliser un wrapper qui exécute certains tests ou tous les tests sélectionnés.
Par exemple:
./run_tests antigravity/*.py
ou pour exécuter tous les tests de manière récursive, utilisez globbing (tests/**/*.py
) (activé par shopt -s globstar
).
Le wrapper peut utiliser argparse
pour analyser les arguments tels que:
parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='*')
Puis chargez tous les tests:
for filename in args.files:
exec(open(filename).read())
puis ajoutez-les à votre suite de tests (en utilisant inspect
):
alltests = unittest.TestSuite()
for name, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj) and name.startswith("FooTest"):
alltests.addTest(unittest.makeSuite(obj))
et les exécuter:
result = unittest.TextTestRunner(verbosity=2).run(alltests)
Vérifiez this example pour plus de détails.
Voir aussi: Comment exécuter tous les tests unitaires Python dans un répertoire?
J'ai remarqué que si vous exécutez l'interface de ligne de commande unittest à partir de votre répertoire "src", les importations fonctionnent correctement, sans modification.
python -m unittest discover -s ../test
Si vous voulez mettre cela dans un fichier de commandes dans votre répertoire de projet, vous pouvez faire ceci:
setlocal & cd src & python -m unittest discover -s ../test
Voici la structure de mon projet:
ProjectFolder:
- project:
- __init__.py
- item.py
- tests:
- test_item.py
J'ai trouvé qu'il était préférable d'importer dans la méthode setUp ():
import unittest
import sys
class ItemTest(unittest.TestCase):
def setUp(self):
sys.path.insert(0, "../project")
from project import item
# further setup using this import
def test_item_props(self):
# do my assertions
if __== "__main__":
unittest.main()
Quelle est la manière habituelle d'exécuter les tests?
J'utilise Python 3.6.2
cd new_project
pytest test/test_antigravity.py
Pour installer pytest: Sudo pip install pytest
Je n'ai défini aucune variable de chemin et mes importations n'échouent pas avec la même structure de projet "test".
J'ai commenté ceci: if __== '__main__'
comme ceci:
test_antigravity.py
import antigravity
class TestAntigravity(unittest.TestCase):
def test_something(self):
# ... test stuff here
# if __== '__main__':
#
# if __package__ is None:
#
# import something
# sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
# from .. import antigravity
#
# else:
#
# from .. import antigravity
#
# unittest.main()
Ajout à @Pierre
Utiliser la structure de répertoire unittest
comme ceci:
new_project
├── antigravity
│ ├── __init__.py # make it a package
│ └── antigravity.py
└── test
├── __init__.py # also make test a package
└── test_antigravity.py
Pour exécuter le module de test test_antigravity.py
:
$ cd new_project
$ python -m unittest test.test_antigravity
Ou une seule TestCase
$ python -m unittest test.test_antigravity.GravityTestCase
Obligatoire n'oubliez pas le __init__.py
même s'il est vide, sinon cela ne fonctionnera pas.
Vous ne pouvez pas importer à partir du répertoire parent sans du vaudou. Voici encore une autre manière qui fonctionne avec au moins Python 3.6.
Commencez par créer un fichier test/context.py avec le contenu suivant:
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
Ensuite, importez le fichier suivant dans le fichier test/test_antigravity.py:
import unittest
try:
import context
except ModuleNotFoundError:
import test.context
import antigravity
Notez que la raison de cette clause try-except est que
Avec cette supercherie, ils travaillent tous les deux.
Maintenant, vous pouvez exécuter all les fichiers de test dans le répertoire de test avec:
$ pwd
/projects/new_project
$ python -m unittest
ou exécutez un fichier de test individuel avec:
$ cd test
$ python test_antigravity
Ok, ce n'est pas beaucoup plus joli que d'avoir le contenu de context.py dans test_antigravity.py, mais un peu Les suggestions sont les bienvenues.
Ce script BASH exécute le répertoire de test python unittest n'importe où dans le système de fichiers, quel que soit le répertoire de travail dans lequel vous vous trouvez.
Ceci est utile lorsque vous restez dans le répertoire de travail ./src
ou ./example
et que vous avez besoin d'un test unitaire rapide:
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"
python -m unittest discover -s "$readlink"/test -v
Pas besoin d'un fichier test/__init__.py
pour surcharger votre paquet/mémoire en surcharge pendant la production.
De cette façon, vous pourrez exécuter les scripts de test à partir de n'importe où et sans modifier les variables système à partir de la ligne de commande.
Cela ajoute le dossier de projet principal au chemin python, avec l'emplacement trouvé par rapport au script lui-même, et non par rapport au répertoire de travail en cours.
import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
Ajoutez cela au sommet de tous vos scripts de test. Cela ajoutera le dossier principal du projet au chemin système, donc toutes les importations de modules qui fonctionnent à partir de là fonctionneront désormais. Et peu importe où vous exécutez les tests.
Vous pouvez évidemment modifier le fichier project_path_hack pour qu'il corresponde à l'emplacement de votre dossier de projet principal.
Vous devriez vraiment utiliser l'outil pip.
Utilisez pip install -e .
pour installer votre paquet en mode de développement. C'est une très bonne pratique, recommandée par pytest (voir leur documentation sur les bonnes pratiques , où vous pouvez également trouver deux présentations de projet à suivre).
Si vous recherchez une solution en ligne de commande uniquement:
Basé sur la structure de répertoire suivante (généralisée avec un répertoire source dédié):
new_project/
src/
antigravity.py
test/
test_antigravity.py
Windows: (en new_project
)
$ set PYTHONPATH=%PYTHONPATH%;%cd%\src
$ python -m unittest discover -s test
Voir cette question si vous souhaitez l’utiliser dans un lot pour boucle.
Linux: (en new_project
)
$ export PYTHONPATH=$PYTHONPATH:$(pwd)/src [I think - please edit this answer if you are a Linux user and you know this]
$ python -m unittest discover -s test
Avec cette approche, il est également possible d’ajouter d’autres répertoires à PYTHONPATH si nécessaire.