运行Python单元测试成功时不打印任何内容,失败时仅打印AssertionError()

12 投票
1 回答
3035 浏览
提问于 2025-04-17 00:17

我有一个标准的unittest格式的测试模块。

class my_test(unittest.TestCase):

    def test_1(self):
        [tests]

    def test_2(self):
        [tests]
  etc....

我公司有一个专用的测试工具,它会把我的模块当成命令行脚本来执行,并且会捕捉到我模块中出现的任何错误,但要求我的模块在成功时不能有任何输出。

所以,我想找到一种方法,让我的测试模块在运行时不输出任何内容,这样如果所有测试都通过,就什么都不显示;如果有测试失败并抛出AssertionError错误,这个错误就能通过标准的Python错误堆栈显示出来(就像在普通的Python脚本中那样)。

官方文档建议使用unittest.main()函数来运行给定模块中的所有测试,像这样:

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

问题是,这样会把测试结果包裹在unittest的工具中,即使所有测试都成功,它仍然会在屏幕上打印一些多余的信息;如果有错误,它也不会像普通的Python错误那样直接显示,而是会被包裹在工具中。

我尝试通过重定向输出到其他流来解决这个问题,使用了:

with open('.LOG','a') as logf:
    suite = unittest.TestLoader().loadTestsFromTestCase(my_test)
    unittest.TextTestRunner(stream = logf).run(suite)

但这里的问题是,所有的内容都被重定向到日志文件中(包括所有错误通知)。所以当我公司的工具运行这个模块时,它会认为执行成功,因为它看不到任何错误(因为所有错误都被重定向到日志文件了)。

有没有什么建议可以让我构建一个测试运行器,抑制所有多余的信息,并且让错误通过正常的Python错误堆栈显示出来?如果你觉得有更好的方法来解决这个问题,请告诉我。

编辑:

这是我最后用来解决这个问题的方法。首先,我在我的测试类中添加了一个“get_test_names()”方法:

class my_test(unittest.TestCase):
  etc....
    @staticmethod
    def get_test_names():
        """Return the names of all the test methods for this class."""
        test_names = [ member[0] for memeber in inspect.getmembers(my_test)
                       if 'test_' in member[0] ]

然后我把对unittest.main()的调用替换成了以下内容:

# Unittest catches all errors raised by the test cases, and returns them as 
# formatted strings inside a TestResult object. In order for the test 
# harness to catch these errors they need to be re-raised, and so I am defining 
# this CompareError class to do that. 
# For each code error, a CompareError will be raised, with the original error 
# stack as the argument. For test failures (i.e. assertion errors) an 
# AssertionError is raised.
class CompareError(Exception):
    def __init__(self,err):
        self.err = err
    def __str__(self):
        return repr(self.err)

# Collect all tests into a TestSuite()
all_tests = ut.TestSuite()
for test in my_test.get_test_names():
    all_tests.addTest(my_test(test))
# Define a TestResult object and run tests
results = ut.TestResult()
all_tests.run(results)
# Re-raise any script errors
for error in results.errors:
    raise CompareError(error[1])
# Re-raise any test failures 
for failure in results.failures:
    raise AssertionError(failure[1])

1 个回答

3

我想到了这个。如果你能更改命令行的话,可能就可以去掉内部的输入输出重定向。

import sys, inspect, traceback

# redirect stdout,
# can be replaced by testharness.py > /dev/null at console
class devnull():
    def write(self, data):
        pass

f = devnull()
orig_stdout = sys.stdout
sys.stdout = f

class TestCase():
    def test_1(self):
        print 'test_1'

    def test_2(self):
        raise AssertionError, 'test_2'

    def test_3(self):
        print 'test_3'


if __name__ == "__main__":
    testcase = TestCase()
    testnames =  [ t[0] for t in inspect.getmembers(TestCase)
                        if t[0].startswith('test_') ]

    for testname in testnames:
        try:
            getattr(testcase, testname)()
        except AssertionError, e:
            print >> sys.stderr, traceback.format_exc()

# restore
sys.stdout = orig_stdout

撰写回答