“python setup.py test”에서 unittest discover를 실행하는 방법은 무엇입니까?


82

python setup.py test.NET과 동등한 기능을 실행하는 방법을 알아 내려고 노력 중 python -m unittest discover입니다. run_tests.py 스크립트를 사용하고 싶지 않고 외부 테스트 도구 ( nose또는 같은 py.test) 를 사용하고 싶지 않습니다 . 솔루션이 Python 2.7에서만 작동하면 괜찮습니다.

에서 setup.py, 나는 내가 뭔가를 추가 할 필요가 있다고 생각합니다 test_suite및 / 또는 test_loader설정 필드,하지만 난 제대로 작동하는 조합을 찾을 수 없습니다 :

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

unittestPython 2.7에 내장 된 경우에만 가능 합니까?

참고로 내 프로젝트 구조는 다음과 같습니다.

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 호환 테스트 수집기가 포함되어 있습니다. setup.py에 test_suite = 'unittest2.collector'를 지정하십시오. 이렇게하면 setup.py가 포함 된 디렉토리의 기본 매개 변수를 사용하여 테스트 검색이 시작되므로 아마도 가장 유용한 예입니다 (unittest2 / collector.py 참조).

지금은라는 스크립트를 사용하고 run_tests.py있지만 .NET 만 사용하는 솔루션으로 이동하여이 문제를 제거 할 수 있기를 바랍니다 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)

여기에 오는 모든 사람에게주의를 기울이십시오. setup.py 테스트는 코드 '냄새'로 간주되며 더 이상 사용되지 않도록 설정됩니다. github.com/pytest-dev/pytest-runner/issues/50
Yashash Gaurav

답변:


44

py27 + 또는 py32 +를 사용하는 경우 솔루션은 매우 간단합니다.

test_suite="tests",

1
이 문제가 더 잘 작동했으면 좋겠습니다. stackoverflow.com/questions/6164004/… "테스트 이름은 모듈 이름과 일치해야합니다."foo_test.py "테스트가 있으면 해당 모듈 foo.py가 있어야합니다. . "
Charles L.

1
나는 동의한다. 제 경우에는 말 그대로 .py가있는 Python 모듈이없는 외부 Python을 테스트하는 경우이를 달성하는 좋은 방법이없는 것 같습니다.
Tom Swirly

2
이것이 올바른 해결책입니다. @CharlesL 문제가 없었습니다. 했다. 내 모든 테스트의 이름은 test_*.py. 또한 나는 그것이 실제로 재귀 찾기 위해 주어진 디렉토리를 통해 검색 할 것이라는 점을 발견 중 하나를 확장 클래스를 unittest.TestCast. 당신은 당신이 가지고있는 디렉토리 구조를 가지고있을 때 매우 유용 tests/first_batch/test_*.py하고 tests/second_batch/test_*.py. 그냥 지정할 수 있으며 test_suite="tests",모든 것을 재귀 적으로 선택합니다. 중첩 된 각 디렉토리에는 __init__.py파일 이 있어야 합니다.
dcmm88

39

Setuptools로 패키지 빌드 및 배포 에서 (강조) :

test_suite

unittest.TestCase 하위 클래스 (또는 이들 중 하나 이상을 포함하는 패키지 또는 모듈 또는 이러한 하위 클래스의 메서드)의 이름을 지정하는 문자열 또는 인수없이 호출 할 수있는 함수 이름을 지정 하고 unittest.TestSuite를 반환합니다 .

따라서 setup.pyTestSuite를 반환하는 함수를 추가합니다.

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

3
이 솔루션은 unittest의 2 개의 "레벨"을 생성하기 때문에 문제가 있습니다. 즉, setuptools는 setup.my_test_suite에서 TestSuite를 생성하려고 시도하는 'test'명령을 생성하여 setup ()을 다시 실행하는 setup.py를 강제로 가져옵니다! 이번에는 원하는 테스트를 실행하는 새로운 (중첩 된) 테스트 명령을 생성합니다. 이것은 대부분의 사람들에게 눈에 띄지 않을 수 있지만 테스트 명령을 확장하려고하면 (내 테스트를 '제자리에서'실행할 수 없기 때문에 수정해야했습니다) 이상한 문제가 발생할 수 있습니다. 사용 stackoverflow.com/a/21726329/3272850 대신
dcmm88

2
이로 인해 위에서 언급 한 이유로 테스트가 두 번 실행됩니다. 함수를 __init__.py테스트 폴더 로 이동 하고 참조하여 문제를 해결했습니다.
익명

3
테스트가 두 번 실행되는 문제 는 스크립트의 블록 내에서 setup()함수 를 실행하여 쉽게 해결할 수 있습니다 . 설정 스크립트가 처음 실행될 때 if 블록이 호출됩니다. 두 번째로 설정 스크립트를 모듈로 가져 오므로 if 블록이 호출되지 않습니다. if __name__ == '__main__':setup.py
hoefling

흠, 내 setup.py에는 해당 test_suite매개 변수가 전혀 포함되어 있지 않지만 "python setup.py test"는 여전히 잘 작동합니다. 문서가 말하는 것과는 다릅니다 . "setup () 호출에서 test_suite를 설정하지 않았고 --test-suite 옵션을 제공하지 않으면 오류가 발생합니다." 어떤 생각?
RayLuo

21

이 작업을 수행하기 위해 구성이 필요하지 않습니다. 기본적으로 두 가지 주요 방법이 있습니다.

빠른 방법

당신의 이름을 변경 test_module.py하는 module_test.py(기본적으로 추가 _test특정 모듈에 대한 시험을 접미사로), 그리고 파이썬은 자동으로 찾을 수 있습니다. 이것을 다음에 추가하십시오 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를 가져온 다음 테스트를 실행하는 함수를 만듭니다. 에서 , 이런 일을 입력 :unittesttest_moduletests/__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. 이미이 작업을 수행했을 수도 있지만 그렇지 않은 경우를 대비하여 package모듈과 module스크립트를 가져올 수 있도록 부모 경로를 포함 할 수 있습니다 . 상단에 다음 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 .

다음은 누군가가 참조로 만든 샘플 입니다.


2
질문은 "python setup.py test"가 unittest의 검색 기능을 사용하도록 만드는 방법이었습니다. 이것은 전혀 그것을 다루지 않습니다.
mikenerone 2014 년

어 .. 네, 질문이 뭔가 다른 걸 묻는 거라고 생각 했어요. 어떻게 그런 일이 일어 났는지 잘 모르겠습니다. 정신을 잃고있는 것 같습니다. (
반물질 2014 년

5

한 가지 가능한 해결책은 단순히 testdistutilssetuptools/에 명령을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)

3

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)

3

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

BTW, 질문은 구체적으로이 기능에 관한 것이기 때문에 실제로 발견 (2.7 및 3.2+)을 지원하는 Python 버전 만 대상으로한다고 가정합니다. 물론 이전 버전과의 호환성을 유지하려면 버전 검사에서 삽입물을 래핑 할 수 있습니다 (따라서 이러한 경우에는 setuptools의 표준 로더를 사용).
mikenerone 2014 년

0

이것은 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것이지만 setuptools test 명령은 유일한 소비자이며 지정된 방식으로 호출합니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.