如何在Python unittest2中仅在测试失败时执行代码?

12
我正在使用Python的unittest2框架进行基于类的单元测试。我们正在使用Selenium WebDriver,它有一个方便的save_screenshot()方法。我想在tearDown()中为每个测试失败抓取屏幕截图,以减少调试时间和精力。
但是,我无法找到仅在测试失败时运行代码的方法。无论测试是否成功,都会调用tearDown(),我不想在测试成功时在文件系统中添加成百上千个浏览器截图。
您会如何解决这个问题?
5个回答

8

找到了一个解决方案 - 我可以覆盖 failureException:

@property
def failureException(self):
    class MyFailureException(AssertionError):
        def __init__(self_, *args, **kwargs):
            self.b.save_screenshot('%s.png' % self.id())
            return super(MyFailureException, self_).__init__(*args, **kwargs)
    MyFailureException.__name__ = AssertionError.__name__
    return MyFailureException

这看起来非常糟糕,但目前似乎可以工作。


哇,这是一个多么巧妙的方法。一个异常类,被包装在一个函数中,再被包装在一个属性中,所有这些都是为了将TestCase实例传递到异常的__init__()方法中。虽然丑陋,但至少它能够工作! - kindall

3

这里提供类似于@craigds回答的方法,但支持目录,并且更好地兼容Python 3:

@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

这实际上是在这个博客中发现的。
我用argparse进一步扩展了它:
parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")     

所以目录可以通过系统变量或传递的参数来动态指定:
screenshot_dir = os.environ.get('REPORTS_DIR', self.args.dir) + '/screenshots'

如果您有额外的包装器来运行所有脚本,例如基类,那么这特别有用。


2

重写fail()方法以生成屏幕截图,然后调用TestCase.fail(self)方法?


1
只有当测试用例失败时,才会有所帮助,因为有人调用了 self.fail()。其他失败,例如 self.assertTrue() 等则会绕过此项检查。 - craigds
真的吗?我无法相信他们没有那些调用fail()。当然,你也可以覆盖所有其他方法(叹气)。 - kindall
有些人会这样做,但其他人则不会。看起来有点疯狂 ;) - craigds

2

sys.exc_info()应该提供有关测试是否失败的退出信息。因此,类似于以下内容:

def tearDown(self):
    if sys.exc_info()[0]:
        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../failures', self.driver.browser)
        if not os.path.exists(path):
            try:
                os.makedirs(path)
            except Exception:
                # Since this might not be thread safe
                pass
        filename = '%s.%s.png' % (self.__class__.__name__, self._testMethodName)
        file_path = os.path.join(path, filename)
        self.driver.get_screenshot_as_file(file_path)

1

在每个测试周围使用装饰器。

记住装饰新测试的最安全方法,或避免返回并装饰一堆现有测试的方法是使用元类来包装所有测试函数。 如何包装类的每个方法? 答案提供了所需的基础知识。

你可能应该将被包装的函数过滤为只有测试,例如:

class ScreenshotMetaClass(type):
    """Wraps all tests with screenshot_on_error"""
    def __new__(meta, classname, bases, classDict):
        newClassDict = {}
        for attributeName, attribute in classDict.items():
            if type(attribute) == FunctionType and 'test' in attributeName.lower():
                # replace the function with a wrapped version
                attribute = screenshot_on_error(attribute)
            newClassDict[attributeName] = attribute
        return type.__new__(meta, classname, bases, newClassDict)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接