关于单元测试:Python 3 unittest:如何提取测试结果?

Python 3 unittest: How to extract results of tests?

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

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

1
2
3
4
5
6
import unittest
import testing_module1
import testing_module2
# [...]
if __name__ == '__main__':
    unittest.main()

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

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

  • 它不像标准运行程序那样向stdout输出太多信息;
  • 它使用的XML的特定格式不适合我。

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

我有什么选择?

  • 我可以编写自己的testranner,但我不想重写任何东西,我只想在代码更改最小的情况下向实际的运行程序添加额外的输出。
  • 我可以继承UnitTest.TextTestRunner,但我担心向它添加XML输出将需要重新编写每个方法,首先会失去继承的优势。
  • 我可以在调用unittest.main()后尝试提取测试结果并对其进行分析。这里的问题是,当执行完任何代码后,unittest.main()似乎都会退出。
  • 有什么建议吗?

    谢谢!


    最后我写了两个新的类,继承了unittest.TextTestResultunittest.TextTestRunner。这样,我就可以像这样运行main:

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

    我超载了unittest.TextTestRunner__init__unittest.TextTestResult的:

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

    例如:

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

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

    1
    2
    3
    4
    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())。如果你只给一个这样的班级:

    1
    unittest.main(testRunner=xmlrunner.XMLTestRunner)

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 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。在unittest.main之后继续,方法是添加"exit=false"。根据需要读取捕获的输出和进程。概念证明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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)

    1
    2
    3
    4
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s

    OK

    注意:contextlib具有重定向u stdout,但没有重定向u stderr。上面的代码比ContextLib代码简单。上面假设没有UnitTest未捕获的异常。有关添加try:except:finally的信息,请参见contextlib.contextmanager文档。我把这个留给你。