I am developing a Python module with several source files, each with its own test class derived from unittest right in the source. Consider the directory structure:
我正在开发一个Python模块,其中包含几个源文件,每个都有自己的测试类,它们都是从源代码中的unittest派生出来的。考虑的目录结构:
dirFoo\
test.py
dirBar\
__init__.py
Foo.py
Bar.py
To test either Foo.py or Bar.py, I would add this at the end of the Foo.py and Bar.py source files:
测试或者Foo。py或酒吧。py,我把它加到Foo后面。py和酒吧。py源文件:
if __name__ == "__main__":
unittest.main()
And run Python on either source, i.e.
在任何一个源上运行Python,例如。
$ python Foo.py
...........
----------------------------------------------------------------------
Ran 11 tests in 2.314s
OK
Ideally, I would have "test.py" automagically search dirBar for any unittest derived classes and make one call to "unittest.main()". What's the best way to do this in practice?
理想情况下,我应该有“测试”。py自动搜索任何unittest派生类的dirBar,并调用“unittest.main()”。在实践中最好的方法是什么?
I tried using Python to call execfile for every *.py file in dirBar, which runs once for the first .py file found & exits the calling test.py, plus then I have to duplicate my code by adding unittest.main() in every source file--which violates DRY principles.
我尝试使用Python为每个*调用execfile。在dirBar中,为第一个发现并退出调用测试的.py文件运行一次。然后,我必须通过在每个源文件中添加unittest.main()来复制代码——这违反了DRY原则。
6 个解决方案
#1
58
As of Python 2.7, test discovery is automated in the unittest package. From the docs:
在Python 2.7中,测试发现在unittest包中是自动的。从文档:
Unittest supports simple test discovery. In order to be compatible with test discovery, all of the test files must be modules or packages importable from the top-level directory of the project (this means that their filenames must be valid identifiers).
Unittest支持简单的测试发现。为了与测试发现兼容,所有的测试文件都必须是来自项目*目录的模块或包(这意味着它们的文件名必须是有效的标识符)。
Test discovery is implemented in
TestLoader.discover()
, but can also be used from the command line. The basic command-line usage is:在TestLoader.discover()中实现了测试发现,但也可以从命令行中使用。基本的命令行用法是:
cd project_directory python -m unittest discover
By default it looks for packages named test*.py
, but this can be changed so you might use something like
默认情况下,它查找名为test*的包。py,但是这个可以修改,所以你可以用类似的东西
python -m unittest discover --pattern=*.py
In place of your test.py script.
代替你的测试。py脚本。
#2
26
Here is my test discovery code that seems to do the job. I wanted to make sure I can extend the tests easily without having to list them in any of the involved files, but also avoid writing all tests in one single Übertest file.
下面是我的测试发现代码,它似乎可以完成这项工作。我希望确保我可以轻松地扩展测试,而不需要在任何涉及的文件中列出它们,但也要避免在单个Ubertest文件中编写所有测试。
So the structure is
因此,结构
myTests.py
testDir\
__init__.py
testA.py
testB.py
myTest.py look like this:
myTest。py是这个样子:
import unittest
if __name__ == '__main__':
testsuite = unittest.TestLoader().discover('.')
unittest.TextTestRunner(verbosity=1).run(testsuite)
I believe this is the simplest solution for writing several test cases in one directory. The solution requires Python 2.7 or Python 3.
我相信这是在一个目录中编写几个测试用例的最简单的解决方案。解决方案需要Python 2.7或Python 3。
#3
25
I knew there was an obvious solution:
我知道有一个显而易见的解决办法:
dirFoo\
__init__.py
test.py
dirBar\
__init__.py
Foo.py
Bar.py
Contents of dirFoo/test.py
dirFoo / test.py内容
from dirBar import *
import unittest
if __name__ == "__main__":
unittest.main()
Run the tests:
运行测试:
$ python test.py
...........
----------------------------------------------------------------------
Ran 11 tests in 2.305s
OK
Sorry for the silly question.
很抱歉问了这个愚蠢的问题。
#4
19
You should try nose. It's a library to help create tests and it integrates with unittest
or doctest
. All you need to do is run nosetests
and it'll find all your unittests for you.
你应该试着鼻子。它是一个帮助创建测试的库,并与unittest或doctest集成。你所需要做的就是运行nosetests,它会为你找到所有的unittests。
% nosetests # finds all tests in all subdirectories
% nosetests tests/ # find all tests in the tests directory
#5
2
I came up with a snippet that may do what you want. It walks a path that you provide looking for Python packages/modules and accumulates a set of test suites from those modules, which it then executes all at once.
我想到了一个可以做你想做的事情的片段。它遵循您提供的查找Python包/模块的路径,并从这些模块中收集一组测试套件,然后一次执行所有这些测试套件。
The nice thing about this is that it will work on all packages nested under the directory you specify, and you won't have to manually change the imports as you add new components.
这样做的好处是,它可以在您指定的目录下嵌套的所有包上工作,并且您不必在添加新组件时手动更改导入。
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)
#6
1
In case it happens to help anyone, here is the approach I arrived at for solving this problem. I had the use case where I have the following directory structure:
如果它碰巧帮助了任何人,这里是我解决这个问题的方法。我有一个用例,其中我有以下目录结构:
mypackage/
tests/
test_category_1/
tests_1a.py
tests_1b.py
...
test_category_2/
tests_2a.py
tests_2b.py
...
...
and I want all of the following to work in the obvious way and to be able to be supplied the same commandline arguments as are accepted by unittest:
我希望以下所有内容都能以一种显而易见的方式工作,并且能够提供与unittest所接受的命令行参数相同的参数:
python -m mypackage.tests
python -m mypackage.tests.test_category_1
python -m mypackage.tests.test_category_1.tests_1a
The solution was to set up mypackage/tests/__init__.py
like this:
解决方案是设置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
and to set up mypackage/tests/__main__.py
like this:
建立mypackage/tests/__main__。py是这样的:
import unittest
from . import prepare_load_tests_function, __path__
load_tests = prepare_load_tests_function(__path__)
unittest.main()
and to copy and paste an empty __init__.py
and the following __main__.py
in each mypackage/tests/test_category_n/
:
复制粘贴一个空__init__。py和下面的__main__。在每个mypackage /测试/ test_category_n / py:
import unittest
from .. import prepare_load_tests_function
from . import __path__
load_tests = prepare_load_tests_function(__path__)
unittest.main()
and also to add the standard if __name__ == '__main__': unittest.main()
in each actual tests file.
还要在每个实际的测试文件中添加if __name__ = '__main__': unittest.main()标准。
(Works for me on Python 3.3 on Windows, ymmv.)
(适用于我在Windows上的Python 3.3, ymmv。)
#1
58
As of Python 2.7, test discovery is automated in the unittest package. From the docs:
在Python 2.7中,测试发现在unittest包中是自动的。从文档:
Unittest supports simple test discovery. In order to be compatible with test discovery, all of the test files must be modules or packages importable from the top-level directory of the project (this means that their filenames must be valid identifiers).
Unittest支持简单的测试发现。为了与测试发现兼容,所有的测试文件都必须是来自项目*目录的模块或包(这意味着它们的文件名必须是有效的标识符)。
Test discovery is implemented in
TestLoader.discover()
, but can also be used from the command line. The basic command-line usage is:在TestLoader.discover()中实现了测试发现,但也可以从命令行中使用。基本的命令行用法是:
cd project_directory python -m unittest discover
By default it looks for packages named test*.py
, but this can be changed so you might use something like
默认情况下,它查找名为test*的包。py,但是这个可以修改,所以你可以用类似的东西
python -m unittest discover --pattern=*.py
In place of your test.py script.
代替你的测试。py脚本。
#2
26
Here is my test discovery code that seems to do the job. I wanted to make sure I can extend the tests easily without having to list them in any of the involved files, but also avoid writing all tests in one single Übertest file.
下面是我的测试发现代码,它似乎可以完成这项工作。我希望确保我可以轻松地扩展测试,而不需要在任何涉及的文件中列出它们,但也要避免在单个Ubertest文件中编写所有测试。
So the structure is
因此,结构
myTests.py
testDir\
__init__.py
testA.py
testB.py
myTest.py look like this:
myTest。py是这个样子:
import unittest
if __name__ == '__main__':
testsuite = unittest.TestLoader().discover('.')
unittest.TextTestRunner(verbosity=1).run(testsuite)
I believe this is the simplest solution for writing several test cases in one directory. The solution requires Python 2.7 or Python 3.
我相信这是在一个目录中编写几个测试用例的最简单的解决方案。解决方案需要Python 2.7或Python 3。
#3
25
I knew there was an obvious solution:
我知道有一个显而易见的解决办法:
dirFoo\
__init__.py
test.py
dirBar\
__init__.py
Foo.py
Bar.py
Contents of dirFoo/test.py
dirFoo / test.py内容
from dirBar import *
import unittest
if __name__ == "__main__":
unittest.main()
Run the tests:
运行测试:
$ python test.py
...........
----------------------------------------------------------------------
Ran 11 tests in 2.305s
OK
Sorry for the silly question.
很抱歉问了这个愚蠢的问题。
#4
19
You should try nose. It's a library to help create tests and it integrates with unittest
or doctest
. All you need to do is run nosetests
and it'll find all your unittests for you.
你应该试着鼻子。它是一个帮助创建测试的库,并与unittest或doctest集成。你所需要做的就是运行nosetests,它会为你找到所有的unittests。
% nosetests # finds all tests in all subdirectories
% nosetests tests/ # find all tests in the tests directory
#5
2
I came up with a snippet that may do what you want. It walks a path that you provide looking for Python packages/modules and accumulates a set of test suites from those modules, which it then executes all at once.
我想到了一个可以做你想做的事情的片段。它遵循您提供的查找Python包/模块的路径,并从这些模块中收集一组测试套件,然后一次执行所有这些测试套件。
The nice thing about this is that it will work on all packages nested under the directory you specify, and you won't have to manually change the imports as you add new components.
这样做的好处是,它可以在您指定的目录下嵌套的所有包上工作,并且您不必在添加新组件时手动更改导入。
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)
#6
1
In case it happens to help anyone, here is the approach I arrived at for solving this problem. I had the use case where I have the following directory structure:
如果它碰巧帮助了任何人,这里是我解决这个问题的方法。我有一个用例,其中我有以下目录结构:
mypackage/
tests/
test_category_1/
tests_1a.py
tests_1b.py
...
test_category_2/
tests_2a.py
tests_2b.py
...
...
and I want all of the following to work in the obvious way and to be able to be supplied the same commandline arguments as are accepted by unittest:
我希望以下所有内容都能以一种显而易见的方式工作,并且能够提供与unittest所接受的命令行参数相同的参数:
python -m mypackage.tests
python -m mypackage.tests.test_category_1
python -m mypackage.tests.test_category_1.tests_1a
The solution was to set up mypackage/tests/__init__.py
like this:
解决方案是设置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
and to set up mypackage/tests/__main__.py
like this:
建立mypackage/tests/__main__。py是这样的:
import unittest
from . import prepare_load_tests_function, __path__
load_tests = prepare_load_tests_function(__path__)
unittest.main()
and to copy and paste an empty __init__.py
and the following __main__.py
in each mypackage/tests/test_category_n/
:
复制粘贴一个空__init__。py和下面的__main__。在每个mypackage /测试/ test_category_n / py:
import unittest
from .. import prepare_load_tests_function
from . import __path__
load_tests = prepare_load_tests_function(__path__)
unittest.main()
and also to add the standard if __name__ == '__main__': unittest.main()
in each actual tests file.
还要在每个实际的测试文件中添加if __name__ = '__main__': unittest.main()标准。
(Works for me on Python 3.3 on Windows, ymmv.)
(适用于我在Windows上的Python 3.3, ymmv。)