Как запустить unittest обнаружить из "теста python setup.py"?

Я пытаюсь выяснить, как получить python setup.py test для запуска эквивалента python -m unittest discover. Я не хочу использовать run_tests.py script, и я не хочу использовать внешние инструменты тестирования (например, nose или py.test). Это нормально, если решение работает только на python 2.7.

В setup.py, я думаю, мне нужно добавить что-то в поля test_suite и/или test_loader в config, но я не могу найти подходящую комбинацию:

config = {
    'name': name,
    'version': version,
    'url': url,
    'test_suite': '???',
    'test_loader': '???',
}

Возможно ли это, используя только unittest, встроенный в python 2.7?

FYI, моя структура проекта выглядит следующим образом:

project/
  package/
    __init__.py
    module.py
  tests/
    __init__.py
    test_module.py
  run_tests.py <- I want to delete this
  setup.py

Обновить. Это возможно с помощью unittest2, но я хочу найти что-то эквивалентное, используя только unittest

От https://pypi.python.org/pypi/unittest2

unittest2 включает в себя очень простой сборщик тестов, совместимый с setuptools. Укажите test_suite = 'unittest2.collector' в файле setup.py. Это запускает тестовое обнаружение с параметрами по умолчанию из каталога, содержащего setup.py, поэтому он, пожалуй, наиболее полезен в качестве примера (см. Unittest2/collector.py).

В настоящее время я просто использую script, называемый run_tests.py, но я надеюсь, что смогу избавиться от него, перейдя к решению, которое использует только python setup.py test.

Здесь run_tests.py Я надеюсь удалить:

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)

Ответ 1

От Построение и распространение пакетов с помощью Setuptools (внимание):

test_suite

Строка, обозначающая подкласс unittest.TestCase(или пакет или модуль содержащий один или несколько из них, или способ такого подкласса), или именование функцию, которая может быть вызвана без аргументов и возвращает unittest.TestSuite.

Следовательно, в setup.py вы добавили бы функцию, которая возвращает TestSuite:

import unittest
def my_test_suite():
    test_loader = unittest.TestLoader()
    test_suite = test_loader.discover('tests', pattern='test_*.py')
    return test_suite

Затем вы должны указать команду setup следующим образом:

setup(
    ...
    test_suite='setup.my_test_suite',
    ...
)

Ответ 2

Если вы используете py27 + или py32 +, решение довольно просто:

test_suite="tests",

Ответ 3

Вам не нужна конфигурация, чтобы заставить эту работу работать. В основном это два основных способа:

Быстрый способ

Переименуйте test_module.py в module_test.py (в основном добавьте _test в качестве суффикса для тестов для определенного модуля), и python найдет его автоматически. Просто добавьте это в setup.py:

from setuptools import setup, find_packages

setup(
    ...
    test_suite = 'tests',
    ...
)

Длинный путь

Здесь, как это сделать с вашей текущей структурой каталогов:

project/
  package/
    __init__.py
    module.py
  tests/
    __init__.py
    test_module.py
  run_tests.py <- I want to delete this
  setup.py

В разделе tests/__init__.py вы хотите импортировать unittest и ваш unit test script test_module, а затем создать функцию для запуска тестов. В tests/__init__.py введите что-то вроде этого:

import unittest
import test_module

def my_module_suite():
    loader = unittest.TestLoader()
    suite = loader.loadTestsFromModule(test_module)
    return suite

Класс TestLoader имеет другие функции помимо loadTestsFromModule. Вы можете запустить dir(unittest.TestLoader), чтобы увидеть другие, но этот самый простой в использовании.

Поскольку ваша структура каталогов такова, вы, вероятно, захотите, чтобы test_module мог импортировать ваш module script. Возможно, вы уже это сделали, но на всякий случай вы этого не сделали, вы можете включить родительский путь, чтобы вы могли импортировать модуль package и module script. В верхней части test_module.py введите:

import os, sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

import unittest
import package.module
...

Затем, наконец, в setup.py включите модуль tests и запустите команду, которую вы создали, my_module_suite:

from setuptools import setup, find_packages

setup(
    ...
    test_suite = 'tests.my_module_suite',
    ...
)

Затем вы просто запустите python setup.py test.

Вот пример , сделанный в качестве ссылки.

Ответ 4

Одно из возможных решений - просто расширить команду test для distutils и setuptools/distribute. Это похоже на общее количество и сложнее, чем я бы предпочел, но, кажется, правильно обнаруживает и запускает все тесты в моем пакете при запуске python setup.py test. Я стараюсь выбрать это как ответ на мой вопрос в надежде, что кто-то предоставит более элегантное решение:)

(Вдохновленный https://docs.pytest.org/en/latest/goodpractices.html#integrating-with-setuptools-python-setup-py-test-pytest-runner)

Пример 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)

Ответ 5

Стандартная библиотека Python unittest модуль поддерживает обнаружение (в Python 2.7 и более поздних версиях и Python 3.2 и более поздних). Если вы можете принять эти минимальные версии, вы можете просто добавить аргумент командной строки discover в команду unittest.

Для 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,
    },
)

Ответ 6

Другим менее идеальным решением, слегка вдохновленным http://hg.python.org/unittest2/file/2b6411b9a838/unittest2/collector.py

Добавьте модуль, который возвращает TestSuite обнаруженных тестов. Затем настройте настройку для вызова этого модуля.

project/
  package/
    __init__.py
    module.py
  tests/
    __init__.py
    test_module.py
  discover_tests.py
  setup.py

Здесь 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)

И здесь 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)

Ответ 7

Это не приведет к удалению run_tests.py, но заставит его работать с setuptools. Добавить:

class Loader(unittest.TestLoader):
    def loadTestsFromNames(self, names, _=None):
        return self.discover(names[0])

Затем в setup.py: (я предполагаю, что вы делаете что-то вроде setup(**config))

config = {
    ...
    'test_loader': 'run_tests:Loader',
    'test_suite': '.', # your start_dir for discover()
}

Единственный недостаток, который я вижу, это сгибание семантики loadTestsFromNames, но команда testtools test является единственным пользователем и вызывает ее в указанном пути.