Où vont les tests unitaires Python?

490

Si vous écrivez une bibliothèque ou une application, où vont les fichiers de test unitaire?

Il est agréable de séparer les fichiers de test du code de l'application principale, mais il est gênant de les placer dans un sous-répertoire "tests" à l'intérieur du répertoire racine de l'application, car cela rend plus difficile l'importation des modules que vous testerez.

Y a-t-il une meilleure pratique ici?

Lecture seulement
la source
4
Il existe de nombreuses réponses acceptables à cette question et cela n'en fait pas une question pour un site Q / A. Les gens doivent comprendre que toutes les grandes questions utiles ne peuvent pas être posées sur SO. Les formateurs choisissent pour cela et nous devons suivre leurs règles.
Wouter J
121
Les gens doivent également comprendre que lorsque les pages SO se retrouvent en tête de liste pour une recherche Google, il est peut-être préférable de suivre le flux plutôt que de s'en tenir à la doctrine officielle SO.
Christopher Barber
6
Les gens de @ChristopherBarber doivent également comprendre que les décisions et les fermetures agressives sont ce qui a fini par gagner une réputation tellement horrible en tant que site, je préfère ne pas poser de questions à moins que je n'ai vraiment, vraiment pas de meilleure option.
Stefano Borini

Réponses:

200

Pour un fichier module.py, le test unitaire doit normalement être appelé test_module.py, selon les conventions de dénomination Pythonic.

Il existe plusieurs endroits généralement acceptés test_module.py:

  1. Dans le même répertoire que module.py.
  2. Dans ../tests/test_module.py(au même niveau que le répertoire de code).
  3. Dans tests/test_module.py(un niveau sous le répertoire de code).

Je préfère le n ° 1 pour sa simplicité à trouver les tests et à les importer. Quel que soit le système de construction que vous utilisez, vous pouvez facilement le configurer pour exécuter des fichiers à partir de test_. En fait, le modèle par défaut unittestutilisé pour la découverte de test esttest*.py .

user6868
la source
12
Le protocole load_tests recherche les fichiers nommés test * .py par défaut. En outre, ce meilleur résultat Google et cette documentation la plus complète utilisent tous les deux le format test_module.py.
dfarrell07
6
L'utilisation de foo_test.py peut enregistrer une ou deux séquences de touches lors de l'utilisation de la complétion de tabulation car vous n'avez pas un tas de fichiers commençant par 'test_'.
juniper-
11
@juniper, suivre vos pensées module_test apparaîtra dans la complétion automatique lorsque vous codez. Cela peut être déroutant ou ennuyeux.
Medeiros
11
Lors du déploiement de code, nous ne voulons pas déployer les tests sur nos sites de production. Donc, les avoir dans un répertoire './tests/test_blah.py' est facile à tirer lorsque nous faisons des déploiements. AUSSI, certains tests prennent des exemples de fichiers de données, et avoir ceux-ci dans un répertoire de test est vital de peur de déployer des données de test.
Kevin J. Rice
1
@ KevinJ.Rice Ne devriez-vous pas tester que le code fonctionne sur vos sites de production?
endolith
67

Un seul fichier de test

S'il n'y a qu'un seul fichier de test, il est recommandé de le placer dans un répertoire de niveau supérieur:

module/
    lib/
        __init__.py
        module.py
    test.py

Exécutez le test dans CLI

python test.py

De nombreux fichiers de test

Si possède de nombreux fichiers de test, placez-le dans un testsdossier:

module/
    lib/
        __init__.py
        module.py
    tests/
        test_module.py
        test_module_function.py
# test_module.py

import unittest
from lib import module

class TestModule(unittest.TestCase):
    def test_module(self):
        pass

if __name__ == '__main__':
    unittest.main()

Exécutez le test dans CLI

# In top-level /module/ folder
python -m tests.test_module
python -m tests.test_module_function

Utilisation unittest discovery

unittest discovery trouvera tous les tests dans le dossier du package.

Créer un dossier __init__.pydanstests/

module/
    lib/
        __init__.py
        module.py
    tests/
        __init__.py
        test_module.py
        test_module_function.py

Exécutez le test dans CLI

# In top-level /module/ folder

# -s, --start-directory (default current directory)
# -p, --pattern (default test*.py)

python -m unittest discover

Référence

Cadre de test unitaire

Aile d'acier
la source
51

Une pratique courante consiste à placer le répertoire tests dans le même répertoire parent que votre module / package. Donc, si votre module s'appelait foo.py, la disposition de votre répertoire ressemblerait à:

parent_dir/
  foo.py
  tests/

Bien sûr, il n'y a pas qu'une seule façon de procéder. Vous pouvez également créer un sous-répertoire de tests et importer le module à l'aide de l'importation absolue .

Partout où vous mettez vos tests, je vous recommande d'utiliser le nez pour les exécuter. Nose recherche dans vos répertoires des tests. De cette façon, vous pouvez mettre des tests là où ils ont le plus de sens sur le plan organisationnel.

Cristian
la source
16
J'aimerais faire ça mais je ne peux pas le faire fonctionner. Pour exécuter les tests, je suis dans le répertoire parent_dir et tapez: "python tests \ foo_test.py", et dans foo_test.py: "de ..foo importez ceci, que, l'autre" qui échoue avec: "ValueError: Tentative import relatif dans un non-package "parent_dir et tests contiennent un init .py, donc je ne sais pas pourquoi ils ne sont pas des packages. Je soupçonne que c'est parce que le script de niveau supérieur que vous exécutez à partir de la ligne de commande ne peut pas être considéré (faisant partie) d'un package, même s'il se trouve dans un répertoire avec un init .py. Alors, comment puis-je exécuter les tests? Je travaille sur Windows aujourd'hui, bénis mes chaussettes en coton.
Jonathan Hartley
4
La meilleure façon - j'ai trouvé - d'exécuter des tests unitaires est d'installer votre bibliothèque / programme puis d'exécuter des tests unitaires avec nose. Je recommanderais virtualenv et virtualenvwrapper pour rendre cela beaucoup plus facile.
Cristian
@Tartley - vous avez besoin d'un fichier init .py dans votre répertoire 'tests' pour que les importations d'absolution fonctionnent. J'ai cette méthode qui fonctionne avec le nez, donc je ne sais pas pourquoi vous avez des problèmes.
cmcginty
4
Merci Casey - mais j'ai un fichier init .py dans tous les répertoires pertinents. Je ne sais pas ce que je fais mal, mais j'ai ce problème sur tous mes projets Python et je ne comprends pas pourquoi personne d'autre ne le fait. Oh chérie.
Jonathan Hartley
8
Une solution à mon problème, avec Python2.6 ou plus récent, nous permet d'exécuter les tests à partir de la racine de votre projet en utilisant: python -m project.package.tests.module_tests (au lieu de python project / package / tests / module_tests.py) . Cela place le répertoire du module de test sur le chemin, afin que les tests puissent ensuite effectuer une importation relative dans leur répertoire parent pour obtenir le module en cours de test.
Jonathan Hartley
32

Nous avions la même question lors de l'écriture de Pythoscope ( https://pypi.org/project/pythoscope/ ), qui génère des tests unitaires pour les programmes Python. Nous avons interrogé les personnes sur la liste des tests en python avant de choisir un répertoire, il y avait beaucoup d'opinions différentes. Finalement, nous avons choisi de mettre un répertoire "tests" dans le même répertoire que le code source. Dans ce répertoire, nous générons un fichier de test pour chaque module dans le répertoire parent.

Paul Hildebrandt
la source
2
Impressionnant! Je viens de lancer Pythoscope (super logo), sur du code hérité et c'était incroyable. Meilleures pratiques instantanées et vous pouvez demander à d'autres développeurs de remplir les talons de test qui échouent. Maintenant, pour l'accrocher au bambou? :)
Will
27

J'ai également tendance à mettre mes tests unitaires dans le fichier lui-même, comme le note Jeremy Cantrell ci-dessus, même si j'ai tendance à ne pas mettre la fonction de test dans le corps principal, mais plutôt à tout mettre dans un

if __name__ == '__main__':
   do tests...

bloquer. Cela finit par ajouter de la documentation au fichier comme «exemple de code» pour savoir comment utiliser le fichier python que vous testez.

Je devrais ajouter, j'ai tendance à écrire des modules / classes très serrés. Si vos modules nécessitent un très grand nombre de tests, vous pouvez les mettre dans un autre, mais même dans ce cas, j'ajouterais quand même:

if __name__ == '__main__':
   import tests.thisModule
   tests.thisModule.runtests

Cela permet à quiconque lit votre code source de savoir où chercher le code de test.

Thomas Andrews
la source
"comme le note Jeremy Cantrell ci-dessus" où?
endolith
J'aime cette façon de travailler. Le plus simple des éditeurs peut être configuré pour exécuter votre fichier avec une touche de raccourci, donc lorsque vous affichez le code, vous pouvez exécuter les tests en un instant. Malheureusement, dans des langages autres que Python, cela peut sembler horrible, donc si vous êtes dans une boutique C ++ ou Java, vos collègues peuvent froncer les sourcils. Cela ne fonctionne pas non plus correctement avec les outils de couverture de code.
Keeely
19

De temps en temps, je me retrouve à vérifier le sujet du placement de test, et à chaque fois la majorité recommande une structure de dossiers séparée à côté du code de la bibliothèque, mais je trouve que chaque fois les arguments sont les mêmes et ne sont pas si convaincants. Je finis par mettre mes modules de test quelque part à côté des modules de base.

La principale raison de cette opération est la refactorisation .

Quand je déplace des choses, je veux que les modules de test se déplacent avec le code; il est facile de perdre des tests s'ils sont dans une arborescence distincte. Soyons honnêtes, tôt ou tard , vous vous retrouvez avec une structure de dossiers totalement différent, comme django , flacon et bien d' autres. Ce qui est bien si vous ne vous en souciez pas.

La principale question que vous devez vous poser est la suivante:

Suis-je en train d'écrire:

  • a) bibliothèque réutilisable ou
  • b) construire un projet qui regroupe des modules semi-séparés?

Si un:

Un dossier séparé et l'effort supplémentaire pour maintenir sa structure peuvent être mieux adaptés. Personne ne se plaindra du déploiement de vos tests en production .

Mais il est également tout aussi facile d'exclure les tests de la distribution lorsqu'ils sont mélangés aux dossiers principaux; mettez ceci dans le setup.py :

find_packages("src", exclude=["*.tests", "*.tests.*", "tests.*", "tests"]) 

Si b:

Vous pouvez souhaiter - comme chacun de nous - que vous écriviez des bibliothèques réutilisables, mais la plupart du temps leur vie est liée à la vie du projet. La capacité de maintenir facilement votre projet devrait être une priorité.

Ensuite, si vous avez fait du bon travail et que votre module convient parfaitement à un autre projet, il sera probablement copié - non bifurqué ou transformé dans une bibliothèque distincte - dans ce nouveau projet, et déplacera les tests qui se trouvent à côté de lui dans la même structure de dossiers est facile par rapport à la recherche de tests dans un gâchis devenu un dossier de test séparé. (Vous pouvez affirmer que cela ne devrait pas être un gâchis en premier lieu, mais soyons réalistes ici).

Donc, le choix est toujours le vôtre, mais je dirais qu'avec des tests mixtes, vous réalisez les mêmes choses qu'avec un dossier séparé, mais avec moins d'efforts pour garder les choses en ordre.

Janusz Skonieczny
la source
1
quel est le problème avec le déploiement de tests en production, en fait? d'après mon expérience, c'est très utile: permet aux utilisateurs d'exécuter des tests sur leur environnement ... lorsque vous obtenez des rapports de bogues, vous pouvez demander à l'utilisateur d'exécuter la suite de tests pour vous assurer que tout va bien et envoyer des correctifs à chaud pour le test suite directement ... d'ailleurs, ce n'est pas parce que vous mettez vos tests dans module.tests qu'il finira en production, sauf si vous avez fait quelque chose de mal dans votre fichier de configuration ...
anarcat
2
Comme je l'ai écrit dans ma réponse, je ne sépare généralement pas les tests. Mais. Mettre des tests dans un package de distribution peut conduire à exécuter des choses que vous ne voudriez pas normalement dans l'environnement de production (par exemple, du code de niveau module). Cela dépend bien sûr de la façon dont les tests sont écrits, mais pour être sûr que vous les laissez de côté, personne ne fera quelque chose de dangereux par erreur. Je ne suis pas contre l'inclusion de tests dans les distributions, mais je comprends qu'en règle générale, c'est plus sûr. Et les mettre dans un dossier séparé rend super facile de ne pas les inclure dans dist.
Janusz Skonieczny
15

J'utilise un tests/répertoire, puis j'importe les principaux modules d'application à l'aide d'importations relatives. Donc, dans MyApp / tests / foo.py, il pourrait y avoir:

from .. import foo

pour importer le MyApp.foomodule.

John Millikin
la source
5
"ValueError: tentative d'importation relative dans un non-package"
cprn
12

Je ne crois pas qu'il existe une "meilleure pratique" établie.

J'ai placé mes tests dans un autre répertoire en dehors du code de l'application. J'ajoute ensuite le répertoire principal de l'application à sys.path (vous permettant d'importer les modules de n'importe où) dans mon script de test runner (qui fait aussi d'autres choses) avant d'exécuter tous les tests. De cette façon, je n'ai jamais à supprimer le répertoire tests du code principal lorsque je le libère, ce qui me fait gagner du temps et des efforts, si infime.

dwestbrook
la source
3
J'ajoute ensuite le répertoire principal de l'application à sys.path Le problème est que si vos tests contiennent des modules d'assistance (comme le serveur Web de test) et que vous devez importer ces modules d'assistance à partir de vos tests appropriés.
Piotr Dobrogost
Voici à quoi ça ressemble pour moi:os.sys.path.append(os.dirname('..'))
Matt M.
11

D'après mon expérience dans le développement de frameworks de test en Python, je suggère de placer les tests unitaires python dans un répertoire séparé. Maintenez une structure de répertoire symétrique. Cela serait utile pour empaqueter uniquement les bibliothèques de base et non pour empaqueter les tests unitaires. Ci-dessous est implémenté à travers un diagramme schématique.

                              <Main Package>
                               /          \
                              /            \
                            lib           tests
                            /                \
             [module1.py, module2.py,  [ut_module1.py, ut_module2.py,
              module3.py  module4.py,   ut_module3.py, ut_module.py]
              __init__.py]

De cette façon, lorsque vous empaquetez ces bibliothèques à l'aide d'un rpm, vous pouvez simplement empaqueter les modules de bibliothèque principaux (uniquement). Cela facilite la maintenabilité, en particulier dans un environnement agile.

Rahul Biswas
la source
1
J'ai réalisé que l'un des avantages potentiels de cette approche est que les tests peuvent être développés (et peut-être même contrôlés par la version) en tant qu'application indépendante. Bien sûr, à chaque avantage, il y a un inconvénient - le maintien de la symétrie consiste essentiellement à «comptabiliser les entrées doubles» et à refactoriser davantage une corvée.
Jasha
Question de suivi douce: pourquoi l'environnement agile est-il particulièrement bien adapté à cette approche? (c.-à-d. qu'en est-il du flux de travail agile qui rend une structure de répertoires symétrique si bénéfique?)
Jasha
11

Je vous recommande de vérifier certains des principaux projets Python sur GitHub et d'obtenir quelques idées.

Lorsque votre code s'agrandit et que vous ajoutez plus de bibliothèques, il est préférable de créer un dossier de test dans le même répertoire que vous avez setup.py et de refléter la structure de votre répertoire de projet pour chaque type de test (unittest, intégration, ...)

Par exemple, si vous avez une structure de répertoires comme:

myPackage/
    myapp/
       moduleA/
          __init__.py
          module_A.py
       moduleB/
          __init__.py
          module_B.py
setup.py

Après avoir ajouté le dossier de test, vous aurez une structure de répertoires comme:

myPackage/
    myapp/
       moduleA/
          __init__.py
          module_A.py
       moduleB/
          __init__.py
          module_B.py
test/
   unit/
      myapp/
         moduleA/
            module_A_test.py
         moduleB/
            module_B_test.py
   integration/
          myapp/
             moduleA/
                module_A_test.py
             moduleB/
                module_B_test.py
setup.py

De nombreux packages Python correctement écrits utilisent la même structure. Un très bon exemple est le paquet Boto. Vérifiez https://github.com/boto/boto

Arash
la source
1
Il est recommandé d'utiliser "tests" au lieu de "test", car "test" est un module intégré à Python. docs.python.org/2/library/test.html
brodul
Pas toujours .. par exemple, matplotlibil a sous matplotlib/lib/matplotlib/tests( github.com/matplotlib/matplotlib/tree/… ), sklearnil a sous scikitelearn/sklearn/tests( github.com/scikit-learn/scikit-learn/tree/master/sklearn/tests )
alpha_989
7

Comment je le fais ...

Structure des dossiers:

project/
    src/
        code.py
    tests/
    setup.py

Setup.py pointe vers src / comme l'emplacement contenant mes modules de projets, puis je lance:

setup.py develop

Ce qui ajoute mon projet dans des packages de site, pointant vers ma copie de travail. Pour exécuter mes tests, j'utilise:

setup.py tests

En utilisant le testeur que j'ai configuré.

Dale Reidy
la source
Vous semblez avoir outrepassé le terme "module". La plupart des programmeurs Python penseraient probablement que le module est le fichier que vous avez appelé code.py. Il serait plus judicieux d'appeler le répertoire de premier niveau "projet".
blokeley
4

Je préfère le répertoire des tests de haut niveau. Cela signifie que les importations deviennent un peu plus difficiles. Pour cela j'ai deux solutions:

  1. Utilisez setuptools. Ensuite , vous pouvez passer test_suite='tests.runalltests.suite'dans setup(), et peut exécuter les tests simplement:python setup.py test
  2. Définissez PYTHONPATH lors de l'exécution des tests: PYTHONPATH=. python tests/runalltests.py

Voici comment ces éléments sont pris en charge par le code dans M2Crypto:

Si vous préférez exécuter des tests avec nosetests, vous devrez peut-être faire quelque chose d'un peu différent.

bstpierre
la source
3

Nous utilisons

app/src/code.py
app/testing/code_test.py 
app/docs/..

Dans chaque fichier de test que nous insérons ../src/dans sys.path. Ce n'est pas la meilleure solution mais ça marche. Je pense que ce serait génial si quelqu'un venait avec quelque chose comme maven en java qui vous donne des conventions standard qui fonctionnent, peu importe le projet sur lequel vous travaillez.

André
la source
1

Si les tests sont simples, mettez-les simplement dans la docstring - la plupart des frameworks de test pour Python pourront utiliser cela:

>>> import module
>>> module.method('test')
'testresult'

Pour d'autres tests plus impliqués, je les mettrais dans ../tests/test_module.pyou dans tests/test_module.py.


la source
1

En C #, j'ai généralement séparé les tests dans un assemblage séparé.

En Python - jusqu'à présent - j'ai eu tendance à écrire des doctests, où le test se trouve dans la docstring d'une fonction, ou à les mettre dans le if __name__ == "__main__"bloc en bas du module.

George V. Reilly
la source
0

Lors de l'écriture d'un package appelé "foo", je mettrai les tests unitaires dans un package séparé "foo_test". Les modules et sous-packages auront alors le même nom que le module de package SUT. Par exemple, les tests pour un module foo.xy se trouvent dans foo_test.xy Les fichiers __init__.py de chaque package de test contiennent alors une suite AllTests qui inclut toutes les suites de test du package. setuptools fournit un moyen pratique de spécifier le package de test principal, de sorte qu'après "développement de python setup.py", vous pouvez simplement utiliser "test de python setup.py" ou "python setup.py test -s foo_test.x.SomeTestSuite" dans le juste une suite spécifique.

Sebastian Rittau
la source
0

J'ai mis mes tests dans le même répertoire que le code sous test (CUT); pour foo.pyles tests seront en foo_ut.pyou similaire. (Je modifie le processus de découverte de test pour les trouver.)

Cela place les tests juste à côté du code dans une liste de répertoires, ce qui rend évident que les tests sont là et rend l'ouverture des tests aussi simple que possible lorsqu'ils sont dans un fichier séparé. (Pour les éditeurs de ligne de commande vim foo*et lorsque vous utilisez un navigateur de système de fichiers graphique, cliquez simplement sur le fichier CUT, puis sur le fichier de test immédiatement adjacent.)

Comme d' autres l'ont souligné , cela facilite également la refactorisation et l'extraction du code pour une utilisation ailleurs si cela s'avère nécessaire.

Je n'aime vraiment pas l'idée de placer les tests dans une arborescence de répertoires complètement différente; pourquoi rendre plus difficile que nécessaire pour les développeurs d'ouvrir les tests lorsqu'ils ouvrent le fichier avec la CUT? Ce n'est pas comme si la grande majorité des développeurs sont si désireux d'écrire ou de peaufiner les tests qu'ils ignoreront tout obstacle à cela, au lieu d'utiliser la barrière comme excuse. (Bien au contraire, selon mon expérience; même lorsque vous faites en sorte que cela soit aussi simple que possible, je connais de nombreux développeurs qui ne peuvent pas se soucier d'écrire des tests.)

cjs
la source
-2

J'ai récemment commencé à programmer en Python, donc je n'ai pas encore vraiment eu la chance de découvrir les meilleures pratiques. Mais, j'ai écrit un module qui va et trouve tous les tests et les exécute.

Donc j'ai:

app /
 appfile.py
tester/
 appfileTest.py

Je vais devoir voir comment ça se passe au fur et à mesure que je progresse vers des projets plus importants.

quamrana
la source
4
Salut, le camelCase est un peu bizarre dans le monde du python.
Stuart Axon