Estoy tratando de averiguar cómo python setup.py testejecutar 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 noseo 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_suitey / o test_loaderen 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 unittestconstruido 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 unittest2pero 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.pyque 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_*.pyytests/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__.pyarchivo.De la creación y distribución de paquetes con herramientas de configuración (el énfasis es mío):
Por lo tanto,
setup.pyagregarí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_suiteLuego, especificaría el comando de la
setupsiguiente manera:setup( ... test_suite='setup.my_test_suite', ... )fuente
__init__.pycarpeta de pruebas y haciendo referencia a eso.setup()función dentro delif __name__ == '__main__':bloque en elsetup.pyscript. 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_suitepará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.pyamodule_test.py(básicamente agregue_testcomo 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 elunittesty su script de prueba unitariatest_moduley 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 suiteLa
TestLoaderclase 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_moduleque pueda importar sumodulescript. Es posible que ya lo haya hecho, pero en caso de que no lo haya hecho, puede incluir la ruta principal para poder importar elpackagemódulo y elmodulescript. 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.pyincluya eltestsmó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
testcomando paradistutilsysetuptools/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
TestSuitede 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
unittestmó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 eldiscoverargumento de la línea de comando alunittestcomando.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