将Python的unittest结果放在tearDown()方法中

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

CAVEAT:我目前还没有办法双重检查下面的理论,远离开箱。 所以这可能是在黑暗中的一个镜头。

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

请参阅sys.exc_info文档。

另外一个更明确的方法是编写一个方法装饰器,可以将它放在所有需要特殊处理的testing用例方法上。 这个装饰器可以拦截断言exception,并根据这个修改自己的某个状态,允许你的tearDown方法学习到什么。

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

如果你看一下unittest.TestCase.run的实现,你可以看到所有的testing结果都被收集在作为parameter passing的结果对象(通常是一个unittest.TestResult实例)中。 unittest.TestCase对象中没有结果状态。

所以,你可以在unittest.TestCase.tearDown方法中做很多事情,除非你毫不留情地打破testing用例和testing结果的优雅解耦,如下所示:

 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 波纹pipe )。

这个解决scheme适用于Python版本2.7到3.63.7-alpha之前的最新版本和开发),在tearDown之前的任何代码中没有任何装饰器或其他修改。 一切工作根据内置的分类结果。 也跳过testing或expectedFailure被正确识别。 它对当前testing的结果进行评估,而不是对迄今为止所有testing的总结。 兼容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之前不会有更多的例外。 package unittest期望可以通过tearDown引发第二个exception。 因此,列表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) 

与其他解决scheme比较 (关于提交Python源代码库的历史logging):

  • 这个解决scheme像许多其他解决scheme一样使用TestCase实例的私有属性 ,但是我仔细检查了Python源代码库中的所有相关提交,其中三个替代名称涵盖了从Python 2.7到3.6.2以后的代码历史logging,没有任何差距。 在一些新的主要的Python发行版之后,这可能会成为一个问题,但是对于一个新的Python来说,它可以被清楚地识别,跳过并且很容易修复。 一个好处是没有任何修改之前运行tearDown,它不应该打破testing和unit testing的所有function都支持,使用pytest,可以工作很多扩展包,但不nosetest(不是一个惊喜becase nosetest不兼容,例如与unittest.expectedFailure)。

  • 使用用户testing方法的装饰器解决scheme或定制的failureException ( mgilson ,Pavel Repin 2nd way,kenorb)对于未来的Python版本都是健壮的,但是如果所有东西都应该完全工作的话,它们就会像雪球一样增长,unit testing更复制的内部。 装饰函数的可读性较差(甚至一个装饰器添加了更多的级别),它们对于debugging更加复杂,而且如果另一个更重要的装饰器出现问题,它是不太容易的。 (感谢mgilson的基本function已经准备就绪,已知问题可以修复。)

  • 解决方法是修改run方法并获取result参数

    • ( scoffey )也适用于Python 2.6。 结果的解释可以改善到问题的要求,但在Python 3.4+中没有任何工作,因为result在tearDown调用之后更新,从来没有。
    • Mark G .:(用Python 2.7,3.2,3.3,3.4和nosetesttesting)
  • 解决schemeexc_info() (Pavel Repin 2st方式)只适用于Python 2。

  • 其他解决scheme主要是相似的,但不太完整或有更多的缺点。


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

注意(通过阅读提交消息):为什么testing结果与testing非常分离的一个原因是内存泄漏的预防。 每个exception信息都可以访问失败进程状态的帧,包括所有局部variables。 如果一个帧被分配给一个代码块中的一个局部variables,也可能失败,那么可以很容易地创build一个交叉存储区域。 这并不可怕,这要归功于垃圾回收器,但是释放的内存可能会比内存释放正确的时候更加快速。 这就是为什么exception信息和追踪很快转换为string的原因,以及为什么临时对象self._outcome被封装,并在finally块中设置为None,以防止内存泄漏。

如果您正在使用Python2,则可以使用_resultForDoCleanups方法。 这个方法返回一个TextTestResult对象:

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

你可以使用这个对象来检查你的testing结果:

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

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

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

从amatellanes的答案继续,如果你在Python3.4,你不能使用_outcomeForDoCleanups 。 以下是我设法破解的内容:

 def _test_has_failed(self): for method, error in self._outcome.errors: if error: return True return False 

可怜的,但它似乎工作。

这取决于你想要制作什么types的报告。

如果您希望在失败时执行一些操作(例如生成屏幕截图 ),而不是使用tearDown() ,则可以通过重写failureException来实现该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 

对于那些依赖unit testing内部解决scheme的人来说,这是一个解决scheme:

首先,我们创build一个装饰器,它将在TestCase实例上设置一个标志来确定testing用例是否失败或通过:

 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检测失败的testing的事实。 据我所知,唯一需要处理的特殊exception是unittest.SkipTest (不表示testing失败)。 所有其他的例外都表明testing失败,所以当我们把它们标记为泡泡时,

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

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

可能有很多情况下,这不能正确处理。 例如, 它不能正确地检测失败的子testing或预期的失败。 我会对其他失败模式感兴趣,所以如果你发现一个我没有正确处理的情况,请在评论中告诉我,我会研究它。


*如果没有更简单的方法,我不会做_tag_error一个私人的function;-)

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 

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

示例显示如何:

  • 发现当前的testing是否在错误或失败列表中有错误或失败
  • 用PASS或FAIL或EXCEPTION打印testingID

这里testing的例子可以用@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() eg 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) 

受到斯科菲的回答的启发,我决定把mercilessnes带到下一个层次,并提出以下几点。

它既适用于vanillaunit testing,也适用于通过noseteststesting,也适用于Python版本2.7,3.2,3.3和3.4(我没有特别testing3.0,3.1或3.5,因为我没有安装在但是如果我正确地阅读了源代码 ,它也应该在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,控制stream似乎已经改变了一点: 调用每个testing的tearDown()方法后, Python 3的unittest包处理结果 …如果我们只需在testing类中添加一行(或六个):

 @@ -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__': 

然后重新运行testing:

 $ 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在testing结束后会处理错误/失败,所以我们无法轻易推断每种情况下使用result.errorsresult.failures的testing结果。 (我认为从结构上来说,在testing结果处理结束之后可能会更有意义,但是,根据testing的合格/不合格状态,它可以完成一个完全有效的testing结束过程很难遇见…)

因此,我们可以引用_outcomeForDoCleanups ,而不像其他人已经提到的那样,它包含了当前正在运行的testing的结果对象,并且具有必要的errorsfailrues属性,我们可以用它来推断testing的结果状态的时间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开始,这个私有成员variables不再存在 ,而是添加了一个新的(尽pipe也是私有的)方法: _feedErrorsToResult

这意味着对于版本3.4( 及更高版本 ),如果需求足够大,可以 – 非常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方法…

免责声明:纯粹的教育,不要在家里等这样的等等。我不是特别为这个解决scheme感到自豪,但它似乎现在工作得够好,是我可以破解后最好的在周六下午摆弄一两个小时