Estoy tratando de averiguar cómo python setup.py test
ejecutar el equivalente de python -m unittest discover
. No quiero usar un script run_tests.py y no quiero usar ninguna herramienta de prueba externa (como nose
o py.test
). Está bien si la solución solo funciona en Python 2.7.
En setup.py
, creo que necesito agregar algo a los campos test_suite
y / o test_loader
en la configuración, pero parece que no puedo encontrar una combinación que funcione correctamente:
config = {
'name': name,
'version': version,
'url': url,
'test_suite': '???',
'test_loader': '???',
}
¿Es esto posible usando solo unittest
construido en Python 2.7?
FYI, la estructura de mi proyecto se ve así:
project/
package/
__init__.py
module.py
tests/
__init__.py
test_module.py
run_tests.py <- I want to delete this
setup.py
Actualización : esto es posible con unittest2
pero quiero encontrar algo equivalente usando solounittest
De https://pypi.python.org/pypi/unittest2
unittest2 incluye un colector de pruebas compatible con setuptools muy básico. Especifique test_suite = 'unittest2.collector' en su setup.py. Esto inicia el descubrimiento de pruebas con los parámetros predeterminados del directorio que contiene setup.py, por lo que quizás sea más útil como ejemplo (consulte unittest2 / collector.py).
Por ahora, solo estoy usando un script llamado run_tests.py
, pero espero poder deshacerme de esto moviéndome a una solución que solo use python setup.py test
.
Aquí está el run_tests.py
que espero eliminar:
import unittest
if __name__ == '__main__':
# use the default shared TestLoader instance
test_loader = unittest.defaultTestLoader
# use the basic test runner that outputs to sys.stderr
test_runner = unittest.TextTestRunner()
# automatically discover all tests in the current dir of the form test*.py
# NOTE: only works for python 2.7 and later
test_suite = test_loader.discover('.')
# run the test suite
test_runner.run(test_suite)
Respuestas:
Si usa py27 + o py32 +, la solución es bastante simple:
test_suite="tests",
fuente
test_*.py
. Además, descubrí que en realidad buscará recursivamente a través del directorio dado para encontrar cualquier clase que se extiendaunittest.TestCast
. Esto es extremadamente útil si tiene una estructura de directorio en la que tienetests/first_batch/test_*.py
ytests/second_batch/test_*.py
. Simplemente puede especificartest_suite="tests",
y recogerá todo de forma recursiva. Tenga en cuenta que cada directorio anidado deberá tener un__init__.py
archivo.De la creación y distribución de paquetes con herramientas de configuración (el énfasis es mío):
Por lo tanto,
setup.py
agregaría una función que devuelva un TestSuite:import unittest def my_test_suite(): test_loader = unittest.TestLoader() test_suite = test_loader.discover('tests', pattern='test_*.py') return test_suite
Luego, especificaría el comando de la
setup
siguiente manera:setup( ... test_suite='setup.my_test_suite', ... )
fuente
__init__.py
carpeta de pruebas y haciendo referencia a eso.setup()
función dentro delif __name__ == '__main__':
bloque en elsetup.py
script. La primera vez que se ejecuta el script de configuración, por lo que se llamará al bloque if; la segunda vez, el script de configuración se importará como un módulo, por lo que no se llamará al bloque if.test_suite
parámetro en absoluto, pero "python setup.py test" todavía funciona bien para mí. Eso es diferente de lo que dice la documentación : "Si no configuró un test_suite en su llamada setup () y no proporciona una opción --test-suite, se producirá un error". ¿Alguna idea?No necesita configuración para que esto funcione. Básicamente, hay dos formas principales de hacerlo:
La forma rapida
Cambie el nombre de su
test_module.py
amodule_test.py
(básicamente agregue_test
como sufijo a las pruebas para un módulo en particular), y Python lo encontrará automáticamente. Solo asegúrate de agregar esto asetup.py
:from setuptools import setup, find_packages setup( ... test_suite = 'tests', ... )
El largo camino
A continuación, le indicamos cómo hacerlo con su estructura de directorio actual:
En
tests/__init__.py
, desea importar elunittest
y su script de prueba unitariatest_module
y luego crear una función para ejecutar las pruebas. Entests/__init__.py
, escriba algo como esto:import unittest import test_module def my_module_suite(): loader = unittest.TestLoader() suite = loader.loadTestsFromModule(test_module) return suite
La
TestLoader
clase tiene otras funciones ademásloadTestsFromModule
. Puedes correrdir(unittest.TestLoader)
para ver los demás, pero este es el más sencillo de usar.Dado que la estructura de su directorio es tal, probablemente querrá
test_module
que pueda importar sumodule
script. Es posible que ya lo haya hecho, pero en caso de que no lo haya hecho, puede incluir la ruta principal para poder importar elpackage
módulo y elmodule
script. En la parte superior de tutest_module.py
, escribe:import os, sys sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) import unittest import package.module ...
Luego, finalmente,
setup.py
incluya eltests
módulo y ejecute el comando que creómy_module_suite
:from setuptools import setup, find_packages setup( ... test_suite = 'tests.my_module_suite', ... )
Entonces simplemente corre
python setup.py test
.Aquí hay una muestra que alguien hizo como referencia.
fuente
Una posible solución es simplemente extender el
test
comando paradistutils
ysetuptools
/distribute
. Esto parece un kluge total y mucho más complicado de lo que preferiría, pero parece descubrir y ejecutar correctamente todas las pruebas en mi paquete al ejecutarsepython setup.py test
. Estoy esperando seleccionar esto como la respuesta a mi pregunta con la esperanza de que alguien brinde una solución más elegante :)(Inspirado en https://docs.pytest.org/en/latest/goodpractices.html#integrating-with-setuptools-python-setup-py-test-pytest-runner )
Ejemplo
setup.py
:try: from setuptools import setup except ImportError: from distutils.core import setup def discover_and_run_tests(): import os import sys import unittest # get setup.py directory setup_file = sys.modules['__main__'].__file__ setup_dir = os.path.abspath(os.path.dirname(setup_file)) # use the default shared TestLoader instance test_loader = unittest.defaultTestLoader # use the basic test runner that outputs to sys.stderr test_runner = unittest.TextTestRunner() # automatically discover all tests # NOTE: only works for python 2.7 and later test_suite = test_loader.discover(setup_dir) # run the test suite test_runner.run(test_suite) try: from setuptools.command.test import test class DiscoverTest(test): def finalize_options(self): test.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): discover_and_run_tests() except ImportError: from distutils.core import Command class DiscoverTest(Command): user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): discover_and_run_tests() config = { 'name': 'name', 'version': 'version', 'url': 'http://example.com', 'cmdclass': {'test': DiscoverTest}, } setup(**config)
fuente
Otra solución menos que ideal ligeramente inspirada en http://hg.python.org/unittest2/file/2b6411b9a838/unittest2/collector.py
Agregue un módulo que devuelva una
TestSuite
de las pruebas descubiertas. Luego configure el programa de instalación para llamar a ese módulo.Aquí está
discover_tests.py
:import os import sys import unittest def additional_tests(): setup_file = sys.modules['__main__'].__file__ setup_dir = os.path.abspath(os.path.dirname(setup_file)) return unittest.defaultTestLoader.discover(setup_dir)
Y aqui esta
setup.py
:try: from setuptools import setup except ImportError: from distutils.core import setup config = { 'name': 'name', 'version': 'version', 'url': 'http://example.com', 'test_suite': 'discover_tests', } setup(**config)
fuente
El
unittest
módulo de biblioteca estándar de Python admite el descubrimiento (en Python 2.7 y posterior, y Python 3.2 y posterior). Si puede asumir esas versiones mínimas, simplemente puede agregar eldiscover
argumento de la línea de comando alunittest
comando.Solo se necesita un pequeño ajuste para
setup.py
:import setuptools.command.test from setuptools import (find_packages, setup) class TestCommand(setuptools.command.test.test): """ Setuptools test command explicitly using test discovery. """ def _test_args(self): yield 'discover' for arg in super(TestCommand, self)._test_args(): yield arg setup( ... cmdclass={ 'test': TestCommand, }, )
fuente
Esto no eliminará run_tests.py, pero hará que funcione con setuptools. Añadir:
class Loader(unittest.TestLoader): def loadTestsFromNames(self, names, _=None): return self.discover(names[0])
Luego, en setup.py: (supongo que estás haciendo algo como
setup(**config)
)config = { ... 'test_loader': 'run_tests:Loader', 'test_suite': '.', # your start_dir for discover() }
El único inconveniente que veo es que está doblando la semántica de
loadTestsFromNames
, pero el comando de prueba de setuptools es el único consumidor y lo llama de una manera específica .fuente