在单元测试中捕获KeyboardInterrupt的位置?

时间:2016-08-26 14:33:02

标签: python unit-testing python-unittest

我是使用Appium服务器在移动设备上进行UI测试。我希望能够在开发测试时取消测试过程。目前,当我CTRL-C退出进程(python unittest)时,我必须重新启动appium服务器,因为会话没有正确关闭(这可以在tearDown()方法中完成测试,但是因为我按CTRL-C,这将不会被执行。)相反,我希望每次测试被tearDown()取消时都会KeyboardInterrupt点火。

现在是我的问题:我在哪里放置try-catch块来实现这个目标?在Python单元测试中是否有处理此问题的最佳实践?我需要在self.driver.quit()触发后立即访问类变量(KeyboardInterrupt)。类变量位于放入unittest.TestSuite正在运行的unittest.TextTestRunner的类中。

try:
  self.test_something()
except KeyboadInterrupt:
  self.driver.quit()

我看了unittest.TestResult及其stop()方法,但没有找到解释其用法的实际例子。

2 个答案:

答案 0 :(得分:2)

您可以根据需要更改Python的默认行为:

import signal

def my_ctrlc_handler(signal, frame):
    driver_class.quit()
    raise KeyboardInterrupt

signal.signal(signal.SIGINT, my_ctrlc_handler)

答案 1 :(得分:0)

我制作了一个自定义TestCase,以确保如果用tearDown()取消了测试,则tearDownClass()tearDownModule()Ctrl-C都可以运行。如果在tearDown()中引发了 any 异常,它还可以确保setUp()等全部运行。

代码

class SafeTestCase(TestCase):
    """
    SafeTestCase makes sure that tearDown / cleanup methods are always run when
    They should be.
    """

    def run(self, result=None):
        test_method = getattr(self, self._testMethodName)
        wrapped_test = self._cleanup_wrapper(test_method, KeyboardInterrupt)
        setattr(self, self._testMethodName, wrapped_test)

        self.setUp = self._cleanup_wrapper(self.setUp, BaseException)

        return super().run(result)

    def _cleanup_wrapper(self, method, exception):
        def wrapped(*args, **kwargs):
            try:
                return method(*args, **kwargs)
            except exception:
                self.tearDown()
                self.doCleanups()
                raise

        return wrapped

用法

要使用此功能,只需确保您的TestCase继承自SafeTestCase而不是unittest.TestCase

示例

class MyTestCase(SafeTestCase):

    @classmethod
    def setUpClass(cls) -> None:
        print('setUpClass entered')
        print('setUpClass exited')

    @classmethod
    def tearDownClass(cls) -> None:
        print('tearDownClass entered')
        print('tearDownClass exited')

    def setUp(self) -> None:
        print('setUp entered')
        # raise ValueError()  # tearDown() and tearDownClass() will still run
        print('setUp exited')

    def tearDown(self):
        print('tearDown entered')
        foo = 1
        print('tearDown exited')

    def test_test(self):
        print('test entered')
        raise KeyboardInterrupt()   # tearDown() and tearDownClass() will still run
        print('test exited')

将打印类似

setUpClass entered
setUpClass exited
setUp entered
setUp exited
test entered
tearDown entered
tearDown exited
tearDownClass entered
tearDownClass exited

并切换注释哪个异常将打印类似

setUpClass entered
setUpClass exited
setUp entered
tearDown entered
tearDown exited
tearDownClass entered
tearDownClass exited

注意事项

  • 所有tearDown方法必须是幂等的。可能需要拆开一步 将在没有运行相应的setUp的情况下运行。
  • 如果SetUpClass或SetUpModule中引发了异常,则tearDowns将 不能运行。 (解决此问题需要制作一个自定义的TestSuite 如果您通过PyCharm运行测试,则将被忽略。)