web-dev-qa-db-fra.com

Comment exécuter tous les tests d'unité Python dans un répertoire?

J'ai un répertoire qui contient mes tests unitaires Python. Chaque module de test unitaire est de la forme test _ *. Py. J'essaie de créer un fichier appelé all_test.py qui, vous l'aurez deviné, exécutera tous les fichiers dans le formulaire de test susmentionné et renverra le résultat. J'ai essayé deux méthodes jusqu'à présent. les deux ont échoué. Je vais montrer les deux méthodes et j'espère que quelqu'un sait comment le faire correctement.

Pour ma première tentative courageuse, je me suis dit "Si je viens d'importer tous mes modules de test dans le fichier, puis d'appeler cette unittest.main() doodad, cela fonctionnera, n'est-ce pas?" Eh bien, je me suis trompé.

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]

if __== "__main__":
     unittest.main()

Cela n'a pas fonctionné, le résultat obtenu est le suivant:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

Pour mon deuxième essai, j’ai pensé, d’accord, peut-être que je vais essayer de faire tout ce processus d’essai de manière plus "manuelle". J'ai donc essayé de le faire ci-dessous:

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite 

result = unittest.TestResult()
testSuite.run(result)
print result

#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __== "__main__":
    unittest.main()

Cela aussi n'a pas fonctionné, mais cela semble si proche!

$ python all_test.py 
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

Je semble avoir une suite quelconque et je peux exécuter le résultat. Je suis un peu préoccupé par le fait qu'il est dit que je n'ai que run=1, il semble que cela devrait être run=2, mais c'est un progrès. Mais comment puis-je passer et afficher le résultat au principal? Ou comment puis-je le faire fonctionner pour que je puisse simplement exécuter ce fichier et, ce faisant, exécuter tous les tests unitaires de ce répertoire?

255
Stephen Cagle

Vous pouvez utiliser un testeur qui le ferait pour vous. nez est très bon par exemple. Une fois exécuté, il trouvera des tests dans l’arborescence actuelle et les exécutera.

Mis à jour:

Voici un code de mes jours d'avant le nez. Vous ne voulez probablement pas la liste explicite des noms de modules, mais peut-être que le reste vous sera utile.

testmodules = [
    'cogapp.test_makefiles',
    'cogapp.test_whiteutils',
    'cogapp.test_cogapp',
    ]

suite = unittest.TestSuite()

for t in testmodules:
    try:
        # If the module defines a suite() function, call it to get the suite.
        mod = __import__(t, globals(), locals(), ['suite'])
        suitefn = getattr(mod, 'suite')
        suite.addTest(suitefn())
    except (ImportError, AttributeError):
        # else, just load all the test cases from the module.
        suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))

unittest.TextTestRunner().run(suite)
101
Ned Batchelder

Avec Python 2.7 et versions ultérieures, vous n'avez pas besoin d'écrire de nouveau code ni d'utiliser des outils tiers pour le faire. l'exécution récursive du test via la ligne de commande est intégrée.

python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'

Vous pouvez en lire plus dans la documentation python 2.7 ou python 3.x unittest.

400
Travis Bear

Ceci est maintenant possible directement à partir de unittest: nittest.TestLoader.discover .

import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)

runner = unittest.TextTestRunner()
runner.run(suite)
56
slaughter98

Dans python 3, si vous utilisez _unittest.TestCase_:

  • Vous devez avoir un fichier vide (ou non) ___init__.py_ dans votre répertoire test (doit être nommé _test/_)
  • Vos fichiers de test dans _test/_ correspondent au modèle _test_*.py_. Ils peuvent se trouver dans un sous-répertoire sous _test/_, et ces sous-répertoires peuvent être nommés de manière quelconque.

Ensuite, vous pouvez exécuter tous les tests avec:

_python -m unittest
_

Terminé! Une solution moins de 100 lignes. Espérons qu'un autre _ débutant python gagne du temps en trouvant ceci.

52
tmck-code

En étudiant un peu le code ci-dessus (en utilisant spécifiquement TextTestRunner et defaultTestLoader), j'ai pu me rapprocher de plus près. Finalement, j'ai corrigé mon code en passant simplement toutes les suites de tests à un seul constructeur de suites, plutôt que de les ajouter "manuellement", ce qui corrigeait mes autres problèmes. Alors voici ma solution.

import glob
import unittest

test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)

Oui, il est probablement plus facile d’utiliser le nez que de le faire, mais c’est tout.

30
Stephen Cagle

Si vous souhaitez exécuter tous les tests de différentes classes de cas de test et que vous souhaitez les spécifier explicitement, vous pouvez le faire comme suit:

from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns

if __== "__main__":

    loader = TestLoader()
    tests = [
        loader.loadTestsFromTestCase(test)
        for test in (TestSymbols, TestPatterns)
    ]
    suite = TestSuite(tests)

    runner = TextTestRunner(verbosity=2)
    runner.run(suite)

uclid est mon projet et TestSymbols et TestPatterns sont des sous-classes de TestCase.

24
demented hedgehog

J'ai utilisé la méthode discover et une surcharge de load_tests pour obtenir ce résultat en un nombre (minimal, je pense) de lignes de code:

def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
    suite = TestSuite()
    for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
        for test_suite in all_test_suite:
            suite.addTests(test_suite)
    return suite

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

Exécution sur cinq ans quelque chose comme

Ran 27 tests in 0.187s
OK
13
rds

J'ai essayé différentes approches mais toutes semblent imparfaites ou je dois maquiller du code, c'est embêtant. Mais il existe un moyen pratique sous Linux, qui consiste simplement à trouver tous les tests selon un certain modèle, puis à les invoquer un par un.

find . -name 'Test*py' -exec python '{}' \;

et surtout, ça marche vraiment.

7
zinking

Dans le cas d'une bibliothèque ou d'une application empaquetée , vous ne souhaitez pas le faire. setuptoolsle fera pour vous .

Pour utiliser cette commande, les tests de votre projet doivent être encapsulés dans une suite de tests unittest par une fonction, une classe ou une méthode TestCase ou un module ou package contenant des classes TestCase. Si la suite nommée est un module et que le module possède une fonction additional_tests(), il est appelé et le résultat (qui doit être un unittest.TestSuite) est ajouté aux tests à exécuter. Si la suite nommée est un package, des sous-modules et sous-packages sont ajoutés de manière récursive à la suite de tests globale .

Dites-lui simplement où se trouve votre paquet de test racine, par exemple:

setup(
    # ...
    test_suite = 'somepkg.test'
)

Et lancez python setup.py test.

La découverte basée sur les fichiers peut être problématique dans Python 3, sauf si vous évitez les importations relatives dans votre suite de tests, car discover utilise l'importation de fichier. Même s'il supporte l'option optionnelle top_level_dir, mais j'ai eu des erreurs de récursion infinies. Ainsi, une solution simple pour un code non empaqueté consiste à mettre les éléments suivants dans __init__.py de votre package de test (voir protocole load_tests ).

import unittest

from . import foo, bar


def load_tests(loader, tests, pattern):
    suite = unittest.TestSuite()
    suite.addTests(loader.loadTestsFromModule(foo))
    suite.addTests(loader.loadTestsFromModule(bar))

    return suite
6
saaj

J'utilise PyDev/LiClipse et je n'ai pas vraiment compris comment exécuter tous les tests en même temps à partir de l'interface graphique. (edit: vous faites un clic droit sur le dossier de test racine et choisissez Run as -> Python unit-test

Voici ma solution de contournement actuelle:

import unittest

def load_tests(loader, tests, pattern):
    return loader.discover('.')

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

Je mets ce code dans un module appelé all dans mon répertoire de test. Si je lance ce module comme unittest de LiClipse, tous les tests sont exécutés. Si je demande à ne répéter que des tests spécifiques ou ayant échoué, seuls ces tests sont exécutés. Cela n'interfère pas non plus avec mon testeur de ligne de commande (nosetest) - il est ignoré.

Vous devrez peut-être modifier les arguments en discover en fonction de la configuration de votre projet.

4
Dunes

Basé sur la réponse de Stephen Cagle , j'ai ajouté le support pour les modules de test imbriqués.

import fnmatch
import os
import unittest

def all_test_modules(root_dir, pattern):
    test_file_names = all_files_in(root_dir, pattern)
    return [path_to_module(str) for str in test_file_names]

def all_files_in(root_dir, pattern):
    matches = []

    for root, dirnames, filenames in os.walk(root_dir):
        for filename in fnmatch.filter(filenames, pattern):
            matches.append(os.path.join(root, filename))

    return matches

def path_to_module(py_file):
    return strip_leading_dots( \
        replace_slash_by_dot(  \
            strip_extension(py_file)))

def strip_extension(py_file):
    return py_file[0:len(py_file) - len('.py')]

def replace_slash_by_dot(str):
    return str.replace('\\', '.').replace('/', '.')

def strip_leading_dots(str):
    while str.startswith('.'):
       str = str[1:len(str)]
    return str

module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname 
    in module_names]

testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)

Le code recherche dans tous les sous-répertoires de . les fichiers *Tests.py qui sont ensuite chargés. Il s'attend à ce que chaque *Tests.py contienne une seule classe *Tests(unittest.TestCase) qui est chargée à son tour et exécutée l'une après l'autre.

Cela fonctionne avec une imbrication profonde arbitraire de répertoires/modules, mais chaque répertoire doit contenir au moins un fichier __init__.py vide. Cela permet au test de charger les modules imbriqués en remplaçant les barres obliques (ou barres obliques inverses) par des points (voir replace_slash_by_dot).

2
Peter Kofler

Ce script BASH exécute le répertoire de test python unittest de ANYWHERE dans le système de fichiers, quel que soit le répertoire de travail dans lequel vous vous trouvez: son répertoire de travail se trouve toujours à l'emplacement de ce répertoire test.

TOUS TESTS, $ PWD indépendant

le module unittest Python est sensible à votre répertoire actuel, sauf si vous lui indiquez où (avec l'option discover -s).

Ceci est utile lorsque vous restez dans le répertoire de travail ./src ou ./example et que vous avez besoin d'un test unitaire global rapide:

#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

python -m unittest discover -s "$readlink"/test -v

ESSAIS SÉLECTIONNÉS, $ PWD indépendants

Je nomme ce fichier d’utilitaire: runone.py et l’utilise comme ceci:

runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

(cd "$dirname"/test; python -m unittest $1)

Nul besoin d'un fichier test/__init__.py pour surcharger votre paquet/mémoire supplémentaire pendant la production.

1
John Greene

Comme la découverte de test semble être un sujet complet, il existe un cadre dédié à la découverte de test:

Plus de lecture ici: https://wiki.python.org/moin/PythonTestingToolsTaxonomy

0
Bactisme