首页 文章

如何在目录中运行所有Python单元测试?

提问于
浏览
205

我有一个包含我的Python单元测试的目录 . 每个单元测试模块的形式为 test_*.py . 我正在尝试创建一个名为 all_test.py 的文件,您猜对了,运行上述测试表单中的所有文件并返回结果 . 到目前为止,我尝试了两种方法;都失败了 . 我将展示这两种方法,我希望那里的人知道如何正确地做到这一点 .

对于我的第一次勇敢尝试,我想“如果我只是在文件中导入我的所有测试模块,然后调用这个 unittest.main() doodad,它会起作用,对吧?”好吧,事实证明我错了 .

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]

if __name__ == "__main__":
     unittest.main()

这不起作用,我得到的结果是:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

对于我的第二次尝试,我可以,好吧,也许我会尝试以更“手动”的方式完成整个测试 . 所以我试着在下面这样做:

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite 

result = unittest.TestResult()
testSuite.run(result)
print result

#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __name__ == "__main__":
    unittest.main()

这也没用,但似乎太近了!

$ python all_test.py 
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

我似乎有一些类型的套件,我可以执行结果 . 我有点担心它说我只有 run=1 ,看起来应该是 run=2 ,但这是进步 . 但是如何将结果传递给main?或者我如何基本上使它工作,所以我可以运行这个文件,并在这样做,运行此目录中的所有单元测试?

14 回答

  • 1

    通过研究上面的代码(特别是使用 TextTestRunnerdefaultTestLoader ),我能够非常接近 . 最后我通过将所有测试套件传递给单个套件构造函数来修复我的代码,而不是添加它们"manually",这修复了我的其他问题 . 所以这是我的解决方案 .

    import glob
    import unittest
    
    test_files = glob.glob('test_*.py')
    module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
    suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
    test_suite = unittest.TestSuite(suites)
    test_runner = unittest.TextTestRunner().run(test_suite)
    

    是的,使用鼻子可能比做这个更容易,但这是重点 .

  • 36

    因为测试发现似乎是一个完整的主题,所以有一些专门的框架来测试发现:

    更多阅读:https://wiki.python.org/moin/PythonTestingToolsTaxonomy

  • 93

    您可以使用可以为您执行此操作的测试运行器 . nose非常好 . 例如 . 运行时,它将在当前树中找到测试并运行它们 .

    更新:

    这是我前鼻子时代的一些代码 . 您可能不希望显式的模块名称列表,但其余部分可能对您有用 .

    testmodules = [
        'cogapp.test_makefiles',
        'cogapp.test_whiteutils',
        'cogapp.test_cogapp',
        ]
    
    suite = unittest.TestSuite()
    
    for t in testmodules:
        try:
            # If the module defines a suite() function, call it to get the suite.
            mod = __import__(t, globals(), locals(), ['suite'])
            suitefn = getattr(mod, 'suite')
            suite.addTest(suitefn())
        except (ImportError, AttributeError):
            # else, just load all the test cases from the module.
            suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))
    
    unittest.TextTestRunner().run(suite)
    
  • 6

    基于Stephen Cagle的答案,我添加了对嵌套测试模块的支持 .

    import fnmatch
    import os
    import unittest
    
    def all_test_modules(root_dir, pattern):
        test_file_names = all_files_in(root_dir, pattern)
        return [path_to_module(str) for str in test_file_names]
    
    def all_files_in(root_dir, pattern):
        matches = []
    
        for root, dirnames, filenames in os.walk(root_dir):
            for filename in fnmatch.filter(filenames, pattern):
                matches.append(os.path.join(root, filename))
    
        return matches
    
    def path_to_module(py_file):
        return strip_leading_dots( \
            replace_slash_by_dot(  \
                strip_extension(py_file)))
    
    def strip_extension(py_file):
        return py_file[0:len(py_file) - len('.py')]
    
    def replace_slash_by_dot(str):
        return str.replace('\\', '.').replace('/', '.')
    
    def strip_leading_dots(str):
        while str.startswith('.'):
           str = str[1:len(str)]
        return str
    
    module_names = all_test_modules('.', '*Tests.py')
    suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname 
        in module_names]
    
    testSuite = unittest.TestSuite(suites)
    runner = unittest.TextTestRunner(verbosity=1)
    runner.run(testSuite)
    

    代码在 . 的所有子目录中搜索 *Tests.py 文件,然后加载这些文件 . 它希望每个 *Tests.py 包含一个类 *Tests(unittest.TestCase) ,它依次加载并一个接一个地执行 .

    这适用于目录/模块的任意深度嵌套,但其间的每个目录至少需要包含一个空的 __init__.py 文件 . 这允许测试通过用点替换斜杠(或反斜杠)来加载嵌套模块(参见 replace_slash_by_dot ) .

  • 343

    使用Python 2.7及更高版本,您不必编写新代码或使用第三方工具来执行此操作;通过命令行执行递归测试是内置的 .

    python -m unittest discover <test_directory>
    # or
    python -m unittest discover -s <directory> -p '*_test.py'
    

    您可以在python 2.7python 3.x unittest文档中阅读更多内容 .

  • 6

    无论您在哪个工作目录中,此BASH脚本都将从文件系统中的ANYWHERE执行python unittest测试目录:其工作目录始终位于 test 目录所在的位置 .

    ALL TESTS, independent $PWD

    unittest Python模块对您当前的目录很敏感,除非您告诉它在哪里(使用 discover -s 选项) .

    这在停留在 ./src./example 工作目录中时非常有用,您需要进行快速的整体单元测试:

    #!/bin/bash
    this_program="$0"
    dirname="`dirname $this_program`"
    readlink="`readlink -e $dirname`"
    
    python -m unittest discover -s "$readlink"/test -v
    

    SELECTED TESTS, independent $PWD

    我将此实用程序文件命名为: runone.py 并像这样使用它:

    runone.py <test-python-filename-minus-dot-py-fileextension>
    
    #!/bin/bash
    this_program="$0"
    dirname="`dirname $this_program`"
    readlink="`readlink -e $dirname`"
    
    (cd "$dirname"/test; python -m unittest $1)
    

    生产环境 过程中不需要 test/__init__.py 文件来增加包/内存开销 .

  • 1

    现在可以直接从unittest:unittest.TestLoader.discover .

    import unittest
    loader = unittest.TestLoader()
    start_dir = 'path/to/your/test/files'
    suite = loader.discover(start_dir)
    
    runner = unittest.TextTestRunner()
    runner.run(suite)
    
  • 29

    如果是打包的库或应用程序,您不希望这样做 . setuptools will do it for you .

    要使用此命令,必须通过函数,TestCase类或方法或包含TestCase类的模块或包将项目的测试包装在unittest测试套件中 . 如果命名套件是模块,并且模块具有additional_tests()函数,则调用它并将结果(必须是unittest.TestSuite)添加到要运行的测试中 . 如果命名套件是一个包,则任何子模块和子包将以递归方式添加到整个测试套件中 .

    只需告诉它根测试包的位置,例如:

    setup(
        # ...
        test_suite = 'somepkg.test'
    )
    

    并运行 python setup.py test .

    基于文件的发现在Python 3中可能存在问题,除非您避免在测试套件中进行相对导入,因为discover使用文件导入 . 即使它支持可选的 top_level_dir ,但我有一些无限的递归错误 . 因此,对于非打包代码的简单解决方案是将以下内容放在测试包的 __init__.py 中(请参阅load_tests Protocol) .

    import unittest
    
    from . import foo, bar
    
    
    def load_tests(loader, tests, pattern):
        suite = unittest.TestSuite()
        suite.addTests(loader.loadTestsFromModule(foo))
        suite.addTests(loader.loadTestsFromModule(bar))
    
        return suite
    
  • 1

    在python 3中,如果您正在使用 unittest.TestCase 并且测试目录中有一个空(或其他) __init__.py 文件,那么您可以运行所有测试

    python -m unittest
    

    完成!小于100行的解决方案 . 希望另一个python初学者通过找到它来节省时间 .

  • 18

    我尝试了各种方法,但都看起来有缺陷,或者我不得不化妆一些代码,这很烦人 . 但是有一个方便的在linux下,只需通过某种模式查找每个测试,然后逐个调用它们 .

    find . -name 'Test*py' -exec python '{}' \;
    

    最重要的是,它绝对有效 .

  • 4

    这是我的方法,创建a wrapper从命令行运行测试:

    #!/usr/bin/env python3
    import os, sys, unittest, argparse, inspect, logging
    
    if __name__ == '__main__':
        # Parse arguments.
        parser = argparse.ArgumentParser(add_help=False)
        parser.add_argument("-?", "--help",     action="help",                        help="show this help message and exit" )
        parser.add_argument("-v", "--verbose",  action="store_true", dest="verbose",  help="increase output verbosity" )
        parser.add_argument("-d", "--debug",    action="store_true", dest="debug",    help="show debug messages" )
        parser.add_argument("-h", "--host",     action="store",      dest="host",     help="Destination host" )
        parser.add_argument("-b", "--browser",  action="store",      dest="browser",  help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
        parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")
        parser.add_argument('files', nargs='*')
        args = parser.parse_args()
    
        # Load files from the arguments.
        for filename in args.files:
            exec(open(filename).read())
    
        # See: http://codereview.stackexchange.com/q/88655/15346
        def make_suite(tc_class):
            testloader = unittest.TestLoader()
            testnames = testloader.getTestCaseNames(tc_class)
            suite = unittest.TestSuite()
            for name in testnames:
                suite.addTest(tc_class(name, cargs=args))
            return suite
    
        # Add all tests.
        alltests = unittest.TestSuite()
        for name, obj in inspect.getmembers(sys.modules[__name__]):
            if inspect.isclass(obj) and name.startswith("FooTest"):
                alltests.addTest(make_suite(obj))
    
        # Set-up logger
        verbose = bool(os.environ.get('VERBOSE', args.verbose))
        debug   = bool(os.environ.get('DEBUG', args.debug))
        if verbose or debug:
            logging.basicConfig( stream=sys.stdout )
            root = logging.getLogger()
            root.setLevel(logging.INFO if verbose else logging.DEBUG)
            ch = logging.StreamHandler(sys.stdout)
            ch.setLevel(logging.INFO if verbose else logging.DEBUG)
            ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
            root.addHandler(ch)
        else:
            logging.basicConfig(stream=sys.stderr)
    
        # Run tests.
        result = unittest.TextTestRunner(verbosity=2).run(alltests)
        sys.exit(not result.wasSuccessful())
    

    为简单起见,请原谅我的非PEP8编码标准 .

    然后,您可以为所有测试创建公共组件的BaseTest类,因此您的每个测试都将如下所示:

    from BaseTest import BaseTest
    class FooTestPagesBasic(BaseTest):
        def test_foo(self):
            driver = self.driver
            driver.get(self.base_url + "/")
    

    要运行,只需将测试指定为命令行参数的一部分,例如:

    ./run_tests.py -h http://example.com/ tests/**/*.py
    
  • -2

    我使用PyDev / LiClipse并没有真正弄清楚如何从GUI一次运行所有测试 . (编辑:右键单击根测试文件夹并选择 Run as -> Python unit-test

    这是我目前的解决方法:

    import unittest
    
    def load_tests(loader, tests, pattern):
        return loader.discover('.')
    
    if __name__ == '__main__':
        unittest.main()
    

    我将此代码放在我的测试目录中名为 all 的模块中 . 如果我从LiClipse运行此模块作为单元测试,则运行所有测试 . 如果我要求只重复特定或失败的测试,那么只运行那些测试 . 它没有被忽视 .

    您可能需要根据项目设置将参数更改为 discover .

  • 22

    如果您想从各种测试用例类中运行所有测试,并且您很乐意明确指定它们,那么您可以这样做:

    from unittest import TestLoader, TextTestRunner, TestSuite
    from uclid.test.test_symbols import TestSymbols
    from uclid.test.test_patterns import TestPatterns
    
    if __name__ == "__main__":
    
        loader = TestLoader()
        tests = [
            loader.loadTestsFromTestCase(test)
            for test in (TestSymbols, TestPatterns)
        ]
        suite = TestSuite(tests)
    
        runner = TextTestRunner(verbosity=2)
        runner.run(suite)
    

    其中 uclid 是我的项目, TestSymbolsTestPatternsTestCase 的子类 .

  • 12

    我已经使用了 discover 方法和 load_tests 的重载来实现这个结果(我认为最小的)数字代码行:

    def load_tests(loader, tests, pattern):
    ''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
    '''
        suite = TestSuite()
        for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
            for test_suite in all_test_suite:
                suite.addTests(test_suite)
        return suite
    
    if __name__ == '__main__':
        unittest.main()
    

    执行就像五个人一样

    Ran 27 tests in 0.187s
    OK
    

相关问题