我有一个目录,其中包含我的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?还是我基本上如何使其工作,以便我可以运行该文件,然后运行此目录中的所有单元测试?

评论

如果您使用的是Python 2.7+,请跳至Travis的答案

您是否曾经尝试从测试实例对象运行测试?

有关示例文件结构的解决方案,请参见此答案。

#1 楼

使用Python 2.7和更高版本,您无需编写新代码或使用第三方工具即可完成此操作。内置了通过命令行执行递归测试的功能。将__init__.py放入您的测试目录中,然后:

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


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

评论


问题包括:ImportError:起始目录不可导入:

–缩放
13年5月5日在2:25

至少在Linux上使用Python 2.7.8时,命令行调用都不给我递归。我的项目有几个子项目,这些子项目的单元测试分别位于“ unit_tests / / python /”目录中。如果我指定了这样的路径,则将运行该子项目的单元测试,但仅使用“ unit_tests”作为测试目录参数,就不会找到测试(而不是我希望的所有子项目的所有测试)。有什么提示吗?

–user686249
15年7月15日在14:30

关于递归:第一个没有的命令默认为“”。并递归到子模块。也就是说,要发现的所有测试目录都需要具有init.py。如果这样做,将通过发现命令找到它们。刚尝试过,就可以了。

– EmilStenström
16年5月5日在12:45

这对我有用。我有一个包含四个文件的测试文件夹,可以在我的Linux终端上运行,非常棒。

–JasTonAChair
16-09-22在9:42

谢谢!为什么这不是公认的答案?我认为,更好的答案永远是不需要任何外部依赖项的答案。

–乔纳森·本恩
17年9月26日在13:02

#2 楼

在python 3中,如果您使用的是unittest.TestCase


您的__init__.py目录中必须有一个空的(或其他)test文件(必须命名为test/
您的测试test/中的文件与模式test_*.py相匹配。它们可以位于test/下的子目录中,并且这些子目录可以命名为任意名称。

然后,您可以使用以下命令运行所有测试:

python -m unittest


完成!解决方案少于100行。希望其他python初学者可以通过查找此方法节省时间。

评论


请注意,默认情况下,它仅以“ test”开头的文件名搜索测试

– Shawabawa
18/12/6在14:58

没错,原始问题涉及“每个单元测试模块的形式为test _ *。py。”的事实,因此,此答案可直接答复。我现在更新了答案,使其更加明确

– tmck代码
19年1月24日在1:01

谢谢,这是我使用Travis Bear的答案所缺少的。

– Jeremy Cochoy
19/12/7在15:42

#3 楼

您可以使用测试运行程序来为您执行此操作。例如鼻子很好。运行时,它将在当前树中找到测试并运行它们。

更新时间:

这是我前鼻时期的一些代码。您可能不需要明确的模块名称列表,但其余的模块可能对您有用。

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)


评论


与仅将所有测试模块显式导入一个test_all.py模块并调用unittest.main()相比,这种方法的优势是您可以选择在某些模块中声明测试套件,而在其他模块中声明测试套件吗?

– Corey Porter
09年11月13日23:50

我试了一下鼻子,效果很好。在我的项目中安装和运行都很容易。我什至可以在virtualenv内运行几行脚本来实现自动化。鼻子+1!

–杰西·韦伯(Jesse Webb)
2012年1月5日18:59

并非总是可行的:有时,如果尝试在模块上运行导入,则项目的导入结构可能导致混乱。

– chiffa
15年11月20日在13:47

请注意,鼻子已经“在过去几年中处于维护模式”,目前建议对新项目使用鼻子2,pytest或仅使用普通unittest / unittest2。

–库尔特·皮克
17年1月11日在10:45

您是否曾经尝试从测试实例对象运行测试?

–木偶奇遇记
17年6月24日在23:16

#4 楼

现在可以直接从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)


评论


我也尝试过这种方法,进行了几次测试,但是效果很好。优秀的!!!但是我很好奇我只有4个测试。它们一起运行0.032s,但是当我使用此方法全部运行它们时,我得到结果.... -------------------------- --------------------------------------------在0.000秒内进行了4次测试好为什么区别,它来自哪里?

– Simkusr
18-4-22在5:58



我无法从命令行运行看起来像这样的文件。应该如何调用它?

–达斯汀·米歇尔(Dustin Michels)
18-09-18在20:18

python file.py

–slaughter98
18-09-19在2:21



完美地工作!只需在您的test /目录中进行设置,然后设置start_id =“ ./”即可。恕我直言,这个答案现在(Python 3.7)已被接受!

– jjwdesign
19年6月17日在0:57

您可以将最后一行更改为´res = Runner.run(suite); sys.exit(如果res.wasSuccessful()则为0,否则为1)´,如果您想要正确的退出代码

–萨达普
20-04-18在8:07

#5 楼

通过研究上面的代码(特别是使用TextTestRunnerdefaultTestLoader),我可以很接近了。最终,我还通过仅将所有测试套件传递给单个套件的构造函数来固定代码,而不是“手动”添加它们,从而解决了其他问题。所以这是我的解决方案。

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)


是的,仅使用鼻子比这样做更容易,但这并不重要。

评论


好,它对于当前目录工作正常,如何直接调用子目录?

–蔡礼
13年1月8日在6:00

拉里(Larry),有关递归测试发现,请参见新答案(stackoverflow.com/a/24562019/104143)

– Peter Kofler
2014年7月3日在19:38

您是否曾经尝试从测试实例对象运行测试?

–木偶奇遇记
17年6月24日在23:17

#6 楼

如果要运行各种测试用例类中的所有测试,并且希望明确地指定它们,则可以这样做:

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的子类。

评论


来自unittest.TestLoader文档:“通常,无需创建此类的实例; unittest模块提供了一个可以作为unittest.defaultTestLoader共享的实例。”同样,由于TestSuite接受iterable作为参数,因此可以在循环中构造该iterable以避免重复loader.loadTestsFromTestCase。

–两位炼金术士
15年3月17日在23:11

@ Two-Bit Alchemist,您的第二点特别好。我将代码更改为包含,但无法对其进行测试。 (虽然我意识到我很不理智(给他们一个骆驼大小写变量名称),但第一个mod会让它看起来像Java。.

–痴呆的刺猬
16年2月12日在4:52

这是我的最爱,很干净。能够将其打包并在我的常规命令行中将其作为参数。

– MarkII
16-10-22在20:13

#7 楼

我使用了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


评论


我想这仅适用于python2.7

–蔡礼
13年1月8日在5:41

@larrycai也许,我通常使用Python 3,有时使用Python 2.7。该问题与特定版本无关。

–rds
13年1月8日在9:14

我使用的是Python 3.4,发现返回了一个套件,从而使循环变得多余。

–沙丘
2014年7月22日14:12

对于将来的Larry来说:“ Python 2.7在unittest中添加了许多新功能,包括测试发现。unittest2允许您将这些功能与Python的早期版本一起使用。”

–两位炼金术士
15年3月17日在23:48

#8 楼

我尝试了各种方法,但似乎都存在缺陷,或者我必须编写一些代码,这很烦人。但是在Linux下,有一种简便的方法,就是通过某种模式找到每个测试,然后逐个调用它们。

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


,最重要的是,它肯定可以工作。

#9 楼

如果是打包的库或应用程序,则不想这样做。 setuptools会为您做。


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


只需告诉它您的根测试包在哪里,例如:

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


并运行python setup.py test

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

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


评论


很好的答案,它可以在部署之前用于自动测试!谢谢

– Arthur Clerc-Gherardi
19年11月25日在9:56

#10 楼

我使用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运行此模块,则将运行所有测试;如果我仅要求重复特定或失败的测试,则仅运行那些测试。这不会干扰我的命令行测试要么成为(noestests),要么被忽略。

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

评论


所有测试文件和测试方法的名称均应以“ test_”开头。否则,命令“运行方式-> Python单元测试”将找不到它们。

– Stefan
17年9月7日在12:39

#11 楼

这是一个老问题,但现在(2019年)对我有用的是:

python -m unittest *_test.py


我所有的测试文件都与源文件位于同一文件夹中,以_test结尾。

#12 楼

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

#13 楼

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


nose
Py.Test

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

#14 楼

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

所有测试,独立$ PWD

unittest Python模块对您当前的目录敏感,除非您告诉它当前位置(使用discover -s选项)。

在保持./src./example正常工作时这很有用目录,您需要进行快速的整体单元测试:



 #!/bin/bash
this_program="runone.py"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

python -m unittest discover -s "$readlink"/test -v
 


选定的测试,独立的$ PWD

我将此实用程序文件命名为:runone.py <test-python-filename-minus-dot-py-fileextension> 并像这样使用它:

 #!/bin/bash
this_program="test/__init__.py"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

(cd "$dirname"/test; python -m unittest )
 




 q4312079q 


不需要一个q4312079q文件会在生产过程中加重您的包装/内存开销。

#15 楼

我只是在基本测试目录中创建了一个discover.py文件,并为子目录中的任何内容添加了import语句。然后discover可以通过在discover.py
上运行它来在这些目录中找到我所有的测试。
python -m unittest discover ./test -p '*.py'

# /test/discover.py
import unittest

from test.package1.mod1 import XYZTest
from test.package1.package2.mod2 import ABCTest
...

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


#16 楼

这是我创建包装程序以从命令行运行测试的方法:

#!/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


评论


大多数答案与测试发现无关(即日志记录等)。堆栈溢出用于回答问题,而不是炫耀不相关的代码。

– Corey Goldberg
17年1月19日在4:29