【问题标题】:Recursive unittest discover递归单元测试发现
【发布时间】:2015-06-25 03:37:47
【问题描述】:

我有一个包,其中包含一个目录“tests”,我在其中存储了我的单元测试。我的包裹看起来像:

.
├── 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。没有办法找到两者吗?

【问题讨论】:

标签: python python-2.7 python-unittest


【解决方案1】:

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

如果测试包名称(带有__init__.py 的目录)与 模式,然后将检查包是否有“load_tests”功能。如果 this 存在,然后它会被 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 个测试。

【讨论】:

  • 这比python -m unittest discover -s tests --recursive 要难得多,我真的希望有人在投票结束前会尖酸地评论。 (也就是说,谢谢!
  • @AdamSmith 没问题,我希望它有所帮助——经过一番挖掘,看起来有一种更简单的方法,它更多地依赖于 unittest 发现而不是我的显式检查—— - 我将编辑我的答案以包含此替代方案。
  • 嗯,我偶然发现了一种更简单的方法。我会自己回答,虽然你的还是更万无一失。
【解决方案2】:

经过一番挖掘,似乎只要更深层次的模块仍然可导入,它们就会通过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_*模块。

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2018-08-04
        • 2018-04-09
        • 2020-08-29
        • 2011-12-05
        • 2011-03-18
        • 2017-10-27
        • 2020-09-05
        • 2011-12-02
        • 2014-10-07
        相关资源
        最近更新 更多