unit - testing en python
¿Cómo ejecuto todas las pruebas unitarias de Python en un directorio? (14)
Tengo un directorio que contiene mis pruebas unitarias de Python. Cada módulo de prueba de unidad tiene la forma de prueba _ *. Py . Estoy intentando crear un archivo llamado all_test.py que, adivinó, ejecutará todos los archivos en el formulario de prueba mencionado y devolverá el resultado. He intentado dos métodos hasta ahora; ambos han fracasado Mostraré los dos métodos y espero que alguien sepa cómo hacerlo correctamente.
Para mi primer intento valeroso, pensé: "Si solo importo todos mis módulos de prueba en el archivo y luego llamo a esta 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
Para mi segundo intento, pensé, bueno, tal vez intentaré hacer todo esto de 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 he run=1
, parece que debería run=2
, pero es progreso. Pero, ¿cómo paso y muestro el resultado a main? O, ¿cómo básicamente lo hago funcionar para que pueda ejecutar este archivo y, al hacerlo, ejecutar todas las pruebas de unidad en este directorio?
Basándome 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 .
para *Tests.py
archivos que luego se cargan. Espera que cada *Tests.py
contenga una sola clase *Tests(unittest.TestCase)
que se cargan y se ejecutan una tras otra.
Esto funciona con la anidación profunda y arbitraria de directorios / módulos, pero cada directorio intermedio debe contener al menos un archivo __init__.py
vacío. Esto permite que la prueba cargue los módulos anidados al reemplazar las barras inclinadas (o barras invertidas) por puntos (vea replace_slash_by_dot
).
Bueno, al estudiar el código de arriba un poco (específicamente usando TextTestRunner
y TextTestRunner
), pude acercarme bastante. Finalmente, solucioné mi código al pasar todas las suites de prueba a un solo constructor de suites, en lugar de agregarlas "manualmente", lo que solucionó mis otros problemas. Así que 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 es más fácil usar solo la nariz que hacer esto, pero eso no es lo importante.
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 la prueba recursiva a través de la línea de comando está incorporada.
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 la prueba de la unidad python 2.7 o python 3.x.
Debido a que el descubrimiento de prueba parece ser un tema completo, hay un marco dedicado para probar el descubrimiento:
Más información aquí: https://wiki.python.org/moin/PythonTestingToolsTaxonomy
En Python 3, si está usando unittest.TestCase
y tiene un archivo __init__.py
vacío (o de otro modo) en su directorio de prueba, 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.
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 envolverse en un conjunto de pruebas de prueba de unidad mediante una función, una clase o método TestCase o un módulo o paquete que
TestCase
clasesTestCase
. Si la suite nombrada es un módulo, y el módulo tiene una funciónadditional_tests()
, se llama y el resultado (que debe ser una prueba deunittest.TestSuite
.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 dile dónde está tu paquete de prueba de raíz, como:
setup(
# ...
test_suite = ''somepkg.test''
)
Y ejecute 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, porque el discover
utiliza la importación de archivos. A pesar de que soporta top_level_dir
opcional, pero tuve algunos errores de recursión infinitos. Por lo tanto, una solución simple para un código no empaquetado es colocar lo siguiente en __init__.py
de su paquete de prueba (ver 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
Este es mi enfoque al crear 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())
Por motivos de simplicidad, disculpe mis estándares de codificación no PEP8 .
Luego, puede crear la clase BaseTest para componentes comunes para todas sus pruebas, de modo que cada una de sus pruebas simplemente se vería así:
from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
def test_foo(self):
driver = self.driver
driver.get(self.base_url + "/")
Para ejecutar, simplemente especifique pruebas como parte de los argumentos de la línea de comando, por ejemplo:
./run_tests.py -h http://example.com/ tests/**/*.py
Este script BASH ejecutará el directorio de prueba de prueba de python desde CUALQUIER LUGAR en el sistema de archivos, sin importar en qué directorio de trabajo se encuentre: su directorio de trabajo siempre estará donde se encuentre ese directorio de test
.
TODAS LAS PRUEBAS, $ PWD independientes
El módulo Python de unittest es sensible a su directorio actual, a menos que le diga dónde (usando la opción discover -s
).
Esto es útil cuando se mantiene en el directorio de trabajo ./src
o ./example
y necesita una prueba de unidad general rápida:
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"
python -m unittest discover -s "$readlink"/test -v
PRUEBAS SELECCIONADAS, $ PWD independientes
runone.py
este archivo de utilidad: runone.py
y 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 un archivo de test/__init__.py
para cargar su paquete / sobrecarga de memoria durante la producción.
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)
He utilizado el método de discover
y una sobrecarga de load_tests
para lograr este resultado en un número de líneas de código (mínimo, creo):
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 fives algo así como
Ran 27 tests in 0.187s
OK
Intenté varios enfoques, pero todos parecen defectuosos o tengo que maquillar un código, eso es molesto. Pero hay una forma 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.
Podría usar un corredor de prueba que haría esto por usted. nose es muy buena por ejemplo. Cuando se ejecute, encontrará pruebas en el árbol actual y las ejecutará.
Actualizado:
Aquí hay un código de mis días anteriores 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)
Si desea ejecutar todas las pruebas de varias clases de casos de prueba y está feliz de especificarlas explícitamente, puede hacerlo de la siguiente manera:
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 uclid
es mi proyecto y TestSymbols
y TestPatterns
son subclases de TestCase
.
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 elige Run 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 all
en mi directorio de prueba. Si ejecuto este módulo como una prueba de unidad de LiClipse, se ejecutan todas las pruebas. Si le pido que repita solo pruebas específicas o fallidas, entonces solo se ejecutan esas pruebas. Tampoco interfiere con mi corredor de prueba de la línea de comandos (pruebas), se ignora.
Es posible que deba cambiar los argumentos para discover
según la configuración de su proyecto.