简体   繁体   English

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

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

I am using Python's (3.4.1) unittest module for my unit tests.我正在使用 Python 的 (3.4.1) unittest模块进行单元测试。

I load all my testing module files using imports and then run unittest.main():我使用导入加载所有测试模块文件,然后运行 ​​unittest.main():

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

This works perfectly for me as it is simple and respect the command line arguments I use to control verbosity or which test(s) to run.这对我来说非常有效,因为它很简单并且尊重我用来控制详细程度或运行哪个测试的命令行参数。

I want to continue to output the same information, but I would like to generate an XML file from the results.我想继续输出相同的信息,但我想从结果中生成一个 XML 文件。 I tried xmlrunner ( https://github.com/xmlrunner/unittest-xml-reporting/ ) but:我试过 xmlrunner ( https://github.com/xmlrunner/unittest-xml-reporting/ ) 但是:

  • it does not output as much info to stdout as the standard runner;它不会向标准输出输出与标准运行器一样多的信息;
  • it uses a specific format of the XML that doesn't suites me.它使用不适合我的特定 XML 格式。

I would like to generate the XML (I don't mind doing it manually) with the format I need but with minimal change to how the tests are run.我想以我需要的格式生成 XML(我不介意手动执行),但对测试的运行方式进行最小的更改。

What are my options?我有哪些选择?

  1. I could write my own TestRunner but I don't want to re-write everything, I just want to add extra output to the actual runner with minimal code change.我可以编写自己的 TestRunner,但我不想重写所有内容,我只想以最少的代码更改为实际运行程序添加额外的输出。
  2. I could inherit unittest.TextTestRunner but I fear that adding XML output to it would require re-writing every methods, loosing the advantage of inheritance in the first place.我可以继承 unittest.TextTestRunner 但我担心将 XML 输出添加到它会需要重新编写每个方法,首先失去继承的优势。
  3. I could try to extract the test results after the call to unittest.main() and parse it.我可以尝试在调用unittest.main()后提取测试结果并解析它。 The problem here is that unittest.main() seems to exit when it's done so any code after it is not executed.这里的问题是unittest.main()似乎在完成后退出,因此它之后的任何代码都没有执行。

Any suggestion?有什么建议吗?

Thanks!谢谢!

I ended up writing two new classes, inheriting from unittest.TextTestResult and unittest.TextTestRunner .我最终编写了两个新类,继承自unittest.TextTestResultunittest.TextTestRunner That way, I could run main like that:这样,我可以像这样运行 main:

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

I overloaded unittest.TextTestRunner 's __init__ and those from unittest.TextTestResult :我重载了unittest.TextTestRunner__init__和来自unittest.TextTestResult那些:

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

For example:例如:

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

Since these add*() functions are called with the actual test, I can store them in a global list and parse them at the end of my XMLTestRunner.run() :由于这些add*()函数是在实际测试中调用的,我可以将它们存储在一个全局列表中,并在我的XMLTestRunner.run()的末尾解析它们:

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

Note that these functions are normally defined in /usr/lib/python3.4/unittest/runner.py .请注意,这些函数通常在/usr/lib/python3.4/unittest/runner.py定义。

Warning note : By using an actual object passed unittest.main() 's testRunner argument as shown, the command line arguments given when launching python are ignored.警告说明:通过使用实际对象传递unittest.main()testRunner参数,如图所示,启动 python 时给出的命令行参数将被忽略。 For example, increasing verbose level with -v argument is ignored.例如,使用-v参数增加详细级别将被忽略。 This is because the TestProgram class, defined in /usr/lib/python3.4/unittest/main.py , detects if unittest.main() was run with testRunner being a class or an object (see runTests() near the end of the file).这是因为在/usr/lib/python3.4/unittest/main.py定义的TestProgram类检测unittest.main()是在testRunner是一个类还是一个对象的情况下运行的(请参阅接近末尾的runTests()文件)。 If you give just a class like that:如果您只提供这样的课程:

unittest.main(testRunner=xmlrunner.XMLTestRunner)

then command line arguments are parsed.然后解析命令行参数。 But you pass an instantiated object (like I need), runTests() will just use it as is.但是您传递了一个实例化的对象(就像我需要的那样), runTests()将按runTests()使用它。 I thus had to parse arguments myself in my XMLTestRunner.__init__() :因此,我不得不在我的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')

How does this work for you.这对你有什么作用。 Capture the output of unittest, which goes to sys.stderr, in a StringIO.在 StringIO 中捕获 unittest 的输出,该输出转到 sys.stderr。 Continue after unittest.main by adding `exit=False'.通过添加`exit = False'在unittest.main之后继续。 Read the captured output and process as you want.根据需要读取捕获的输出和处理。 Proof of concept:概念证明:

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())

This prints (to sys.stdout)这打印(到 sys.stdout)

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

OK

Note: contextlib has redirect_stdout, but not redirect_stderr.注意:contextlib 有redirect_stdout,但没有redirect_stderr。 The above is simpler that the contextlib code.上面的内容比 contextlib 代码更简单。 The above assumes that there are no exceptions not caught by unittest.以上假设没有未由 unittest 捕获的异常。 See the contextlib.contextmanager doc for adding try: except: finally.请参阅 contextlib.contextmanager 文档以添加 try: except: finally。 I leave that to you.我把它留给你。

I have faced the same issue with catching FAIL events from unittest lib.我在从 unittest lib 中捕获 FAIL 事件时遇到了同样的问题。 Following big_gie's answer, this code appeared:在 big_gie 的回答之后,出现了这段代码:

File testFileName_1.py文件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