在 Django 单元测试中使用 mock 修补 celery 任务

Posted

技术标签:

【中文标题】在 Django 单元测试中使用 mock 修补 celery 任务【英文标题】:Using mock to patch a celery task in Django unit tests 【发布时间】:2013-08-15 05:57:56 【问题描述】:

我正在尝试使用 python 模拟库来修补在我的 django 应用程序中保存模型时运行的 Celery 任务,以查看它是否被正确调用。

基本上,任务是在 myapp.tasks 中定义的,并像这样在我的 models.py 文件的顶部导入:

from .tasks import mytask

...然后使用mytask.delay(foo, bar) 在模型内部的save() 上运行。到目前为止一切顺利 - 当我实际运行 Celeryd 等时效果很好。

我想构建一个模拟任务的单元测试,只是为了检查它是否被正确的参数调用,并且实际上并没有尝试运行 Celery 任务。

所以在测试文件中,我在标准 TestCase 中有这样的内容:

from mock import patch # at the top of the file

# ...then later
def test_celery_task(self):
    with patch('myapp.models.mytask.delay') as mock_task:
        # ...create an instance of the model and save it etc
        self.assertTrue(mock_task.called)

...但它永远不会被调用/总是错误的。我尝试了各种化身(修补myapp.models.mytask,并检查是否改为调用mock_task.delay。我从模拟文档中收集到导入路径至关重要,谷歌搜索告诉我它应该是路径在被测模块内部看到(如果我理解正确,应该是 myapp.models.mytask.delay 而不是 myapp.tasks.mytask.delay)。

我哪里错了?修补 Celery 任务是否有一些特定的困难?我可以修补celery.task(用作mytask 的装饰器)吗?

【问题讨论】:

您是否尝试过设置“CELERY_ALWAYS_EAGER = True”而不是模拟它? 【参考方案1】:

@task 装饰器将函数替换为Task 对象(请参阅documentation)。如果您模拟任务本身,您将用MagicMock 替换(有点神奇)Task 对象,它根本不会安排任务。而是模拟 Task 对象的 run() 方法,如下所示:

@override_settings(CELERY_ALWAYS_EAGER=True)
@patch('monitor.tasks.monitor_user.run')
def test_monitor_all(self, monitor_user):
    """
    Test monitor.all task
    """

    user = ApiUserFactory()
    tasks.monitor_all.delay()
    monitor_user.assert_called_once_with(user.key)

【讨论】:

您在两个问题上逐字逐句地发布这个答案是有原因的吗? @Micheled'Amico:我不同意,如果你模拟这个解释的任务本身,答案就谈到了它没有运行的具体问题。模拟 delay() 方法,虽然可能在特定情况下工作实际上是错误的答案,因为您以后可能会更改代码以使用 apply_async() 或其他方法,突然您的测试会因为错误的原因而中断。 +1 @DanielleMadeley。我最初修补了延迟并且测试运行良好,直到我们需要将它们链接为子任务并且一切都匆匆忙忙! 在较新的版本中,我们需要使用CELERY_TASK_ALWAYS_EAGER 我认为这是一个更好的答案,因为它适用于链式任务以及使用apply_async 调用的任务(提供countdown 参数)。【参考方案2】:

您遇到的问题与这是 Celery 任务这一事实无关。你只是碰巧修补了错误的东西。 ;)

具体来说,您需要找出哪个视图或其他文件正在导入“mytask”并在那里对其进行修补,因此相关行如下所示:

with patch('myapp.myview.mytask.delay') as mock_task:

这里有更多的味道:

http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch

【讨论】:

干杯!我还没有尝试过(项目现在处于休眠状态)但很快就会尝试并将其标记为已回答。我似乎记得在你建议的主题上尝试了一堆变体,但很有可能我当时的血糖很低...... :-) 实际上,我正在按照您的建议执行此操作,如问题代码中所示...无法正常工作。哦,好吧。 问题是修补模型。这闻起来很不对劲,因为我怀疑您没有在模型中使用“延迟”,而是在其他地方使用 - 可能是视图,因此我的补丁代码(上图)略有不同。 好吧,实际上,我在模型上被覆盖的save() 方法中调用了mytask.delay() 。 (我正在使用该任务将一些数据从模型发送到外部系统)。这会对修补的工作方式产生影响吗? 返回并从头开始尝试一切:这一次效果很好。不知道我以前做错了什么,但我很高兴。 :-)

以上是关于在 Django 单元测试中使用 mock 修补 celery 任务的主要内容,如果未能解决你的问题,请参考以下文章

使用 unittest.mock 在 python 中修补 SMTP 客户端

使用放心模拟 mvc 修补端点测试

使用unittest.mock在python中修补SMTP客户端

在 monotouch/monodroid 中使用 mocks 进行单元测试

Python——单元测试中mock原理和使用

java mock框架 —— Mcktio