Làm cách nào để chạy khám phá mới nhất từ ​​“thử nghiệm python setup.py”?


82

Tôi đang cố gắng tìm ra cách python setup.py testchạy tương đương với python -m unittest discover. Tôi không muốn sử dụng tập lệnh run_tests.py và tôi không muốn sử dụng bất kỳ công cụ kiểm tra bên ngoài nào (như nosehoặc py.test). Sẽ ổn nếu giải pháp chỉ hoạt động trên python 2.7.

Trong setup.py, tôi nghĩ rằng tôi cần thêm một cái gì đó vào các trường test_suitevà / hoặc test_loadertrong cấu hình, nhưng dường như tôi không thể tìm thấy một tổ hợp nào hoạt động chính xác:

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

Điều này có khả thi bằng cách chỉ unittestđược xây dựng trong python 2.7 không?

FYI, cấu trúc dự án của tôi trông như thế này:

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

Cập nhật : Điều này có thể xảy ra với unittest2nhưng tôi muốn tìm thứ gì đó tương đương chỉ bằng cách sử dụngunittest

Từ https://pypi.python.org/pypi/unittest2

unittest2 bao gồm một bộ thu thử nghiệm tương thích với setuptools rất cơ bản. Chỉ định test_suite = 'unittest2.collector' trong setup.py của bạn. Điều này bắt đầu khám phá thử nghiệm với các tham số mặc định từ thư mục chứa setup.py, vì vậy nó có lẽ hữu ích nhất làm ví dụ (xem unittest2 / collector.py).

Hiện tại, tôi chỉ đang sử dụng một tập lệnh có tên run_tests.py, nhưng tôi hy vọng tôi có thể loại bỏ điều này bằng cách chuyển sang một giải pháp chỉ sử dụng python setup.py test.

Đây là những run_tests.pygì tôi hy vọng sẽ xóa:

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)

Chỉ là một lời cảnh báo cho bất cứ ai tình cờ đến đây. setup.py kiểm tra được coi là một mã 'mùi' và cũng được thiết lập để không được dùng nữa. github.com/pytest-dev/pytest-runner/issues/50
Yashash Gaurav

Câu trả lời:


44

Nếu bạn sử dụng py27 + hoặc py32 +, giải pháp khá đơn giản:

test_suite="tests",

1
Tôi ước điều này hoạt động tốt hơn, tôi đã gặp phải vấn đề này: stackoverflow.com/questions/6164004/… "Tên thử nghiệm phải khớp với tên mô-đun. Nếu có thử nghiệm" foo_test.py ", thì cần phải có mô-đun foo.py tương ứng . "
Charles L.

1
Tôi đồng ý. Trong trường hợp của tôi, nơi tôi đang thử nghiệm bên ngoài Python, nơi thực sự không có mô-đun Python nào như vậy với .py, dường như không có cách nào tốt để đạt được điều này.
Tom Swirly

2
Đây là giải pháp chính xác. Tôi không gặp sự cố @CharlesL. đã có. Tất cả các bài kiểm tra của tôi đều được đặt tên test_*.py. Ngoài ra, tôi phát hiện ra rằng nó thực sự sẽ tìm kiếm đệ quy qua thư mục nhất định để tìm bất kỳ lớp nào mở rộng unittest.TestCast. Điều này cực kỳ hữu ích nếu bạn có cấu trúc thư mục mà bạn có tests/first_batch/test_*.pytests/second_batch/test_*.py. Bạn chỉ có thể chỉ định test_suite="tests",và nó sẽ chọn mọi thứ một cách đệ quy. Lưu ý rằng mỗi thư mục lồng nhau sẽ cần có một __init__.pytệp trong đó.
dcmm88

39

Từ việc xây dựng và phân phối các gói với Setuptools (tôi nhấn mạnh):

test_suite

Một chuỗi đặt tên cho lớp con unittest.TestCase (hoặc một gói hoặc mô-đun chứa một hoặc nhiều trong số chúng hoặc một phương thức của lớp con đó) hoặc đặt tên cho một hàm có thể được gọi mà không có đối số và trả về một unittest.TestSuite .

Do đó, trong setup.pybạn sẽ thêm một hàm trả về TestSuite:

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

Sau đó, bạn sẽ chỉ định lệnh setupnhư sau:

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

3
Có một vấn đề với giải pháp này, bởi vì nó tạo ra 2 "cấp độ" kém nhất. Có nghĩa là setuptools sẽ tạo một lệnh 'test' sẽ cố gắng tạo TestSuite từ setup.my_test_suite, lệnh này sẽ buộc nó phải nhập setup.py, lệnh này sẽ chạy lại setup ()! Lần thứ hai này, nó sẽ tạo một lệnh kiểm tra mới (lồng nhau) chạy kiểm tra mong muốn của bạn. Điều này có thể không đáng chú ý đối với hầu hết mọi người, nhưng nếu bạn cố gắng mở rộng lệnh kiểm tra (tôi cần sửa đổi nó vì tôi không thể chạy thử nghiệm 'tại chỗ'), bạn có thể gặp phải các vấn đề kỳ lạ. Sử dụng stackoverflow.com/a/21726329/3272850 thay
dcmm88

2
Điều này khiến các bài kiểm tra chạy hai lần đối với tôi vì những lý do đã đề cập ở trên. Đã sửa nó bằng cách chuyển hàm vào __init__.pythư mục tests và tham chiếu đến nó.
Ẩn danh

3
Có thể dễ dàng khắc phục sự cố với các thử nghiệm được thực thi hai lần bằng cách thực thi setup()hàm bên trong if __name__ == '__main__':khối trong setup.pytập lệnh. Lần đầu tiên tập lệnh thiết lập đang được thực thi, vì vậy khối if sẽ được gọi; lần thứ hai tập lệnh thiết lập sẽ được nhập dưới dạng mô-đun, vì vậy khối if sẽ không được gọi.
hoefling

Rất tiếc, tôi nhận thấy rằng setup.py của tôi KHÔNG chứa test_suitetham số đó , nhưng "thử nghiệm python setup.py" vẫn hoạt động tốt đối với tôi. Điều đó khác với những gì tài liệu nói : "Nếu bạn không đặt test_suite trong lệnh gọi setup () và không cung cấp tùy chọn --test-suite, thì sẽ xảy ra lỗi." Bất kỳ ý tưởng?
RayLuo

21

Bạn không cần cấu hình để làm việc này. Về cơ bản có hai cách chính để làm điều đó:

Cách nhanh chóng

Đổi tên của bạn test_module.pythành module_test.py(về cơ bản thêm _testdưới dạng hậu tố cho các bài kiểm tra cho một mô-đun cụ thể) và python sẽ tự động tìm thấy nó. Chỉ cần đảm bảo thêm điều này vào setup.py:

from setuptools import setup, find_packages

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

Con đường dài

Đây là cách thực hiện với cấu trúc thư mục hiện tại của bạn:

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

Bên dưới tests/__init__.py, bạn muốn nhập unittestvà tập lệnh kiểm tra đơn vị của mình test_module, sau đó tạo một hàm để chạy kiểm tra. Trong tests/__init__.py, nhập một cái gì đó như sau:

import unittest
import test_module

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

Ngoài ra, TestLoaderlớp còn có các chức năng khác loadTestsFromModule. Bạn có thể chạy dir(unittest.TestLoader)để xem những cái khác, nhưng cái này là đơn giản nhất để sử dụng.

Vì cấu trúc thư mục của bạn là như vậy, có thể bạn sẽ muốn test_modulecó thể nhập moduletập lệnh của mình . Bạn có thể đã làm điều này, nhưng trong trường hợp chưa làm, bạn có thể bao gồm đường dẫn chính để bạn có thể nhập packagemô-đun và moduletập lệnh. Ở đầu của bạn test_module.py, hãy nhập:

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

import unittest
import package.module
...

Rồi cuối cùng, trong setup.py, bao gồm các testsmô-đun và chạy lệnh mà bạn đã tạo, my_module_suite:

from setuptools import setup, find_packages

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

Sau đó, bạn chỉ cần chạy python setup.py test.

Đây là một mẫu ai đó đã làm để tham khảo.


2
Câu hỏi đặt ra là làm thế nào để "kiểm tra python setup.py" sử dụng khả năng khám phá của unittest. Điều này không giải quyết điều đó ở tất cả.
mikenerone

Ugh ... yeah Tôi hoàn toàn nghĩ rằng câu hỏi đang hỏi một cái gì đó khác nhau. Tôi không chắc chuyện đó xảy ra như thế nào, chắc tôi đang mất trí rồi :(
phản vật chất

5

Một giải pháp khả thi là chỉ cần mở rộng testlệnh for distutilsand setuptools/ distribute. Điều này có vẻ như là một tổng số kluge và phức tạp hơn tôi muốn, nhưng dường như phát hiện và chạy chính xác tất cả các bài kiểm tra trong gói của tôi khi chạy python setup.py test. Tôi đang tiếp tục chọn đây làm câu trả lời cho câu hỏi của mình với hy vọng rằng ai đó sẽ cung cấp một giải pháp thanh lịch hơn :)

(Lấy cảm hứng từ https://docs.pytest.org/en/latest/goodpractices.html#integrating-with-setuptools-python-setup-py-test-pytest-runner )

Ví dụ 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

Một giải pháp kém lý tưởng khác được lấy cảm hứng từ http://hg.python.org/unittest2/file/2b6411b9a838/unittest2/collector.py

Thêm một mô-đun trả về một TestSuitetrong số các bài kiểm tra đã phát hiện. Sau đó, cấu hình thiết lập để gọi mô-đun đó.

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

Đây là 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)

Và đây là 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

unittestMô-đun thư viện tiêu chuẩn của Python hỗ trợ khám phá (trong Python 2.7 trở lên và Python 3.2 trở lên). Nếu bạn có thể giả định các phiên bản tối thiểu đó, thì bạn chỉ cần thêm discoverđối số dòng lệnh vào unittestlệnh.

Chỉ cần một chỉnh sửa nhỏ để 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, tôi giả định ở trên rằng bạn chỉ nhắm mục tiêu các phiên bản Python hỗ trợ khám phá thực tế (2.7 và 3.2+), vì câu hỏi là về tính năng này cụ thể. Tất nhiên, bạn có thể bọc chèn trong một kiểm tra phiên bản nếu bạn muốn vẫn tương thích với các phiên bản cũ hơn (do đó, sử dụng trình tải chuẩn của setuptools trong những trường hợp đó).
mikenerone

0

Thao tác này sẽ không xóa run_tests.py, nhưng sẽ làm cho nó hoạt động với các công cụ thiết lập. Thêm vào:

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

Sau đó, trong setup.py: (Tôi cho rằng bạn đang làm điều gì đó giống như vậy setup(**config))

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

Nhược điểm duy nhất mà tôi thấy là nó làm cong ngữ nghĩa của nó loadTestsFromNames, nhưng lệnh kiểm tra setuptools là người dùng duy nhất và gọi nó theo một cách cụ thể .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.