【问题标题】:Python: How to run unittest.main() for all source files in a subdirectory?Python:如何为子目录中的所有源文件运行 unittest.main()?
【发布时间】:2010-10-13 06:56:35
【问题描述】:

我正在开发一个包含多个源文件的 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 原则。

【问题讨论】:

    标签: python unit-testing


    【解决方案1】:

    从 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 脚本。

    【讨论】:

    • 模式是选择要运行的一组受限测试的唯一方法吗?我在使用发现进行测试时遇到问题,但由于路径问题而在我想运行单个测试时失败。
    • 对 python 2.6 有什么建议吗?
    • @larrycai 要么在此处查看其他答案,要么查看 unittest2 pypi.python.org/pypi/unittest2
    【解决方案2】:

    这是我的测试发现代码,似乎可以完成这项工作。我想确保我可以轻松地扩展测试,而不必在任何涉及的文件中列出它们,而且还要避免将所有测试都写在一个 Ü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。

    【讨论】:

      【解决方案3】:

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

      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
      

      【讨论】:

      • 您错过了 'dirBar/__init__.py` 或 from dirBar import * 都不起作用。顺便说一句,包/模块名称使用小写。
      • 另外,问题在于你假设 Foo.py 和 Bar.py 的测试用例暴露在 dirBar init.py 模块中。如果不是,您的 test.py 将不会测试任何内容。
      • 所以 7 年后,我认为有人不妨添加 init,因为没有其他人会。
      • 请展示您的dirBar/__init__.py 的样子。
      • 我不知道这是否是作者的意图,但我让它工作的方式不是“从 dirBar 导入 *”,而是“从 dirBar.Foo 导入 *”和“ from dirBar.Bar import *" 和 init.,py 是一个空文件,它告诉 python 该目录应该被视为一个包。
      【解决方案4】:

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

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

      【讨论】:

      • 该问题明确询问了有关使用 unittest 的解决方案
      • 解释使用可能与问题意图相关的鼻子测试的任何直接好处可能是有益的?特别是因为这个问题专门询问了单元测试。
      【解决方案5】:

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

      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()
      

      并在每个mypackage/tests/test_category_n/ 中复制并粘贴一个空的__init__.py 和以下__main__.py

      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。)

      【讨论】:

      • +1 直接使用discover API,它支持我们无法控制python cmd的情况
      【解决方案6】:

      我想出了一个可以做你想做的事的 sn-p。它会按照您提供的路径查找 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)
      

      【讨论】:

      • 我认为你的get_test_suites(path)实际上需要是get_test_suites(package_path)
      猜你喜欢
      • 2016-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多