web-dev-qa-db-fra.com

Exécuter unittest avec une structure de répertoire de test typique

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."

553
Major Major

La meilleure solution à mon avis consiste à utiliser unittestinterface 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.

520
Pierre

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".

45
Carl Meyer

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.

19
stw_dev

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?

18
Mark Byers

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. 

8
Tom Willis

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
8
andpei

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

5
Vlad Bezden

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)
5
Derek Soike

Utilisez setup.py develop pour que votre répertoire de travail fasse partie de l'environnement Python installé, puis exécutez les tests.

5
Ned Batchelder

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?

4
kenorb

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
4
Alan L

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()
3
rolika

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()
3
aliopi

Python 3+

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.

2
eusoubrasileiro

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 

  • import test.context échoue lorsqu'il est exécuté avec "python test_antigravity.py" et 
  • import context échoue lorsqu'il est exécuté avec "python -m unittest" à partir du répertoire new_project.

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.

2
tjk

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.

1
Egbert S

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.

0
chasmani

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).

0
squid

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.

0
pj.dewitte