【问题标题】:Python 3 unittest: How to extract results of tests?Python 3 unittest:如何提取测试结果?
【发布时间】:2014-09-25 17:49:53
【问题描述】:

我正在使用 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() 完成后似乎退出了,因此没有执行之后的任何代码。

有什么建议吗?

谢谢!

【问题讨论】:

    标签: python unit-testing python-3.x


    【解决方案1】:

    我最终编写了两个新类,继承自 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() 将按原样使用它。因此,我不得不自己在 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')
    

    【讨论】:

    • 能否获得更多关于您如何进行覆盖的信息?这正是我需要弄清楚如何做的事情,但我不确定如何实现。
    • 你的意思是addSuccess() 和co.?这只是继承;子类的方法将优先于父类的方法...
    【解决方案2】:

    这对您有何帮助。在 StringIO 中捕获 unittest 的输出,该输出将发送到 sys.stderr。在 unittest.main 之后继续添加“exit=False”。根据需要读取捕获的输出和过程。概念证明:

    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。我把它留给你。

    【讨论】:

    • 谢谢,这是一条有趣的路径。但我需要在测试中存储比屏幕上显示的更多信息。而且解析可能相当繁琐......
    【解决方案3】:

    我在从 unittest 库中捕获 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())
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多