¿Cómo ejecuto todas las pruebas unitarias de Python en un directorio?


315

Tengo un directorio que contiene mis pruebas unitarias de Python. Cada módulo de prueba unitaria tiene la forma test _ *. Py . Estoy intentando crear un archivo llamado all_test.py que, lo adivinaste , ejecutará todos los archivos en el formulario de prueba mencionado anteriormente y devolverá el resultado. He probado dos métodos hasta ahora; Ambos han fallado. Mostraré los dos métodos, y espero que alguien sepa cómo hacerlo correctamente.

Para mi primer intento valiente, pensé "Si solo importo todos mis módulos de prueba en el archivo y luego llamo a este unittest.main()doodad, funcionará, ¿verdad?" Bueno, resulta que estaba equivocado.

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 __name__ == "__main__":
     unittest.main()

Esto no funcionó, el resultado que obtuve fue:

$ python all_test.py 

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

OK

Sin embargo, para mi segundo intento, bueno, tal vez intentaré hacer todo este proceso de prueba de una manera más "manual". Así que intenté hacer eso a continuación:

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 __name__ == "__main__":
    unittest.main()

Esto tampoco funcionó, ¡pero parece tan cerca!

$ 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

Parece que tengo una suite de algún tipo, y puedo ejecutar el resultado. Estoy un poco preocupado por el hecho de que dice que solo tengo run=1, parece que debería ser run=2, pero es un progreso. Pero, ¿cómo paso y visualizo el resultado a main? ¿O cómo básicamente lo hago funcionar para poder ejecutar este archivo y, al hacerlo, ejecutar todas las pruebas unitarias en este directorio?


1
Pase a la respuesta de Travis si está usando Python 2.7+
Rocky

¿alguna vez intentaste ejecutar las pruebas desde un objeto de instancia de prueba?
Pinocho

Vea esta respuesta para una solución con una estructura de archivo de ejemplo.
Derek Soike

Respuestas:


477

Con Python 2.7 y versiones posteriores, no tiene que escribir código nuevo ni usar herramientas de terceros para hacer esto; La ejecución de prueba recursiva a través de la línea de comando está incorporada. Ponga un __init__.pyen su directorio de prueba y:

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

Puede leer más en la documentación de Python 2.7 o Python 3.x unittest.


11
problemas incluyen: ImportError: directorio de inicio no es importable:
zinking

66
Al menos con Python 2.7.8 en Linux, ni la invocación de línea de comando me da recurrencia. Mi proyecto tiene varios subproyectos cuyas pruebas unitarias viven en los respectivos directorios "unit_tests / <subproject> / python /". Si especifico dicha ruta, las pruebas unitarias para ese subproyecto se ejecutan, pero con solo "unit_tests" como argumento del directorio de prueba, no se encuentran pruebas (en lugar de todas las pruebas para todos los subproyectos, como esperaba). Alguna pista?
user686249

66
Acerca de la recursividad: el primer comando sin un <test_directory> está predeterminado en "." y recurre a submódulos . Es decir, todos los directorios de pruebas que desea descubrir deben tener un init .py. Si lo hacen, serán encontrados por el comando de descubrimiento. Solo lo intenté, funcionó.
Emil Stenström

Esto funcionó para mí. Tengo una carpeta de pruebas con cuatro archivos, ejecuto esto desde mi terminal de Linux, cosas geniales.
JasTonAChair

55
¡Gracias! ¿Por qué no es esta la respuesta aceptada? En mi opinión, la mejor respuesta es siempre la que no requiere dependencias externas ...
Jonathan Benn

108

Podrías usar un corredor de prueba que haría esto por ti. La nariz es muy buena, por ejemplo. Cuando se ejecuta, encontrará pruebas en el árbol actual y las ejecutará.

Actualizado:

Aquí hay un código de mis días previos a la nariz. Probablemente no desee la lista explícita de nombres de módulos, pero quizás el resto le sea útil.

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)

2
¿Es la ventaja de este enfoque sobre la importación explícita de todos sus módulos de prueba en un módulo test_all.py y llamar a unittest.main () que opcionalmente puede declarar un conjunto de pruebas en algunos módulos y no en otros?
Corey Porter el

1
Probé la nariz y funciona perfectamente. Fue fácil de instalar y ejecutar en mi proyecto. Incluso pude automatizarlo con unas pocas líneas de script, ejecutándose dentro de un virtualenv. +1 para la nariz!
Jesse Webb

No siempre es factible: a veces, importar la estructura del proyecto puede hacer que se confunda la nariz si intenta ejecutar las importaciones en los módulos.
chiffa

44
Tenga en cuenta que nose ha estado "en modo de mantenimiento durante los últimos años" y actualmente se recomienda usar nose2 , pytest o simplemente unittest / unittest2 para nuevos proyectos.
Kurt Peek el

¿alguna vez intentaste ejecutar las pruebas desde un objeto de instancia de prueba?
Pinocho

96

En Python 3, si estás usando unittest.TestCase:

  • Debe tener un __init__.pyarchivo vacío (o no) en su testdirectorio ( debe tener un nombre test/)
  • Sus archivos de prueba dentro test/coinciden con el patrón test_*.py. Pueden estar dentro de un subdirectorio debajo test/, y esos subdirectorios pueden nombrarse como cualquier cosa.

Luego, puede ejecutar todas las pruebas con:

python -m unittest

¡Hecho! Una solución de menos de 100 líneas. Esperemos que otro principiante de Python ahorre tiempo al encontrar esto.


3
Tenga en cuenta que, de forma predeterminada, solo busca pruebas en nombres de archivos que comienzan con "prueba"
Shawabawa

3
Eso es correcto, la pregunta original se refería al hecho de que "Cada módulo de prueba unitaria tiene la forma test _ *. Py.", Por lo que esta respuesta en respuesta directa. Ahora he informado que la respuesta sea más explícito
tmck código

1
Gracias, lo que me faltaba para usar la respuesta de Travis Bear.
Jeremy Cochoy

65

Esto ahora es posible directamente desde unittest: unittest.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)

3
También probé este método, tengo un par de pruebas, pero funciona perfectamente. ¡¡¡Excelente!!! Pero tengo curiosidad, solo tengo 4 pruebas. Juntos corren 0.032s, pero cuando uso este método para ejecutarlos todos, obtengo el resultado .... ---------------------------------------------------------------------- Ran 4 tests in 0.000s OK¿Por qué? La diferencia, ¿de dónde viene?
simkus

Tengo problemas para ejecutar un archivo que se ve así desde la línea de comandos. ¿Cómo se debe invocar?
Dustin Michels

python file.py
sacrificio98

1
Trabajado sin problemas! Simplemente configúrelo en su prueba / directorio y luego configure start_id = "./". En mi humilde opinión, esta respuesta es ahora (Python 3.7) la forma aceptada!
jjwdesign

Puede cambiar la última línea a ´res = runner.run (suite); sys.exit (0 si res.wasSuccessful () else 1) ´ si desea un código de salida correcto
Sadap

32

Bueno, al estudiar un poco el código anterior (específicamente usando TextTestRunnery defaultTestLoader), pude acercarme bastante. Finalmente, arreglé mi código simplemente pasando todas las suites de prueba a un solo constructor de suites, en lugar de agregarlas "manualmente", lo que solucionó mis otros problemas. Entonces aquí está mi solución.

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)

Sí, probablemente sea más fácil usar la nariz que hacer esto, pero eso no viene al caso.


bueno, funciona bien para el directorio actual, ¿cómo invocar el sub-directamente?
Larry Cai

Larry, mira la nueva respuesta ( stackoverflow.com/a/24562019/104143 ) para el descubrimiento de prueba recursiva
Peter Kofler,

¿alguna vez intentaste ejecutar las pruebas desde un objeto de instancia de prueba?
Pinocho

25

Si desea ejecutar todas las pruebas de varias clases de casos de prueba y está feliz de especificarlas explícitamente, puede hacerlo así:

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

if __name__ == "__main__":

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

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

donde uclidestá mi proyecto TestSymbolsy TestPatternsson subclases de TestCase.


De los documentos unittest.TestLoader : "Normalmente, no es necesario crear una instancia de esta clase; el módulo unittest proporciona una instancia que se puede compartir como unittest.defaultTestLoader". Además, dado que TestSuiteacepta un iterable como argumento, puede construir dicho iterable en un bucle para evitar repetirlo loader.loadTestsFromTestCase.
Alquimista de dos bits

@ Two-Bit Alchemist, su segundo punto en particular es bueno. Cambiaría el código para incluirlo, pero no puedo probarlo. (El primer mod haría que se pareciera demasiado a Java para mi gusto ... aunque me doy cuenta de que estoy siendo irracional (atorníllelos y sus nombres de variables de casos de camellos)).
erizo demente

Este es mi favorito, muy limpio. Pude empaquetar esto y convertirlo en un argumento en mi línea de comando regular.
MarkII

15

He utilizado el discovermétodo y una sobrecarga de load_testspara lograr este resultado en un número (mínimo, creo) de líneas de código:

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 __name__ == '__main__':
    unittest.main()

Ejecución en cinco años algo así como

Ran 27 tests in 0.187s
OK

esto está disponible solo para python2.7, supongo
Larry Cai

@larrycai Tal vez, generalmente estoy en Python 3, a veces Python 2.7. La pregunta no estaba vinculada a una versión específica.
rds

Estoy en Python 3.4 y Discover devuelve una suite, haciendo que el bucle sea redundante.
Dunas

Para el futuro de Larry: "Se agregaron muchas características nuevas a unittest en Python 2.7, incluido el descubrimiento de pruebas. Unittest2 le permite usar estas características con versiones anteriores de Python".
Alquimista de dos bits

8

Intenté varios enfoques, pero todos parecen defectuosos o tengo que inventar un código, eso es molesto. Pero hay una manera conveniente en Linux, que es simplemente encontrar cada prueba a través de cierto patrón y luego invocarlas una por una.

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

y lo más importante, definitivamente funciona.


7

En el caso de una biblioteca o aplicación empaquetada , no desea hacerlo. setuptools Lo hará por ti .

Para usar este comando, las pruebas de su proyecto deben estar envueltas en un unittestconjunto de pruebas ya sea por una función, una clase o método TestCase, o un módulo o paquete que contenga TestCaseclases. Si la suite nombrada es un módulo y el módulo tiene una additional_tests()función, se llama y el resultado (que debe ser a unittest.TestSuite) se agrega a las pruebas que se ejecutarán. Si la suite nombrada es un paquete, todos los submódulos y subpaquetes se agregan recursivamente a la suite de prueba general .

Solo dígale dónde está su paquete de prueba raíz, como:

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

Y ejecutar python setup.py test.

El descubrimiento basado en archivos puede ser problemático en Python 3, a menos que evite las importaciones relativas en su conjunto de pruebas, ya que discoverutiliza la importación de archivos. A pesar de que admite opcional top_level_dir, pero tuve algunos errores de recursión infinita. Entonces, una solución simple para un código no empaquetado es poner lo siguiente en __init__.pysu paquete de prueba (vea el protocolo 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

¡Buena respuesta, y se puede usar para automatizar la prueba antes de implementarla! Gracias
Arthur Clerc-Gherardi

4

Utilizo PyDev / LiClipse y realmente no he descubierto cómo ejecutar todas las pruebas a la vez desde la GUI. (editar: hace clic derecho en la carpeta de prueba raíz y eligeRun as -> Python unit-test

Esta es mi solución actual:

import unittest

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

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

Puse este código en un módulo llamado allen mi directorio de prueba. Si ejecuto este módulo como una prueba unitaria de LiClipse, se ejecutarán todas las pruebas. Si solo pido repetir pruebas específicas o fallidas, solo se ejecutan esas pruebas. Tampoco interfiere con mi corredor de prueba de línea de comandos (pruebas nasales): se ignora.

Es posible que deba cambiar los argumentos discoversegún la configuración de su proyecto.


Los nombres de todos los archivos de prueba y métodos de prueba deben comenzar con "test_". De lo contrario, el comando "Ejecutar como -> Prueba de unidad de Python" no los encontrará.
Stefan

2

Basado en la respuesta de Stephen Cagle , agregué soporte para módulos de prueba anidados.

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)

El código busca en todos los subdirectorios .de *Tests.pyarchivos que se cargan a continuación. Se espera que cada uno *Tests.pycontenga una sola clase *Tests(unittest.TestCase)que se carga a su vez y se ejecuta una tras otra.

Esto funciona con anidamiento profundo arbitrario de directorios / módulos, pero cada directorio intermedio debe contener __init__.pyal menos un archivo vacío . Esto permite que la prueba cargue los módulos anidados mediante la sustitución de barras (o barras invertidas) por puntos (ver replace_slash_by_dot).


2

Esta es una vieja pregunta, pero lo que funcionó para mí ahora (en 2019) es:

python -m unittest *_test.py

Todos mis archivos de prueba están en la misma carpeta que los archivos de origen y terminan en _test.



1

Este script de BASH ejecutará el directorio de prueba de prueba de unidad de Python desde CUALQUIER LUGAR del sistema de archivos, sin importar en qué directorio de trabajo se encuentre: su directorio de trabajo siempre estará donde testesté ubicado ese directorio.

TODAS LAS PRUEBAS, $ PWD independiente

El módulo de Python unittest es sensible a su directorio actual, a menos que le indique dónde (usando la discover -sopción).

Esto es útil cuando permanece en el directorio ./srco en el ./exampletrabajo y necesita una prueba general rápida de la unidad:

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

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

PRUEBAS SELECCIONADAS, independientes $ PWD

Nombre este archivo de utilidad: runone.pyy lo uso así:

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)

No es necesario que un test/__init__.pyarchivo cargue su paquete / sobrecarga de memoria durante la producción.


-3

Aquí está mi enfoque creando un contenedor para ejecutar pruebas desde la línea de comandos:

#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging

if __name__ == '__main__':
    # Parse arguments.
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-?", "--help",     action="help",                        help="show this help message and exit" )
    parser.add_argument("-v", "--verbose",  action="store_true", dest="verbose",  help="increase output verbosity" )
    parser.add_argument("-d", "--debug",    action="store_true", dest="debug",    help="show debug messages" )
    parser.add_argument("-h", "--host",     action="store",      dest="host",     help="Destination host" )
    parser.add_argument("-b", "--browser",  action="store",      dest="browser",  help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
    parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")
    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    # Load files from the arguments.
    for filename in args.files:
        exec(open(filename).read())

    # See: http://codereview.stackexchange.com/q/88655/15346
    def make_suite(tc_class):
        testloader = unittest.TestLoader()
        testnames = testloader.getTestCaseNames(tc_class)
        suite = unittest.TestSuite()
        for name in testnames:
            suite.addTest(tc_class(name, cargs=args))
        return suite

    # Add all tests.
    alltests = unittest.TestSuite()
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj) and name.startswith("FooTest"):
            alltests.addTest(make_suite(obj))

    # Set-up logger
    verbose = bool(os.environ.get('VERBOSE', args.verbose))
    debug   = bool(os.environ.get('DEBUG', args.debug))
    if verbose or debug:
        logging.basicConfig( stream=sys.stdout )
        root = logging.getLogger()
        root.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
        root.addHandler(ch)
    else:
        logging.basicConfig(stream=sys.stderr)

    # Run tests.
    result = unittest.TextTestRunner(verbosity=2).run(alltests)
    sys.exit(not result.wasSuccessful())

En aras de la simplicidad, disculpe mis estándares de codificación que no son PEP8 .

Luego puede crear la clase BaseTest para componentes comunes para todas sus pruebas, de modo que cada una de sus pruebas simplemente se vea así:

from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
    def test_foo(self):
        driver = self.driver
        driver.get(self.base_url + "/")

Para ejecutar, simplemente especifica las pruebas como parte de los argumentos de la línea de comandos, por ejemplo:

./run_tests.py -h http://example.com/ tests/**/*.py

2
La mayor parte de esta respuesta no tiene nada que ver con el descubrimiento de prueba (es decir, el registro, etc.). Stack Overflow es para responder preguntas, no para mostrar código no relacionado.
Corey Goldberg
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.