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

Joe*_*ert 62 python unit-testing nose

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

sco*_*fey 39

如果查看实现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()
Run Code Online (Sandbox Code Playgroud)

编辑:这适用于Python 2.6 - 3.3,(修改为新的Python 下面).


hyn*_*cer 36

此解决方案适用于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)
Run Code Online (Sandbox Code Playgroud)

注释:只需报告一个或零个异常(错误或失败),因为之前不能再预期tearDown.该程序包unittest期望tearDown可以引发第二个异常.因此,列表errorsfailures可以包含拆卸之前一起只有一个或零个元素."演示"评论后的行报告了一个简短的结果.

演示输出:(不重要)

$ 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)
Run Code Online (Sandbox Code Playgroud)

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

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

  • 在用户测试方法上使用装饰器或使用自定义 failureException(mgilson,Pavel Repin 2nd way,kenorb)的解决方案对于未来的Python版本是强大的,但是如果一切都应该完全有效,它们会像雪球一样增长,具有更多支持的异常和更多复制的单元测试内部.装饰函数具有不太可读的回溯(甚至更多级别由一个装饰器添加),它们对于调试来说更复杂,并且如果另一个更重要的装饰器有问题则是不愉快的.(感谢mgilson,基本功能已准备就绪,可以修复已知问题.)

  • 具有修改run方法和捕获result 参数的解决方案

    • (scoffey)也适用于Python 2.6.结果的解释可以改善这个问题的要求,但没有任何东西可以在Python 3.4+工作,因为result拆机电话后更新,前所未有.
    • Mark G.:(使用Python 2.7,3.2,3.3,3.4测试并使用nosetest测试)
  • 解决方案exc_info()(Pavel Repin第2版)仅适用于Python 2.

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


由Python source repository
= 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()
        ...
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

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

  • @ShanthaDodmane:谢谢.我在2天后发现了这个解决方案:-)阅读Python git存储库,验证它是否正确,但是为时已晚,无法引起注意. (2认同)

Pav*_*pin 14

CAVEAT:目前我没办法仔细检查以下理论,远离开发盒.所以这可能是在黑暗中拍摄的.

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

请参阅sys.exc_info文档.

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

@assertion_tracker
def test_foo(self):
    # some test logic
Run Code Online (Sandbox Code Playgroud)

  • 不适合我.`sys.exc_info`总是3 Nones,即使在失败的测试中也是如此.可能与Python3 unittest不同? (8认同)
  • @mgilson这个`exc_info()`的解决方案自从Python 3.0(最新的3.0版预发行版或2009年6月的3.1.0稳定版)以来已被打破,因为原来的`sys.exc_info()`只能在最里面访问`try:... except:...`在Python 3中阻止.它会在外面自动清除.Python 3.2到3.7-dev中的模块unittest在离开"except"块之前保存`exc_info()`或者在Python 3.0,3.1中将exc_info的重要部分转换为字符串.(我现在在所有上述Python版本上验证了它.) (5认同)
  • 不幸的是,这并不能在“错误”和“失败”之间进行区分-http://docs.python.org/library/unittest.html#organizing-test-code (2认同)
  • 出于好奇,这不适用于最新的python版本。我相信它会在python3.4(ish)处中断。 (2认同)

ama*_*nes 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
Run Code Online (Sandbox Code Playgroud)

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

def tearDown(self):
    if not self._outcomeForDoCleanups.success:
        ...
Run Code Online (Sandbox Code Playgroud)

  • `._outcomeForDoCleanups` 已在 3.4 中消失。有一个叫做“._outcome”的东西,但它似乎没有暴露测试通过/失败状态...... (2认同)

hwj*_*wjp 9

继amatellanes的回答之后,如果您使用的是Python3.4,则无法使用_outcomeForDoCleanups.这是我设法破解的内容:

def _test_has_failed(self):
    for method, error in self._outcome.errors:
        if error:
            return True
    return False
Run Code Online (Sandbox Code Playgroud)

令人讨厌,但它似乎工作.


ken*_*orb 8

这取决于您希望生成哪种报告.

如果您想要在失败时执行某些操作(例如生成屏幕截图),而不是使用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
Run Code Online (Sandbox Code Playgroud)