J'essaie de comprendre comment obtenir python setup.py test
pour exécuter l'équivalent de python -m unittest discover
. Je ne souhaite pas utiliser de script run_tests.py et je ne souhaite utiliser aucun outil de test externe (tel que nose
ou py.test
). Ce n'est pas grave si la solution ne fonctionne que sur Python 2.7.
Dans setup.py
, je pense que je dois ajouter quelque chose aux champs test_suite
et/ou test_loader
dans config, mais je ne parviens pas à trouver une combinaison qui fonctionne correctement:
config = {
'name': name,
'version': version,
'url': url,
'test_suite': '???',
'test_loader': '???',
}
Est-ce possible d'utiliser uniquement unittest
intégré à python 2.7?
Pour votre information, la structure de mon projet ressemble à ceci:
project/
package/
__init__.py
module.py
tests/
__init__.py
test_module.py
run_tests.py <- I want to delete this
setup.py
Update: Ceci est possible avec unittest2
mais je veux trouver quelque chose d'équivalent en utilisant seulement unittest
De https://pypi.python.org/pypi/unittest2
unittest2 inclut un collecteur de test très basique compatible avec setuptools. Spécifiez test_suite = 'unittest2.collector' dans votre fichier setup.py. Cela lance la découverte du test avec les paramètres par défaut du répertoire contenant setup.py. Il est donc peut-être plus utile comme exemple (voir unittest2/collector.py).
Pour le moment, j'utilise simplement un script appelé run_tests.py
, mais j'espère pouvoir m'en débarrasser en passant à une solution utilisant uniquement python setup.py test
.
Voici le run_tests.py
que j'espère supprimer:
import unittest
if __== '__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)
Si vous utilisez py27 + ou py32 +, la solution est simple:
test_suite="tests",
De Construction et distribution de packages avec Setuptools (c'est moi qui souligne):
suite de tests
Chaîne nommant une sous-classe unittest.TestCase (ou un package ou module Contenant un ou plusieurs d'entre eux, ou une méthode de cette sous-classe), ou nommant une fonction qui peut être appelée sans arguments et renvoie un unestest.TestSuite.
Par conséquent, dans setup.py
, vous ajouteriez une fonction qui renvoie un TestSuite:
import unittest
def my_test_suite():
test_loader = unittest.TestLoader()
test_suite = test_loader.discover('tests', pattern='test_*.py')
return test_suite
Ensuite, vous spécifierez la commande setup
comme suit:
setup(
...
test_suite='setup.my_test_suite',
...
)
Vous n'avez pas besoin de config pour que cela fonctionne. Il y a fondamentalement deux manières de le faire:
Le moyen rapide
Renommez votre test_module.py
en module_test.py
(en gros, ajoutez _test
en tant que suffixe aux tests d'un module particulier), et python le trouvera automatiquement. Assurez-vous simplement d'ajouter ceci à setup.py
:
from setuptools import setup, find_packages
setup(
...
test_suite = 'tests',
...
)
Le long chemin
Voici comment le faire avec votre structure de répertoire actuelle:
project/
package/
__init__.py
module.py
tests/
__init__.py
test_module.py
run_tests.py <- I want to delete this
setup.py
Sous tests/__init__.py
, vous souhaitez importer la unittest
et votre script de test unitaire test_module
, puis créer une fonction pour exécuter les tests. Dans tests/__init__.py
, tapez quelque chose comme ceci:
import unittest
import test_module
def my_module_suite():
loader = unittest.TestLoader()
suite = loader.loadTestsFromModule(test_module)
return suite
La classe TestLoader
a d'autres fonctions que loadTestsFromModule
. Vous pouvez exécuter dir(unittest.TestLoader)
pour voir les autres, mais celui-ci est le plus simple à utiliser.
Étant donné que votre structure de répertoires est telle, vous souhaiterez probablement que le test_module
puisse importer votre script module
. Vous l'avez peut-être déjà fait, mais au cas où ce ne serait pas le cas, vous pouvez inclure le chemin d'accès parent afin de pouvoir importer le module package
et le script module
. En haut de votre test_module.py
, tapez:
import os, sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import unittest
import package.module
...
Enfin, dans setup.py
, incluez le module tests
et exécutez la commande que vous avez créée, my_module_suite
:
from setuptools import setup, find_packages
setup(
...
test_suite = 'tests.my_module_suite',
...
)
Ensuite, vous lancez simplement python setup.py test
.
Voici un sample quelqu'un fait comme référence.
Une solution possible consiste simplement à étendre la commande test
à distutils
et setuptools
/distribute
. Cela semble être un kluge total et bien plus compliqué que je ne le préférerais, mais semble découvrir et exécuter correctement tous les tests de mon paquet lors de l'exécution de python setup.py test
. Je me retiens de choisir ceci comme réponse à ma question dans l'espoir que quelqu'un fournira une solution plus élégante :)
(Inspiré par https://docs.pytest.org/fr/latest/goodpractices.html#integrating-with-setuptools-python-setup-py-test-pytest-runner )
Exemple 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)
Le module unittest
de la bibliothèque standard de Python prend en charge la découverte (dans Python 2.7 et versions ultérieures et Python 3.2 et versions ultérieures). Si vous pouvez assumer ces versions minimales, vous pouvez simplement ajouter l'argument de ligne de commande discover
à la commande unittest
.
Seul un petit tweak est nécessaire pour 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,
},
)
Une autre solution moins qu'idéale légèrement inspirée par http://hg.python.org/unittest2/file/2b6411b9a838/unittest2/collector.py
Ajoutez un module qui retourne une TestSuite
de tests découverts. Configurez ensuite la configuration pour appeler ce module.
project/
package/
__init__.py
module.py
tests/
__init__.py
test_module.py
discover_tests.py
setup.py
Voici 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)
Et voici 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)
Cela ne supprimera pas run_tests.py, mais le fera fonctionner avec setuptools. Ajouter:
class Loader(unittest.TestLoader):
def loadTestsFromNames(self, names, _=None):
return self.discover(names[0])
Puis dans setup.py: (je suppose que vous faites quelque chose comme setup(**config)
)
config = {
...
'test_loader': 'run_tests:Loader',
'test_suite': '.', # your start_dir for discover()
}
Le seul inconvénient que je vois est qu’il plie la sémantique de loadTestsFromNames
, mais la commande de test setuptools est le seul consommateur et l’appelle de manière spécifiée par .