web-dev-qa-db-fra.com

Comment exécuter unittest discover à partir de "python setup.py test"?

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)
63
cdwilson

Si vous utilisez py27 + ou py32 +, la solution est simple:

test_suite="tests",
36
saschpe

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',
    ...
)
33

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.

15
antimatter

Une solution possible consiste simplement à étendre la commande test à distutilset 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)
4
cdwilson

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,
    },
)
3
CryingCyclops

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)
2
cdwilson

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 .

0
jwelsh