繁体   English   中英

Python 3 unittest:如何提取测试结果?

[英]Python 3 unittest: How to extract results of tests?

我正在使用 Python 的 (3.4.1) unittest模块进行单元测试。

我使用导入加载所有测试模块文件,然后运行 ​​unittest.main():

import unittest
import testing_module1
import testing_module2
# [...]
if __name__ == '__main__':
    unittest.main()

这对我来说非常有效,因为它很简单并且尊重我用来控制详细程度或运行哪个测试的命令行参数。

我想继续输出相同的信息,但我想从结果中生成一个 XML 文件。 我试过 xmlrunner ( https://github.com/xmlrunner/unittest-xml-reporting/ ) 但是:

  • 它不会向标准输出输出与标准运行器一样多的信息;
  • 它使用不适合我的特定 XML 格式。

我想以我需要的格式生成 XML(我不介意手动执行),但对测试的运行方式进行最小的更改。

我有哪些选择?

  1. 我可以编写自己的 TestRunner,但我不想重写所有内容,我只想以最少的代码更改为实际运行程序添加额外的输出。
  2. 我可以继承 unittest.TextTestRunner 但我担心将 XML 输出添加到它会需要重新编写每个方法,首先失去继承的优势。
  3. 我可以尝试在调用unittest.main()后提取测试结果并解析它。 这里的问题是unittest.main()似乎在完成后退出,因此它之后的任何代码都没有执行。

有什么建议吗?

谢谢!

我最终编写了两个新类,继承自unittest.TextTestResultunittest.TextTestRunner 这样,我可以像这样运行 main:

unittest.main(testRunner=xmlrunner.XMLTestRunner(...))

我重载了unittest.TextTestRunner__init__和来自unittest.TextTestResult那些:

  • addSuccess()
  • addError()
  • addFailure()
  • addSubTest()

例如:

def addSuccess(self, test):
    super().addSuccess(test)
    [... store the test into list, dictionary, whatever... ]

由于这些add*()函数是在实际测试中调用的,我可以将它们存储在一个全局列表中,并在我的XMLTestRunner.run()的末尾解析它们:

def run(self, test):
    result = super().run(test)
    self.save_xml_report(result)
    return result

请注意,这些函数通常在/usr/lib/python3.4/unittest/runner.py定义。

警告说明:通过使用实际对象传递unittest.main()testRunner参数,如图所示,启动 python 时给出的命令行参数将被忽略。 例如,使用-v参数增加详细级别将被忽略。 这是因为在/usr/lib/python3.4/unittest/main.py定义的TestProgram类检测unittest.main()是在testRunner是一个类还是一个对象的情况下运行的(请参阅接近末尾的runTests()文件)。 如果您只提供这样的课程:

unittest.main(testRunner=xmlrunner.XMLTestRunner)

然后解析命令行参数。 但是您传递了一个实例化的对象(就像我需要的那样), runTests()将按runTests()使用它。 因此,我不得不在我的XMLTestRunner.__init__()自己解析参数:

# Similar to what /usr/lib/python3.4/unittest/main.py's TestProgram._getParentArgParser() does.
import argparse
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-v', '--verbose', dest='verbosity',
                    action='store_const', const=2, default=1,  # Add default=1, not present in _getParentArgParser()
                    help='Verbose output')
parser.add_argument('-q', '--quiet', dest='verbosity',
                    action='store_const', const=0,
                    help='Quiet output')
parser.add_argument('-f', '--failfast', dest='failfast',
                    action='store_true',
                    help='Stop on first fail or error')
parser.add_argument('-c', '--catch', dest='catchbreak',
                    action='store_true',
                    help='Catch ctrl-C and display results so far')
parser.add_argument('-b', '--buffer', dest='buffer',
                    action='store_true',
                    help='Buffer stdout and stderr during tests')

这对你有什么作用。 在 StringIO 中捕获 unittest 的输出,该输出转到 sys.stderr。 通过添加`exit = False'在unittest.main之后继续。 根据需要读取捕获的输出和处理。 概念证明:

import contextlib
import io
import sys
import unittest

class Mytest(unittest.TestCase):
    def test_true(self):
        self.assertTrue(True)

@contextlib.contextmanager
def err_to(file):
    old_err = sys.stderr
    sys.stderr = file
    yield
    sys.stderr = old_err

if __name__ == '__main__':
    result = io.StringIO()
    with err_to(result):
        unittest.main(exit=False)
    result.seek(0)
    print(result.read())

这打印(到 sys.stdout)

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

注意:contextlib 有redirect_stdout,但没有redirect_stderr。 上面的内容比 contextlib 代码更简单。 以上假设没有未由 unittest 捕获的异常。 请参阅 contextlib.contextmanager 文档以添加 try: except: finally。 我把它留给你。

我在从 unittest lib 中捕获 FAIL 事件时遇到了同样的问题。 在 big_gie 的回答之后,出现了这段代码:

文件testFileName_1.py

import unittest

class TestClassToTestSth(unittest.TestCase):
    def test_One(self):
        self.AssertEqual(True, False, 'Hello world')

import unittest
from io import StringIO
import testFileName_1

def suites():
    return [
        # your testCase classes, for example
        testFileName_1.TestClassToTestSth,
        testFileName_445.TestClassToTestSomethingElse,
    ]

class TextTestResult(unittest.TextTestResult):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.slack = Slack('data-engineering-tests')

    def addFailure(self, test, err):
        super().addFailure(test, err)

        # Whatever you want here
        print(err, test)
        print(self.failures)


class TextTestRunner(unittest.TextTestRunner):
    resultclass = TextTestResult
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


loader = unittest.TestLoader()
suite = unittest.TestSuite()

stream = StringIO()
for test_case in suites():
    suite.addTests(loader.loadTestsFromTestCase(test_case))
runner = TextTestRunner(stream=stream)
result = runner.run(suite)

stream.seek(0)
print(stream.read())

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM