【问题标题】:Want to disable signals in Django testing想要在 Django 测试中禁用信号
【发布时间】:2013-09-03 03:52:37
【问题描述】:

所以我有各种跨应用程序发送的信号和处理程序。但是,当我执行测试/进入“测试模式”时,我希望禁用这些处理程序。

在测试模式下是否有特定于 Django 的禁用信号/处理程序的方法?我可以想到一个非常简单的方法(在 if TESTING 子句中包含处理程序),但我想知道 Django 中是否有更好的方法?...

【问题讨论】:

  • 也许你可以在你的 settings_test.py 中声明一个标志变量,并且只在这个标志为假时绑定信号。像'DEBUG'标志。一种简单的方法是创建自己的信号函数。我将发布最优雅的方法。

标签: django django-testing django-signals


【解决方案1】:

我在为一组测试用例禁用信号时发现了这个问题,Germano 的回答让我找到了解决方案,但它采用了相反的方法,所以我想我会添加它。

在你的测试课中:

class MyTest(TestCase):
    def setUp(self):
        # do some setup
        signals.disconnect(listener, sender=FooModel)

我没有在添加信号时添加决策代码,而是在测试时禁用了它,这对我来说是一个更好的解决方案(因为测试应该围绕代码编写,而不是围绕测试编写代码)。希望对同一条船上的人有用!

编辑:自从写这篇文章以来,我被介绍了另一种禁用信号进行测试的方法。这需要 factory_boy 包 (v2.4.0+),它对于简化 Django 中的测试非常有用。你真的被宠坏了:

import factory
from django.db.models import signals

class MyTest(TestCase):
    @factory.django.mute_signals(signals.pre_save, signals.post_save)
    def test_something(self):

感谢 ups 的警告:它在工厂内部和创建对象时使信号静音,但在您想要显式 save() 时不会进一步在内部测试 - 信号将在那里取消静音。如果这是一个问题,那么在 setUp 中使用简单的断开连接可能是可行的方法。

【讨论】:

  • 太棒了!这应该是公认的答案,因为另一个需要重写源代码(可能不是您自己的代码)。
  • @krischan:对工厂的评论 - 它会在 factory 和创建对象时使信号静音,但当您想要明确 save() 时不会在内部测试 - 信号将在那里取消静音。
  • @ups 啊有趣我不知道!我将编辑答案以包含该注释
  • @krischan 我警告不要编辑解决方案,而是更喜欢第一个。尤其是禁用pre_savepost_save。谁知道还有什么其他重要的事情在听那些。我认为静音/模拟接收者(显式)比发送者(可以连接到许多被隐式静音的接收者)更好
  • 那个signal.disconnect应该是signals.disconnect
【解决方案2】:

如果您不想使用 FactoryBoy,这里有一个完整的示例,其中包含有关如何在测试中禁用特定信号的导入。

from django.db.models import signals
from myapp.models import MyModel

class MyTest(TestCase):

    def test_no_signal(self):
        signals.post_save.disconnect(sender=MyModel, dispatch_uid="my_id")

        ... after this point, the signal is disabled ...

这应该与您的接收器匹配,此示例将匹配此接收器:

@receiver(post_save, sender=MyModel, dispatch_uid="my_id")

我试图在不指定 dispatch_uid 的情况下禁用信号,但它不起作用。

【讨论】:

    【解决方案3】:

    不,没有。不过,您可以轻松地建立条件连接:

    import sys
    
    if not 'test' in sys.argv:
        signal.connect(listener, sender=FooModel)
    

    【讨论】:

    • 我本来打算 :)....最初尝试接受它,但必须等待才能接受....所以先去做了其他事情
    • 请注意,测试始终使用DEBUG = False (docs) 运行,因此在这种情况下,信号仍将在测试中连接。
    • 由于@julen 的评论,这不应该是公认的答案
    • @EmilStensröm 你是对的。我刚刚更新了答案。
    【解决方案4】:

    除了我使用@krischan 的Factory Boy 时,所有答案都对我不起作用。

    就我而言,我想禁用属于另一个包 django_elasticsearch_dsl 的信号,我无法找到 recieverdispatch_uid

    我不想添加 Factory Boy 包,我设法通过阅读其代码来禁用信号以了解信号是如何静音的,结果非常简单:

    from django.db.models import signals
    
    class MyTest(TestCase):
        def test_no_signal(self):
            signals.post_save.receivers = []
    

    我们可以用我们想要禁用的适当信号替换post_save,也可以将它放在setUp 方法中进行所有测试。

    【讨论】:

      【解决方案5】:

      如果您将接收器连接到 AppConfig.ready 中的信号,这是文档推荐的,请参阅 https://docs.djangoproject.com/en/2.2/topics/signals/#connecting-receiver-functions,您可以创建一个替代 AppConfig 用于与其他信号接收器的测试。

      【讨论】:

        【解决方案6】:

        我遇到了类似的问题,无法使用 signals.post_save.disconnect() 成功断开信号。找到了this 替代方法,该方法创建一个装饰器来覆盖指定测试和信号上的SUSPEND_SIGNALS 设置。可能对同一条船上的任何人都有用。

        首先,创建装饰器:

        import functools
        
        from django.conf import settings
        from django.dispatch import receiver
        
        def suspendingreceiver(signal, **decorator_kwargs):
            def our_wrapper(func):
                @receiver(signal, **decorator_kwargs)
                @functools.wraps(func)
                def fake_receiver(sender, **kwargs):
                    if settings.SUSPEND_SIGNALS:
                        return
                    return func(sender, **kwargs)
                return fake_receiver
            return our_wrapper
        

        用新的替换信号上常用的 @receiver 装饰器:

        @suspendingreceiver(post_save, sender=MyModel)
        def mymodel_post_save(sender, **kwargs):
            work()
        

        在您的 TestCase 上使用 Django 的 override_settings()

        @override_settings(SUSPEND_SIGNALS=True)
        class MyTestCase(TestCase):
            def test_method(self):
                Model.objects.create()  # post_save_receiver won't execute
        

        感谢撰写博客的 Josh Smeaton。

        【讨论】:

          【解决方案7】:

          您可以执行以下操作

          from factory.django import mute_signals
          from django.db.models import signals
          def test_your_code():
              with mute_signals(signals.post_save):
                  # ... code that doesn't trigger signals
          

          【讨论】:

            【解决方案8】:

            最简单的方法是使用夹具。只需将此函数放在您的测试文件中,放在将自动运行的任何类之外。

            from django.db.models.signals import pre_save, post_save
            
            @pytest.fixture(autouse=True) # Automatically use in tests.
            def mute_signals(request):
                post_save.receivers = []
                pre_save.receivers = []
            

            更多详情请看https://www.cameronmaske.com/muting-django-signals-with-a-pytest-fixture/

            【讨论】:

              【解决方案9】:

              Factory library 有一个函数,您可以将其用作函数的固定装置。 这是一个如何实现它的示例。

              from django.db.models import signals
              from factory.django import mute_signals
              
              from app.models import Model
              
              @mute_signals(signals.pre_save, signals.post_save)
              def function():
                  instance = Model.objects.get(id=1)
                  instance.attribute = "modify"
                  instance.save()
              

              【讨论】:

                猜你喜欢
                • 2016-12-10
                • 2012-07-14
                • 2011-09-10
                • 2015-05-29
                • 2016-01-13
                • 2017-12-02
                • 2011-04-18
                • 1970-01-01
                • 2015-02-18
                相关资源
                最近更新 更多