递归单元测试发现
Recursive unittest discover
我有一个包含目录 "tests" 的包,我在其中存储我的单元测试。我的包裹看起来像:
.
├── LICENSE
├── models
│ └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│ ├── db
│ │ └── test_employee.py
│ └── test_tc.py
└── todo.txt
从我的包目录中,我希望能够找到 tests/test_tc.py
和 tests/db/test_employee.py
。我宁愿不必安装第三方库(nose
等),也不必手动构建 TestSuite
到 运行 this in.
当然有办法告诉 unittest discover
一旦找到测试就不要停止寻找? python -m unittest discover -s tests
会找到 tests/test_tc.py
,python -m unittest discover -s tests/db
会找到 tests/db/test_employee.py
。没有办法同时找到两者吗?
如果您愿意在测试中添加 __init__.py
文件,您可以在其中放置一个 load_tests
函数来为您处理发现。
If a test package name (directory with __init__.py
) matches the
pattern then the package will be checked for a 'load_tests' function. If
this exists then it will be called with loader, tests, pattern.
If load_tests exists then discovery does not recurse into the package,
load_tests is responsible for loading all tests in the package.
我不确定这是最好的方法,但是编写该函数的一种方法是:
import os
import pkgutil
import inspect
import unittest
# Add *all* subdirectories to this module's path
__path__ = [x[0] for x in os.walk(os.path.dirname(__file__))]
def load_tests(loader, suite, pattern):
for imp, modname, _ in pkgutil.walk_packages(__path__):
mod = imp.find_module(modname).load_module(modname)
for memname, memobj in inspect.getmembers(mod):
if inspect.isclass(memobj):
if issubclass(memobj, unittest.TestCase):
print("Found TestCase: {}".format(memobj))
for test in loader.loadTestsFromTestCase(memobj):
print(" Found Test: {}".format(test))
suite.addTest(test)
print("=" * 70)
return suite
很丑,我同意。
首先将所有子目录添加到测试包的路径 (Docs)。
然后,您使用 pkgutil
寻找包或模块。
找到后,它会检查模块成员是否是 类,如果是 类,是否是 类 的子类 unittest.TestCase
。如果是,类 中的测试将加载到测试套件中。
现在,您可以在项目根目录中输入
python -m unittest discover -p tests
使用-p
模式开关。如果一切顺利,你会看到我所看到的,类似于:
Found TestCase: <class 'test_tc.TestCase'>
Found Test: testBar (test_tc.TestCase)
Found Test: testFoo (test_tc.TestCase)
Found TestCase: <class 'test_employee.TestCase'>
Found Test: testBar (test_employee.TestCase)
Found Test: testFoo (test_employee.TestCase)
======================================================================
....
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
这是预期的,我的两个示例文件中的每一个都包含两个测试,每个测试 testFoo
和 testBar
。
编辑: 经过更多挖掘,您似乎可以将此函数指定为:
def load_tests(loader, suite, pattern):
for imp, modname, _ in pkgutil.walk_packages(__path__):
mod = imp.find_module(modname).load_module(modname)
for test in loader.loadTestsFromModule(mod):
print("Found Tests: {}".format(test._tests))
suite.addTests(test)
这里使用的是loader.loadTestsFromModule()
方法,而不是我上面使用的loader.loadTestsFromTestCase()
方法。它仍然修改 tests
包路径并遍历它寻找模块,我认为这是这里的关键。
输出现在看起来有点不同,因为我们一次添加一个找到的测试套件到我们的主测试套件 suite
:
python -m unittest discover -p tests
Found Tests: [<test_tc.TestCase testMethod=testBar>, <test_tc.TestCase testMethod=testFoo>]
Found Tests: [<test_employee.TestCase testMethod=testBar>, <test_employee.TestCase testMethod=testFoo>]
======================================================================
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
但我们仍然在两个子目录中的 类 中得到了我们预期的 4 个测试。
在进行一些挖掘时,似乎只要更深层次的模块仍然可导入,它们就会通过 python -m unittest discover
被发现。然后,解决方案是简单地向每个目录添加一个 __init__.py
文件以将它们打包。
.
├── LICENSE
├── models
│ └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│ ├── db
│ │ ├── __init__.py # NEW
│ │ └── test_employee.py
│ ├── __init__.py # NEW
│ └── test_tc.py
└── todo.txt
只要每个目录有一个__init__.py
,python -m unittest discover
就可以导入相关的test_*
模块。
使用 init.py 的要点是可能会遇到副作用,例如 file 不是脚本文件路径。使用 FOR DOS 命令可以帮助(未找到 DOS 命令,但有时它有帮助
setlocal
set CWD=%CD%
FOR /R %%T in (*_test.py) do (
CD %%~pT
python %%T
)
CD %CWD%
endlocal
- /R 允许从当前文件夹遍历层次结构。
- (expr) 允许选择测试文件(我使用 _test.py)
- %%~pT 是 shell 中的 $(dirname $T)。
- 我保存并恢复了我的原始目录,因为 .bat 离开了它结束的地方
- setlocal ... endlocal 以免 CWD 污染我的环境。
只需按照文档中给出的 load test protocol 进行操作即可。这甚至适用于名称空间包测试套件。
# test/__init__.py
def load_tests(loader, standard_tests, pattern):
# top level directory cached on loader instance
this_dir = os.path.dirname(__file__)
package_tests = loader.discover(start_dir=this_dir, pattern=pattern)
standard_tests.addTests(package_tests)
return standard_tests
我有一个包含目录 "tests" 的包,我在其中存储我的单元测试。我的包裹看起来像:
.
├── LICENSE
├── models
│ └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│ ├── db
│ │ └── test_employee.py
│ └── test_tc.py
└── todo.txt
从我的包目录中,我希望能够找到 tests/test_tc.py
和 tests/db/test_employee.py
。我宁愿不必安装第三方库(nose
等),也不必手动构建 TestSuite
到 运行 this in.
当然有办法告诉 unittest discover
一旦找到测试就不要停止寻找? python -m unittest discover -s tests
会找到 tests/test_tc.py
,python -m unittest discover -s tests/db
会找到 tests/db/test_employee.py
。没有办法同时找到两者吗?
如果您愿意在测试中添加 __init__.py
文件,您可以在其中放置一个 load_tests
函数来为您处理发现。
If a test package name (directory with
__init__.py
) matches the pattern then the package will be checked for a 'load_tests' function. If this exists then it will be called with loader, tests, pattern.If load_tests exists then discovery does not recurse into the package, load_tests is responsible for loading all tests in the package.
我不确定这是最好的方法,但是编写该函数的一种方法是:
import os
import pkgutil
import inspect
import unittest
# Add *all* subdirectories to this module's path
__path__ = [x[0] for x in os.walk(os.path.dirname(__file__))]
def load_tests(loader, suite, pattern):
for imp, modname, _ in pkgutil.walk_packages(__path__):
mod = imp.find_module(modname).load_module(modname)
for memname, memobj in inspect.getmembers(mod):
if inspect.isclass(memobj):
if issubclass(memobj, unittest.TestCase):
print("Found TestCase: {}".format(memobj))
for test in loader.loadTestsFromTestCase(memobj):
print(" Found Test: {}".format(test))
suite.addTest(test)
print("=" * 70)
return suite
很丑,我同意。
首先将所有子目录添加到测试包的路径 (Docs)。
然后,您使用 pkgutil
寻找包或模块。
找到后,它会检查模块成员是否是 类,如果是 类,是否是 类 的子类 unittest.TestCase
。如果是,类 中的测试将加载到测试套件中。
现在,您可以在项目根目录中输入
python -m unittest discover -p tests
使用-p
模式开关。如果一切顺利,你会看到我所看到的,类似于:
Found TestCase: <class 'test_tc.TestCase'>
Found Test: testBar (test_tc.TestCase)
Found Test: testFoo (test_tc.TestCase)
Found TestCase: <class 'test_employee.TestCase'>
Found Test: testBar (test_employee.TestCase)
Found Test: testFoo (test_employee.TestCase)
======================================================================
....
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
这是预期的,我的两个示例文件中的每一个都包含两个测试,每个测试 testFoo
和 testBar
。
编辑: 经过更多挖掘,您似乎可以将此函数指定为:
def load_tests(loader, suite, pattern):
for imp, modname, _ in pkgutil.walk_packages(__path__):
mod = imp.find_module(modname).load_module(modname)
for test in loader.loadTestsFromModule(mod):
print("Found Tests: {}".format(test._tests))
suite.addTests(test)
这里使用的是loader.loadTestsFromModule()
方法,而不是我上面使用的loader.loadTestsFromTestCase()
方法。它仍然修改 tests
包路径并遍历它寻找模块,我认为这是这里的关键。
输出现在看起来有点不同,因为我们一次添加一个找到的测试套件到我们的主测试套件 suite
:
python -m unittest discover -p tests
Found Tests: [<test_tc.TestCase testMethod=testBar>, <test_tc.TestCase testMethod=testFoo>]
Found Tests: [<test_employee.TestCase testMethod=testBar>, <test_employee.TestCase testMethod=testFoo>]
======================================================================
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
但我们仍然在两个子目录中的 类 中得到了我们预期的 4 个测试。
在进行一些挖掘时,似乎只要更深层次的模块仍然可导入,它们就会通过 python -m unittest discover
被发现。然后,解决方案是简单地向每个目录添加一个 __init__.py
文件以将它们打包。
.
├── LICENSE
├── models
│ └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│ ├── db
│ │ ├── __init__.py # NEW
│ │ └── test_employee.py
│ ├── __init__.py # NEW
│ └── test_tc.py
└── todo.txt
只要每个目录有一个__init__.py
,python -m unittest discover
就可以导入相关的test_*
模块。
使用 init.py 的要点是可能会遇到副作用,例如 file 不是脚本文件路径。使用 FOR DOS 命令可以帮助(未找到 DOS 命令,但有时它有帮助
setlocal
set CWD=%CD%
FOR /R %%T in (*_test.py) do (
CD %%~pT
python %%T
)
CD %CWD%
endlocal
- /R 允许从当前文件夹遍历层次结构。
- (expr) 允许选择测试文件(我使用 _test.py)
- %%~pT 是 shell 中的 $(dirname $T)。
- 我保存并恢复了我的原始目录,因为 .bat 离开了它结束的地方
- setlocal ... endlocal 以免 CWD 污染我的环境。
只需按照文档中给出的 load test protocol 进行操作即可。这甚至适用于名称空间包测试套件。
# test/__init__.py
def load_tests(loader, standard_tests, pattern):
# top level directory cached on loader instance
this_dir = os.path.dirname(__file__)
package_tests = loader.discover(start_dir=this_dir, pattern=pattern)
standard_tests.addTests(package_tests)
return standard_tests