2009-03-13 35 views
51

Tôi đang phát triển một mô-đun Python với một số tệp nguồn, mỗi tệp có lớp thử nghiệm riêng của nó bắt nguồn từ unittest ngay trong nguồn. Hãy xem xét các cấu trúc thư mục:Python: Cách chạy unittest.main() cho tất cả các tệp nguồn trong thư mục con?

dirFoo\ 
    test.py 
    dirBar\ 
     __init__.py 
     Foo.py 
     Bar.py 

Để kiểm tra hoặc Foo.py hoặc Bar.py, tôi sẽ thêm này vào cuối của file nguồn Foo.py và Bar.py:

if __name__ == "__main__": 
    unittest.main() 

Và chạy Python trên một trong hai nguồn, tức là

$ python Foo.py 
........... 
---------------------------------------------------------------------- 
Ran 11 tests in 2.314s 

OK 

Lý tưởng nhất, tôi sẽ phải "test.py" Automagically tìm dirBar cho bất kỳ lớp học có nguồn gốc unittest và làm cho một cuộc gọi đến "unittest.main()". Cách tốt nhất để làm điều này trong thực tế là gì?

Tôi đã thử sử dụng Python để gọi execfile cho mọi tệp * .py trong dirBar, chạy một lần cho tệp .py đầu tiên được tìm thấy & thoát khỏi test.py đang gọi, sau đó tôi phải sao chép mã của mình bằng cách thêm unittest. main() trong mọi tệp nguồn - vi phạm nguyên tắc DRY.

Trả lời

25

Tôi biết có một giải pháp rõ ràng:

dirFoo\ 
    __init__.py 
    test.py 
    dirBar\ 
     Foo.py 
     Bar.py 

Nội dung dirFoo/test.py

from dirBar import * 
import unittest 

if __name__ == "__main__": 

    unittest.main() 

Chạy thử nghiệm:

$ python test.py 
........... 
---------------------------------------------------------------------- 
Ran 11 tests in 2.305s 

OK 

Xin lỗi vì câu hỏi ngớ ngẩn.

+4

Hoặc bạn đã bỏ lỡ 'dirBar/__ init __. Py' hoặc 'từ dirBar nhập khẩu *' sẽ không hoạt động. btw, sử dụng chữ thường cho tên gói/mô-đun. – jfs

+1

Ngoài ra, vấn đề với điều này là bạn giả sử các trường hợp thử nghiệm của Foo.py và Bar.py được hiển thị trong mô-đun dirBar __init__.py. Nếu không, test.py của bạn sẽ không kiểm tra bất cứ điều gì. – cdleary

+4

không hoạt động cho đến nay, xin vui lòng cập nhật nó –

17

Bạn nên thử nose. Đó là thư viện để giúp tạo thử nghiệm và tích hợp với unittest hoặc doctest. Tất cả những gì bạn cần làm là chạy nosetests và nó sẽ tìm thấy tất cả các khoản chưa thanh toán của bạn cho bạn.

% nosetests # finds all tests in all subdirectories 
% nosetests tests/ # find all tests in the tests directory 
+3

Câu hỏi được hỏi rõ ràng về các giải pháp sử dụng unittest –

+0

Có thể có ích khi giải thích bất kỳ lợi ích tức thì nào khi sử dụng các nosetests có thể liên quan đến ý định của câu hỏi? Đặc biệt là kể từ khi câu hỏi này hỏi về unittest cụ thể. –

2

Tôi đã đưa ra một đoạn mã có thể làm những gì bạn muốn. Nó đi theo một con đường mà bạn cung cấp tìm kiếm các gói/mô-đun Python và tích lũy một bộ các bộ thử nghiệm từ các mô-đun đó, sau đó nó thực thi tất cả cùng một lúc.

Điều tuyệt vời về điều này là nó sẽ hoạt động trên tất cả các gói được lồng trong thư mục bạn chỉ định và bạn sẽ không phải thay đổi nhập theo cách thủ công khi bạn thêm thành phần mới.

import logging 
import os 
import unittest 

MODULE_EXTENSIONS = set('.py .pyc .pyo'.split()) 

def unit_test_extractor(tup, path, filenames): 
    """Pull ``unittest.TestSuite``s from modules in path 
    if the path represents a valid Python package. Accumulate 
    results in `tup[1]`. 
    """ 
    package_path, suites = tup 
    logging.debug('Path: %s', path) 
    logging.debug('Filenames: %s', filenames) 
    relpath = os.path.relpath(path, package_path) 
    relpath_pieces = relpath.split(os.sep) 

    if relpath_pieces[0] == '.': # Base directory. 
     relpath_pieces.pop(0) # Otherwise, screws up module name. 
    elif not any(os.path.exists(os.path.join(path, '__init__' + ext)) 
      for ext in MODULE_EXTENSIONS): 
     return # Not a package directory and not the base directory, reject. 

    logging.info('Base: %s', '.'.join(relpath_pieces)) 
    for filename in filenames: 
     base, ext = os.path.splitext(filename) 
     if ext not in MODULE_EXTENSIONS: # Not a Python module. 
      continue 
     logging.info('Module: %s', base) 
     module_name = '.'.join(relpath_pieces + [base]) 
     logging.info('Importing from %s', module_name) 
     module = __import__(module_name) 
     module_suites = unittest.defaultTestLoader.loadTestsFromModule(module) 
     logging.info('Got suites: %s', module_suites) 
     suites += module_suites 

def get_test_suites(path): 
    """:return: Iterable of suites for the packages/modules 
    present under :param:`path`. 
    """ 
    logging.info('Base path: %s', package_path) 
    suites = [] 
    os.path.walk(package_path, unit_test_extractor, (package_path, suites)) 
    logging.info('Got suites: %s', suites) 
    return suites 

if __name__ == '__main__': 
    logging.basicConfig(level=logging.WARN) 
    package_path = os.path.dirname(os.path.abspath(__file__)) 
    suites = get_test_suites(package_path) 
    for suite in suites: 
     unittest.TextTestRunner(verbosity=2).run(suite) 
+0

Tôi nghĩ rằng 'get_test_suites (path)' của bạn thực sự cần phải là 'get_test_suites (package_path)'. –

55

Kể từ Python 2.7, khám phá kiểm tra được tự động hóa trong gói chưa chuyển. Từ số docs:

Unittest hỗ trợ khám phá thử nghiệm đơn giản. Để tương thích với khám phá thử nghiệm, tất cả các tệp thử nghiệm phải là mô-đun hoặc gói có thể nhập từ thư mục cấp cao nhất của dự án (điều này có nghĩa là rằng tên tệp của chúng phải là số nhận dạng hợp lệ).

Khám phá thử nghiệm được triển khai trong TestLoader.discover(), nhưng cũng có thể sử dụng từ dòng lệnh.Việc sử dụng dòng lệnh cơ bản là:

cd project_directory 
python -m unittest discover 

Theo mặc định nó sẽ tìm kiếm các gói tên test*.py, nhưng điều này có thể được thay đổi, do đó bạn có thể sử dụng một cái gì đó giống như

python -m unittest discover --pattern=*.py 

Thay vì thử nghiệm của bạn. py script.

+0

có phải là cách duy nhất để chọn một bộ kiểm thử hạn chế để chạy không? Tôi đang gặp vấn đề với thử nghiệm chạy tốt bằng cách sử dụng phát hiện nhưng không thành công khi tôi muốn chạy thử nghiệm cá nhân do vấn đề đường dẫn. –

+2

bất kỳ đề xuất nào cho python 2.6? –

+0

@larrycai hoặc xem các câu trả lời khác ở đây hoặc xem unittest2 http://pypi.python.org/pypi/unittest2 –

26

Đây là mã khám phá thử nghiệm của tôi dường như thực hiện công việc. Tôi muốn đảm bảo rằng tôi có thể mở rộng các bài kiểm tra dễ dàng mà không cần phải liệt kê chúng trong bất kỳ tệp nào có liên quan, nhưng cũng tránh viết tất cả các bài kiểm tra trong một tệp Übertest duy nhất.

Vì vậy, cấu trúc là

myTests.py 
testDir\ 
    __init__.py 
    testA.py 
    testB.py 

myTest.py trông như thế này:

import unittest 

if __name__ == '__main__': 
    testsuite = unittest.TestLoader().discover('.') 
    unittest.TextTestRunner(verbosity=1).run(testsuite) 

Tôi tin rằng đây là giải pháp đơn giản nhất để viết một vài trường hợp thử nghiệm trong một thư mục. Giải pháp yêu cầu Python 2.7 hoặc Python 3.

1

Trong trường hợp nó xảy ra để giúp đỡ bất cứ ai, đây là cách tiếp cận mà tôi đến để giải quyết vấn đề này. Tôi đã có trường hợp sử dụng nơi tôi có cấu trúc thư mục sau:

mypackage/ 
    tests/ 
     test_category_1/ 
      tests_1a.py 
      tests_1b.py 
      ... 
     test_category_2/ 
      tests_2a.py 
      tests_2b.py 
      ... 
     ... 

và tôi muốn tất cả những điều sau đây để làm việc theo cách rõ ràng và để có thể được cung cấp các đối số dòng lệnh giống như được chấp nhận bởi unittest:

python -m mypackage.tests 
python -m mypackage.tests.test_category_1 
python -m mypackage.tests.test_category_1.tests_1a 

giải pháp là để thiết lập mypackage/tests/__init__.py như thế này:

import unittest 

def prepare_load_tests_function (the__path__): 
    test_suite = unittest.TestLoader().discover(the__path__[0]) 
    def load_tests (_a, _b, _c): 
     return test_suite 
    return load_tests 

và thiết lập mypackage/tests/__main__.py như thế này:

import unittest 
from . import prepare_load_tests_function, __path__ 

load_tests = prepare_load_tests_function(__path__) 
unittest.main() 

và sao chép và dán một trống __init__.py__main__.py sau trong mỗi mypackage/tests/test_category_n/:

import unittest 
from .. import prepare_load_tests_function 
from . import __path__ 

load_tests = prepare_load_tests_function(__path__) 
unittest.main() 

và cũng để thêm tiêu chuẩn if __name__ == '__main__': unittest.main() trong mỗi tập tin kiểm tra thực tế.

(Làm việc cho tôi trên Python 3.3 trên Windows, YMMV.)

+0

+1 để sử dụng API khám phá trực tiếp, hỗ trợ các tình huống mà chúng tôi không kiểm soát được lệnh python cmd – deepelement

Các vấn đề liên quan