Python:如何对子目录中的所有源文件运行unittest.main()?

时间:2009-03-13 22:20:31

标签: python unit-testing

我正在开发一个包含多个源文件的Python模块,每个源文件都有源自unittest的自己的测试类。考虑目录结构:

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

要测试Foo.py或Bar.py,我会在Foo.py和Bar.py源文件的末尾添加它:

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

在任何一个源上运行Python,即

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

OK

理想情况下,我会“test.py”自动搜索dirBar以获取任何unittest派生类,并调用“unittest.main()”。在实践中这样做的最佳方法是什么?

我尝试使用Python为dirBar中的每个* .py文件调用execfile,该文件针对找到的第一个.py文件运行一次&退出调用test.py,然后我必须通过在每个源文件中添加unittest.main()来复制我的代码 - 这违反了DRY原则。

6 个答案:

答案 0 :(得分:59)

从Python 2.7开始,测试发现在unittest包中自动完成。来自docs

  

Unittest支持简单的测试发现。为了兼容   通过测试发现,所有测试文件必须是模块或包   可以从项目的顶级目录导入(这意味着   他们的文件名必须是有效的标识符。)

     

测试发现在TestLoader.discover()中实现,但也可以   从命令行使用。基本命令行用法是:

cd project_directory
python -m unittest discover

默认情况下,它会查找名为test*.py的软件包,但这可以更改,因此您可以使用类似

的软件包
python -m unittest discover --pattern=*.py

代替test.py脚本。

答案 1 :(得分:28)

这是我的测试发现代码,似乎可以完成这项工作。我想确保我可以轻松地扩展测试,而不必在任何涉及的文件中列出它们,但也避免在一个单独的Übertest文件中编写所有测试。

所以结构是

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

myTest.py看起来像这样:

import unittest

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

我相信这是在一个目录中编写多个测试用例的最简单的解决方案。该解决方案需要Python 2.7或Python 3。

答案 2 :(得分:26)

我知道有一个明显的解决方案:

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

dirFoo / test.py的内容

from dirBar import *
import unittest

if __name__ == "__main__":

    unittest.main()

运行测试:

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

OK

抱歉这个愚蠢的问题。

答案 3 :(得分:19)

您应该尝试nose。它是一个帮助创建测试的库,它与unittestdoctest集成在一起。您需要做的只是运行nosetests,它会为您找到所有的单元测试。

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

答案 4 :(得分:2)

我想出了一个可能做你想做的片段。它走的是你提供的路径,寻找Python包/模块,并从这些模块中累积一组测试套件,然后一次性执行。

关于这一点的好处是,它将适用于嵌套在您指定的目录下的所有包,并且您不必在添加新组件时手动更改导入。

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)

答案 5 :(得分:1)

如果它碰巧帮助了任何人,这就是我解决这个问题的方法。我有一个用例,我有以下目录结构:

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

我希望以下所有内容以明显的方式工作,并且能够提供与unittest接受的相同的命令行参数:

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

解决方案是设置mypackage/tests/__init__.py,如下所示:

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

并像这样设置mypackage/tests/__main__.py

import unittest
from . import prepare_load_tests_function, __path__

load_tests = prepare_load_tests_function(__path__)
unittest.main()

并在每个__init__.py中复制并粘贴空的__main__.py和以下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()

并在每个实际测试文件中添加标准if __name__ == '__main__': unittest.main()

(适用于Windows上的Python 3.3,ymmv。)