获取Python的unittest会导致tearDown()方法

时间:2010-12-10 23:37:37

标签: python unit-testing nose

是否有可能在tearDown()方法中获得测试结果(即是否所有断言都已通过)?我正在运行Selenium脚本,我想从tearDown()内部做一些报告,但我不知道这是否可行。

13 个答案:

答案 0 :(得分:38)

如果查看unittest.TestCase.run的实现,可以看到所有测试结果都是在作为参数传递的结果对象(通常是unittest.TestResult实例)中收集的。 unittest.TestCase对象中没有剩余结果状态。

因此,除非你无情地打破测试用例和测试结果的优雅解耦,否则你无法在unittest.TestCase.tearDown方法中做到这一点:

import unittest

class MyTest(unittest.TestCase):

    currentResult = None # holds last result object passed to run method

    def setUp(self):
        pass

    def tearDown(self):
        ok = self.currentResult.wasSuccessful()
        errors = self.currentResult.errors
        failures = self.currentResult.failures
        print ' All tests passed so far!' if ok else \
                ' %d errors and %d failures so far' % \
                (len(errors), len(failures))

    def run(self, result=None):
        self.currentResult = result # remember result for use in tearDown
        unittest.TestCase.run(self, result) # call superclass run method

    def test_onePlusOneEqualsTwo(self):
        self.assertTrue(1 + 1 == 2) # succeeds

    def test_onePlusOneEqualsThree(self):
        self.assertTrue(1 + 1 == 3) # fails

    def test_onePlusNoneIsNone(self):
        self.assertTrue(1 + None is None) # raises TypeError

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

编辑:这适用于Python 2.6 - 3.3, (针对新的Python bellow进行了修改)。

答案 1 :(得分:29)

此解决方案适用于 Python 版本 2.7到3.7 (当前最高版本),在tearDown之前的任何代码中都没有任何装饰器或其他修改。一切都按照内置的结果分类进行。也可以正确识别跳过的测试或expectedFailure。它评估当前测试的结果,而不是迄今为止所有测试的摘要。与pytest兼容。

import unittest

class MyTest(unittest.TestCase):
    def tearDown(self):
        if hasattr(self, '_outcome'):  # Python 3.4+
            result = self.defaultTestResult()  # these 2 methods have no side effects
            self._feedErrorsToResult(result, self._outcome.errors)
        else:  # Python 3.2 - 3.3 or 3.0 - 3.1 and 2.7
            result = getattr(self, '_outcomeForDoCleanups', self._resultForDoCleanups)
        error = self.list2reason(result.errors)
        failure = self.list2reason(result.failures)
        ok = not error and not failure

        # demo:   report short info immediately (not important)
        if not ok:
            typ, text = ('ERROR', error) if error else ('FAIL', failure)
            msg = [x for x in text.split('\n')[1:] if not x.startswith(' ')][0]
            print("\n%s: %s\n     %s" % (typ, self.id(), msg))

    def list2reason(self, exc_list):
        if exc_list and exc_list[-1][0] is self:
            return exc_list[-1][1]

    # DEMO tests
    def test_success(self):
        self.assertEqual(1, 1)

    def test_fail(self):
        self.assertEqual(2, 1)

    def test_error(self):
        self.assertEqual(1 / 0, 1)

评论:只需要报告一个或零个异常(错误或失败),因为在tearDown之前不能再预期。包unittest期望tearDown可以引发第二个异常。因此,列表errorsfailures在tearDown之前只能包含一个或零个元素。 “演示”评论后的行报告了一个简短的结果。

演示输出:(不重要)

$ python3.5 -m unittest test

EF.
ERROR: test.MyTest.test_error
     ZeroDivisionError: division by zero
FAIL: test.MyTest.test_fail
     AssertionError: 2 != 1

==========================================================
... skipped usual output from unittest with tracebacks ...
...
Ran 3 tests in 0.002s

FAILED (failures=1, errors=1)

与其他解决方案的比较 - (关于Python源代码库的提交历史记录):

  • 此解决方案使用TestCase实例的私有属性 其他方案, 但我仔细检查了Python源代码库中的所有相关提交 这三个替代名称涵盖了自Python以来的代码历史 2.7至3.6.2没有任何差距。一些新专业后可能会出现问题 Python发布,但可以清楚地识别,跳过并轻松修复 后来换了一个新的Python。一个优点是之前没有任何修改 运行tearDown,它应该永远不会破坏测试和所有功能 支持unittest,可以使用pytest,它可以使用许多扩展包,但不能使用nosetest(不要太惊讶因为nosetest不兼容,例如使用unittest.expectedFailure)。

  • 在用户测试方法上使用装饰器的解决方案或使用自定义的解决方案 failureException mgilson,Pavel Repin 2nd way,kenorb)强大对抗 未来的Python 版本,但如果一切都应该完全工作,他们会像一个人一样成长 雪球有更多支持的例外和更多复制的内部 单位测试。装饰函数具有不太可读的回溯 (由一个装饰者添加更多级别),它们更复杂 调试,如果另一个更重要的装饰器,这是不愉快的 有问题。 (感谢mgilson,基本功能已经准备好并且已知 问题可以解决。)

  • 使用修改后的run方法并获取result的解决方案 参数

    • scoffey)应该有效 也适用于Python 2.6。结果的解释可以改进 问题的要求,但在Python 3.4+中没有任何作用, 因为result在tearDown调用之后更新,从未在之前更新。
    • Mark G。:(使用Python 2.7,3.2,3.3,3.4测试并使用nosetest测试)
  • exc_info()解决方案(Pavel Repin第2版)仅适用于Python 2.

  • 其他解决方案主要相似,但不完整或更多 缺点

由Python源代码库解释
= Lib / unittest / case.py =
Python v 2.7 - 3.3

class TestCase(object):
    ...
    def run(self, result=None):
        ...
        self._outcomeForDoCleanups = result   # Python 3.2, 3.3
        # self._resultForDoCleanups = result  # Python 2.7
        #                                     # Python 2.6 - no result saved
        ...
        try:
            testMethod()
        except...   # many times for different exception classes
            result.add...(self, sys.exc_info())  # _addSkip, addError, addFailure
        ...
        try:
            self.tearDown()
        ...

Python v.3.4 - 3.6

    def run(self, result=None):
        ...
        # outocome is a context manager to catch and collect different exceptions
        self._outcome = outcome  
        ...
        with outcome...(self):
            testMethod()
        ...
        with outcome...(self): 
            self.tearDown() 
        ... 
        self._feedErrorsToResult(result, outcome.errors)

注意(通过阅读Python提交消息):原因为什么测试结果与测试分离的原因是内存泄漏预防。每个异常信息都可以访问失败的进程状态的帧,包括所有局部变量。如果将帧分配给代码块中也可能失败的局部变量,则可以轻松创建跨内存refence。由于垃圾收集器的缘故,它并不可怕,但是如果内存可以正确释放,则可用内存可以更快地碎片化。这就是为什么异常信息和回溯很快转换为字符串以及为什么临时对象如self._outcome被封装并在finally块中设置为None以防止内存泄漏的原因。

答案 2 :(得分:14)

CAVEAT:目前我无法双重检查以下理论,远离开发框。所以这可能是在黑暗中拍摄的。

也许您可以在tearDown()方法中检查sys.exc_info()的返回值,如果它返回(None, None, None),您就知道测试用例成功了。否则,您可以使用返回的元组来查询异常对象。

请参阅sys.exc_info文档。

另一种更明确的方法是编写一个方法装饰器,您可以将其打到所有需要这种特殊处理的测试用例方法上。这个装饰器可以拦截断言异常,并根据它修改self中的某个状态,允许你的tearDown方法学习了什么。

@assertion_tracker
def test_foo(self):
    # some test logic

答案 3 :(得分:10)

如果您使用的是Python2,则可以使用方法_resultForDoCleanups。此方法返回TextTestResult对象:

<unittest.runner.TextTestResult run=1 errors=0 failures=0>

您可以使用此对象检查测试结果:

def tearDown(self):
    if self._resultForDoCleanups.failures:
        ...
    elif self._resultForDoCleanups.errors:
        ...
    else:
        #Success

如果您使用的是Python3,则可以使用_outcomeForDoCleanups

def tearDown(self):
    if not self._outcomeForDoCleanups.success:
        ...

答案 4 :(得分:9)

来自amatellanes&#39;回答,如果您使用的是Python3.4,则无法使用_outcomeForDoCleanups。这就是我设法破解的原因:

def _test_has_failed(self):
    for method, error in self._outcome.errors:
        if error:
            return True
    return False
很可惜,但似乎有效。

答案 5 :(得分:8)

这取决于您想要制作什么类型的报告。

如果您希望对失败采取某些操作(例如generating a screenshots),而不是使用tearDown(),则可以通过覆盖failureException来实现此目的。

例如:

@property
def failureException(self):
    class MyFailureException(AssertionError):
        def __init__(self_, *args, **kwargs):
            screenshot_dir = 'reports/screenshots'
            if not os.path.exists(screenshot_dir):
                os.makedirs(screenshot_dir)
            self.driver.save_screenshot('{0}/{1}.png'.format(screenshot_dir, self.id()))
            return super(MyFailureException, self_).__init__(*args, **kwargs)
    MyFailureException.__name__ = AssertionError.__name__
    return MyFailureException

答案 6 :(得分:3)

对于我们这些使用依赖unittest内部解决方案的解决方案感到不舒服的人来说,这是一个解决方案:

首先,我们创建一个装饰器,在TestCase实例上设置一个标志,以确定测试用例是否失败或通过:

import unittest
import functools

def _tag_error(func):
    """Decorates a unittest test function to add failure information to the TestCase."""

    @functools.wraps(func)
    def decorator(self, *args, **kwargs):
        """Add failure information to `self` when `func` raises an exception."""
        self.test_failed = False
        try:
            func(self, *args, **kwargs)
        except unittest.SkipTest:
            raise
        except Exception:  # pylint: disable=broad-except
            self.test_failed = True
            raise  # re-raise the error with the original traceback.

    return decorator

这个装饰器实际上非常简单。它依赖于unittest通过 Exceptions 检测到失败测试的事实。据我所知,唯一需要处理的特殊异常是unittest.SkipTest(并不表示测试失败)。所有其他例外都表明测试失败,所以当它们冒泡到我们时,我们会将它们标记为。

我们现在可以直接使用这个装饰器:

class MyTest(unittest.TestCase):
    test_failed = False

    def tearDown(self):
        super(MyTest, self).tearDown()
        print(self.test_failed)

    @_tag_error
    def test_something(self):
        self.fail('Bummer')

它总是会让这个装饰师一直很讨厌。有没有办法简化?是的有! * 我们可以编写一个元类来处理为我们应用装饰器:

class _TestFailedMeta(type):
    """Metaclass to decorate test methods to append error information to the TestCase instance."""
    def __new__(cls, name, bases, dct):
        for name, prop in dct.items():
            # assume that TestLoader.testMethodPrefix hasn't been messed with -- otherwise, we're hosed.
            if name.startswith('test') and callable(prop):
                dct[name] = _tag_error(prop)

        return super(_TestFailedMeta, cls).__new__(cls, name, bases, dct)

现在我们将此应用于我们的基础TestCase子类,我们已全部设置:

import six  # For python2.x/3.x compatibility

class BaseTestCase(six.with_metaclass(_TestFailedMeta, unittest.TestCase)):
    """Base class for all our other tests.

    We don't really need this, but it demonstrates that the
    metaclass gets applied to all subclasses too.
    """


class MyTest(BaseTestCase):

    def tearDown(self):
        super(MyTest, self).tearDown()
        print(self.test_failed)

    def test_something(self):
        self.fail('Bummer')

有很多情况可能无法正常处理。例如,它无法正确检测失败的subtests 或预期的失败。我对其他失败模式感兴趣,所以如果您发现我处理不当的情况,请在评论中告诉我,我会调查一下。

* 如果没有更简单的方法,我就不会使_tag_error成为私人函数; - )

答案 7 :(得分:1)

Python 2.7。

您还可以在unittest.main()之后获得结果:

t = unittest.main(exit=False)
print t.result

或使用套件:

suite.addTests(tests)
result = unittest.result.TestResult()
suite.run(result)
print result

答案 8 :(得分:1)

可以使用unittest.TestCase.id()方法检索当前测试的名称。所以在tearDown中你可以检查self.id()。

示例说明如何:

  • 查找当前测试是否有错误或错误或失败列表
  • 使用PASS或FAIL或EXCEPTION打印测试ID

此处经过测试的示例适用于@scoffey的好例子。

def tearDown(self):
    result = "PASS"
    #### find and show result for current test
    # I did not find any nicer/neater way of comparing self.id() with test id stored in errors or failures lists :-7
    id = str(self.id()).split('.')[-1]
    # id() e.g. tup[0]:<__main__.MyTest testMethod=test_onePlusNoneIsNone>
    #           str(tup[0]):"test_onePlusOneEqualsThree (__main__.MyTest)"
    #           str(self.id()) = __main__.MyTest.test_onePlusNoneIsNone
    for tup in self.currentResult.failures:
        if str(tup[0]).startswith(id):
            print ' test %s failure:%s' % (self.id(), tup[1])
            ## DO TEST FAIL ACTION HERE
            result = "FAIL"
    for tup in self.currentResult.errors:
        if str(tup[0]).startswith(id):
            print ' test %s error:%s' % (self.id(), tup[1])
            ## DO TEST EXCEPTION ACTION HERE
            result = "EXCEPTION"

    print "Test:%s Result:%s" % (self.id(), result)

结果示例:

python run_scripts/tut2.py 2>&1 
E test __main__.MyTest.test_onePlusNoneIsNone error:Traceback (most recent call last):
  File "run_scripts/tut2.py", line 80, in test_onePlusNoneIsNone
    self.assertTrue(1 + None is None) # raises TypeError
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

Test:__main__.MyTest.test_onePlusNoneIsNone Result:EXCEPTION
F test __main__.MyTest.test_onePlusOneEqualsThree failure:Traceback (most recent call last):
  File "run_scripts/tut2.py", line 77, in test_onePlusOneEqualsThree
    self.assertTrue(1 + 1 == 3) # fails
AssertionError: False is not true

Test:__main__.MyTest.test_onePlusOneEqualsThree Result:FAIL
Test:__main__.MyTest.test_onePlusOneEqualsTwo Result:PASS
.
======================================================================
ERROR: test_onePlusNoneIsNone (__main__.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "run_scripts/tut2.py", line 80, in test_onePlusNoneIsNone
    self.assertTrue(1 + None is None) # raises TypeError
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

======================================================================
FAIL: test_onePlusOneEqualsThree (__main__.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "run_scripts/tut2.py", line 77, in test_onePlusOneEqualsThree
     self.assertTrue(1 + 1 == 3) # fails
AssertionError: False is not true

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1, errors=1)

答案 9 :(得分:1)

受到scoffey’s answer的启发,我决定将 mercilessnes 提升到新的水平,并提出以下建议。

它既可以在vanilla unittest中运行,也可以在通过nosetests运行时使用,也适用于Python版本2.7,3.2,3.3和3.4(我没有专门测试3.0,3.1或3.5,因为我没有这些安装目前,但如果我正确阅读source code,它也应该在3.5中工作):

#! /usr/bin/env python

from __future__ import unicode_literals
import logging
import os
import sys
import unittest


# Log file to see squawks during testing
formatter = logging.Formatter(fmt='%(levelname)-8s %(name)s: %(message)s')
log_file = os.path.splitext(os.path.abspath(__file__))[0] + '.log'
handler = logging.FileHandler(log_file)
handler.setFormatter(formatter)
logging.root.addHandler(handler)
logging.root.setLevel(logging.DEBUG)
log = logging.getLogger(__name__)


PY = tuple(sys.version_info)[:3]


class SmartTestCase(unittest.TestCase):

    """Knows its state (pass/fail/error) by the time its tearDown is called."""

    def run(self, result):
        # Store the result on the class so tearDown can behave appropriately
        self.result = result.result if hasattr(result, 'result') else result
        if PY >= (3, 4, 0):
            self._feedErrorsToResultEarly = self._feedErrorsToResult
            self._feedErrorsToResult = lambda *args, **kwargs: None  # no-op
        super(SmartTestCase, self).run(result)

    @property
    def errored(self):
        if (3, 0, 0) <= PY < (3, 4, 0):
            return bool(self._outcomeForDoCleanups.errors)
        return self.id() in [case.id() for case, _ in self.result.errors]

    @property
    def failed(self):
        if (3, 0, 0) <= PY < (3, 4, 0):
            return bool(self._outcomeForDoCleanups.failures)
        return self.id() in [case.id() for case, _ in self.result.failures]

    @property
    def passed(self):
        return not (self.errored or self.failed)

    def tearDown(self):
        if PY >= (3, 4, 0):
            self._feedErrorsToResultEarly(self.result, self._outcome.errors)


class TestClass(SmartTestCase):

    def test_1(self):
        self.assertTrue(True)

    def test_2(self):
        self.assertFalse(True)

    def test_3(self):
        self.assertFalse(False)

    def test_4(self):
        self.assertTrue(False)

    def test_5(self):
        self.assertHerp('Derp')

    def tearDown(self):
        super(TestClass, self).tearDown()
        log.critical('---- RUNNING {} ... -----'.format(self.id()))
        if self.errored:
            log.critical('----- ERRORED -----')
        elif self.failed:
            log.critical('----- FAILED -----')
        else:
            log.critical('----- PASSED -----')


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

使用unittest

运行时
$ ./test.py -v
test_1 (__main__.TestClass) ... ok
test_2 (__main__.TestClass) ... FAIL
test_3 (__main__.TestClass) ... ok
test_4 (__main__.TestClass) ... FAIL
test_5 (__main__.TestClass) ... ERROR
[…]

$ cat ./test.log
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- FAILED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- FAILED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- ERRORED -----

使用nosetests

运行时
$ nosetests ./test.py -v
test_1 (test.TestClass) ... ok
test_2 (test.TestClass) ... FAIL
test_3 (test.TestClass) ... ok
test_4 (test.TestClass) ... FAIL
test_5 (test.TestClass) ... ERROR

$ cat ./test.log
CRITICAL test: ---- RUNNING test.TestClass.test_1 ... -----
CRITICAL test: ----- PASSED -----
CRITICAL test: ---- RUNNING test.TestClass.test_2 ... -----
CRITICAL test: ----- FAILED -----
CRITICAL test: ---- RUNNING test.TestClass.test_3 ... -----
CRITICAL test: ----- PASSED -----
CRITICAL test: ---- RUNNING test.TestClass.test_4 ... -----
CRITICAL test: ----- FAILED -----
CRITICAL test: ---- RUNNING test.TestClass.test_5 ... -----
CRITICAL test: ----- ERRORED -----

背景

开始使用

class SmartTestCase(unittest.TestCase):

    """Knows its state (pass/fail/error) by the time its tearDown is called."""

    def run(self, result):
        # Store the result on the class so tearDown can behave appropriately
        self.result = result.result if hasattr(result, 'result') else result
        super(SmartTestCase, self).run(result)

    @property
    def errored(self):
        return self.id() in [case.id() for case, _ in self.result.errors]

    @property
    def failed(self):
        return self.id() in [case.id() for case, _ in self.result.failures]

    @property
    def passed(self):
        return not (self.errored or self.failed)

但是,这仅适用于Python 2.在Python 3中,包括3.3,控制流似乎有所改变:在调用之后,Python 3的unittest包processes results 每个测试的tearDown()方法......如果我们只是在测试类中添加一行(或六行),就可以确认此行为:

@@ -63,6 +63,12 @@
             log.critical('----- FAILED -----')
         else:
             log.critical('----- PASSED -----')
+        log.warning(
+            'ERRORS THUS FAR:\n'
+            + '\n'.join(tc.id() for tc, _ in self.result.errors))
+        log.warning(
+            'FAILURES THUS FAR:\n'
+            + '\n'.join(tc.id() for tc, _ in self.result.failures))


 if __name__ == '__main__':

然后重新运行测试:

$ python3.3 ./test.py -v
test_1 (__main__.TestClass) ... ok
test_2 (__main__.TestClass) ... FAIL
test_3 (__main__.TestClass) ... ok
test_4 (__main__.TestClass) ... FAIL
test_5 (__main__.TestClass) ... ERROR
[…]

......你会看到你得到这个结果:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4

现在,将上面的内容与Python 2的输出进行比较:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- FAILED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- FAILED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- ERRORED -----
WARNING  __main__: ERRORS THUS FAR:
__main__.TestClass.test_5
WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4

由于Python 3在之后处理错误/失败,测试被拆除,我们无法在每种情况下使用result.errorsresult.failures来推断测试结果。 (我认为在删除它之后处理测试结果可能在架构上更有意义,但是,它确实使得完全有效的用例遵循不同的目的 - 测试过程取决于测试的通过/失败状态有点难以满足......)

因此,我们可以引用result而不是依赖整个_outcomeForDoCleanups对象,而others引用already,其中包含当前运行的结果对象测试,并具有必要的errorsfailrues属性,我们可以使用这些属性在调用tearDown()时推断测试的状态:

@@ -3,6 +3,7 @@
 from __future__ import unicode_literals
 import logging
 import os
+import sys
 import unittest


@@ -16,6 +17,9 @@
 log = logging.getLogger(__name__)


+PY = tuple(sys.version_info)[:3]
+
+
 class SmartTestCase(unittest.TestCase):

     """Knows its state (pass/fail/error) by the time its tearDown is called."""
@@ -27,10 +31,14 @@

     @property
     def errored(self):
+        if PY >= (3, 0, 0):
+            return bool(self._outcomeForDoCleanups.errors)
         return self.id() in [case.id() for case, _ in self.result.errors]

     @property
     def failed(self):
+        if PY >= (3, 0, 0):
+            return bool(self._outcomeForDoCleanups.failures)
         return self.id() in [case.id() for case, _ in self.result.failures]

     @property

这增加了对早期版本的Python 3的支持。

从Python 3.4开始,这个私有成员变量no longer exists,而且添加了一个新的(虽然也是私有)方法:_feedErrorsToResult

这意味着对于版本3.4(and later),如果需要足够强大,可以 - 非常hackishly - 强制一个方法,让它再次像在版本2中那样工作......

@@ -27,17 +27,20 @@
     def run(self, result):
         # Store the result on the class so tearDown can behave appropriately
         self.result = result.result if hasattr(result, 'result') else result
+        if PY >= (3, 4, 0):
+            self._feedErrorsToResultEarly = self._feedErrorsToResult
+            self._feedErrorsToResult = lambda *args, **kwargs: None  # no-op
         super(SmartTestCase, self).run(result)

     @property
     def errored(self):
-        if PY >= (3, 0, 0):
+        if (3, 0, 0) <= PY < (3, 4, 0):
             return bool(self._outcomeForDoCleanups.errors)
         return self.id() in [case.id() for case, _ in self.result.errors]

     @property
     def failed(self):
-        if PY >= (3, 0, 0):
+        if (3, 0, 0) <= PY < (3, 4, 0):
             return bool(self._outcomeForDoCleanups.failures)
         return self.id() in [case.id() for case, _ in self.result.failures]

@@ -45,6 +48,10 @@
     def passed(self):
         return not (self.errored or self.failed)

+    def tearDown(self):
+        if PY >= (3, 4, 0):
+            self._feedErrorsToResultEarly(self.result, self._outcome.errors)
+

 class TestClass(SmartTestCase):

@@ -64,6 +71,7 @@
         self.assertHerp('Derp')

     def tearDown(self):
+        super(TestClass, self).tearDown()
         log.critical('---- RUNNING {} ... -----'.format(self.id()))
         if self.errored:
             log.critical('----- ERRORED -----')

...提供,当然,此类的所有消费者都记得super(…, self).tearDown()方法中的tearDown ......

免责声明:纯粹教育,不要在家等等尝试这个。我对此解决方案并不特别自豪,但它似乎暂时运作良好在星期六下午摆弄一两个小时之后,我是最好的傻瓜......

答案 10 :(得分:1)

我认为您问题的正确答案是,没有一种 clean 方法可以在tearDown()中获得测试结果。这里的大多数答案都涉及访问python unittest模块的一些私有部分,并且通常感觉是解决方法。我强烈建议避免使用这些方法,因为测试结果和测试用例是分离的,因此您不应该这样做。

如果您喜欢干净的代码(例如我),我想您应该使用自己的TestRunner类实例化TestResult。然后,您可以通过覆盖以下方法添加所需的报告:

addError(test, err)
Called when the test case test raises an unexpected exception. err is a tuple of the form returned by sys.exc_info(): (type, value, traceback).

The default implementation appends a tuple (test, formatted_err) to the instance’s errors attribute, where formatted_err is a formatted traceback derived from err.

addFailure(test, err)
Called when the test case test signals a failure. err is a tuple of the form returned by sys.exc_info(): (type, value, traceback).

The default implementation appends a tuple (test, formatted_err) to the instance’s failures attribute, where formatted_err is a formatted traceback derived from err.

addSuccess(test)
Called when the test case test succeeds.

The default implementation does nothing.

答案 11 :(得分:0)

经过python 3.7的测试-用于获取断言失败信息的示例代码,但可以给出如何处理错误的想法:

def tearDown(self):
    if self._outcome.errors[1][1] and hasattr(self._outcome.errors[1][1][1], 'actual'):
        print(self._testMethodName)
        print(self._outcome.errors[1][1][1].actual)
        print(self._outcome.errors[1][1][1].expected)

答案 12 :(得分:0)

简而言之,如果到目前为止所有测试运行均已退出,且没有错误或失败,则会给出comments

True

浏览class WatheverTestCase(TestCase): def tear_down(self): return not self._outcome.result.errors and not self._outcome.result.failures 的属性以访问更详细的可能性。