递归单元测试发现

时间:2015-04-18 06:00:22

标签: python python-2.7 python-unittest

我有一个包含目录"测试"其中我存储了我的单元测试。我的包看起来像:

.
├── LICENSE
├── models
│   └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│   ├── db
│   │   └── test_employee.py
│   └── test_tc.py
└── todo.txt

从我的包目录中,我希望能够找到tests/test_tc.pytests/db/test_employee.py。我不想安装第三方库(nose或者等),或者必须手动构建TestSuite来运行它。

肯定有一种方法可以告诉unittest discover一旦发现测试就不要停止寻找? python -m unittest discover -s tests会找到tests/test_tc.pypython -m unittest discover -s tests/db会找到tests/db/test_employee.py。难道没有办法找到它们吗?

4 个答案:

答案 0 :(得分:53)

在进行一些挖掘时,似乎只要更深层次的模块仍可导入,它们就会通过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__.pypython -m unittest discover就可以导入相关的test_*模块。

答案 1 :(得分:5)

如果您可以在测试中添加__init__.py文件,可以在那里放置load_tests函数来处理发现。

  

如果测试包名称(具有__init__.py的目录)与           然后将检查包的“load_tests”函数。如果           这个存在然后它将使用loader,tests,pattern调用。

     

如果load_tests存在,则发现递归到包中,           load_tests负责加载包中的所有测试。

我很不相信这是最好的方法,但写这个功能的一种方法是:

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

这是预期的,我的两个示例文件中的每一个都包含两个测试,每个testFootestBar

编辑:经过多次挖掘,看起来您可以将此功能指定为:

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个测试。

答案 2 :(得分:1)

使用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 污染我的环境。

答案 3 :(得分:-1)

只需按照文档中给出的 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