GAE:单元测试DeadlineExceededError

时间:2011-08-24 22:16:32

标签: python unit-testing google-app-engine

我一直在使用testbed,webtest和nose来测试我的Python GAE应用程序,这是一个很棒的设置。我现在正在实现与Nick's great example of using the deferred library类似的功能,但我无法找到一种测试DeadlineExceededError触发的代码部分的好方法。

由于这是在任务队列的上下文中,因此构建一个运行时间超过10分钟的测试会很痛苦。有没有办法暂时将任务队列时间限制设置为几秒钟以进行测试?或者可能还有其他方法可以优雅地测试except DeadlineExceededError块中代码的执行情况?

1 个答案:

答案 0 :(得分:1)

为您的代码摘要“GAE上下文”。在生产中为测试提供真正的“GAE实现”提供了一个模拟自己,它将引发DeadlineExceededError。测试不应该依赖于任何超时,应该很快。

样本抽象(只是粘合):

class AbstractGAETaskContext(object):
  def task_spired(): pass # this will throw exception in mock impl

  # here you define any method that you call into GAE, to be mocked
  def defered(...): pass

如果你不喜欢抽象,你可以只进行猴子修补测试,也需要将task_expired函数定义为测试的钩子。 在任务实现功能期间应该调用task_expired。

* UPDATED *这是第3个解决方案:

首先,我想提一下Nick's sample implementation并不是那么好,Mapper类必须承担很多责任(推迟,查询数据,批量更新);这使得测试难以进行,需要定义很多模拟。所以我在单独的课程中提取延期责任。 您只想测试延迟机制,实际发生的事情(更新,查询等)应该在其他测试中处理。

这是deffering课程,这也不再取决于GAE:

class DeferredCall(object):
  def __init__(self, deferred):
    self.deferred = deferred

  def run(self, long_execution_call, context, *args, **kwargs):
    ''' long_execution_call should return a tuple that tell us how was terminate operation, with timeout and the context where was abandoned '''
    next_context, timeouted = long_execution_call(context, *args, **kwargs)
    if timeouted:
        self.deferred(self.run, next_context, *args, **kwargs)

以下是测试模块:

class Test(unittest.TestCase):
    def test_defer(self):
        calls = []
        def mock_deferrer(callback, *args, **kwargs):
            calls.append((callback, args, kwargs))

        def interrupted(self, context):
            return "new_context", True

        d = DeferredCall()
        d.run(interrupted, "init_context")
        self.assertEquals(1, len(calls), 'a deferred call should be')


    def test_no_defer(self):
        calls = []
        def mock_deferrer(callback, *args, **kwargs):
            calls.append((callback, args, kwargs))

        def completed(self, context):
            return None, False

        d = DeferredCall()
        d.run(completed, "init_context")
        self.assertEquals(0, len(calls), 'no deferred call should be')

如何看待Nick的Mapper实现:

class Mapper:
    ...
    def _continue(self, start_key, batch_size):
        ... # here is same code, nothing was changed
        except DeadlineExceededError:
            # Write any unfinished updates to the datastore.
            self._batch_write()
            # Queue a new task to pick up where we left off.
            ##deferred.defer(self._continue, start_key, batch_size)
            return start_key, True ## make compatible with DeferredCall
            self.finish()
            return None, False ## make it comaptible with DeferredCall

    runner = _continue

您注册长时间运行任务的代码;这仅取决于GAE推迟的lib。

import DeferredCall
import PersonMapper # this inherits the Mapper
from google.appengine.ext import deferred

mapper = PersonMapper()
DeferredCall(deferred).run(mapper.run)
相关问题