【问题标题】:Proper way to test Django signals测试 Django 信号的正确方法
【发布时间】:2011-04-18 13:09:54
【问题描述】:

我正在尝试测试发送的信号,它正在提供_args。表单提交后在contact_question_create 视图内触发信号。

我的 TestCase 类似于:

    def test_form_should_post_proper_data_via_signal(self):
        form_data = {'name': 'Jan Nowak'}
        signals.question_posted.send(sender='test', form_data=form_data)
        @receiver(signals.question_posted, sender='test')
        def question_posted_listener(sender, form_data):
            self.name = form_data['name']
        eq_(self.name, 'Jan Nowak')

这是测试此信号的正确方法吗?有更好的想法吗?

【问题讨论】:

标签: python django unit-testing tdd


【解决方案1】:

我有一个使用 mock 库的替代建议,它现在是 Python 3 中 unittest.mock 标准库的一部分(如果您使用的是 Python 2,则必须使用 pip install mock)。

try:
    from unittest.mock import MagicMock
except ImportError:
    from mock import MagicMock

def test_form_should_post_proper_data_via_signal(self):
    """
    Assert signal is sent with proper arguments
    """ 

    # Create handler
    handler = MagicMock()
    signals.question_posted.connect(handler, sender='test')

    # Post the form or do what it takes to send the signal
    signals.question_posted.send(sender='test', form_data=form_data)

    # Assert the signal was called only once with the args
    handler.assert_called_once_with(signal=signals.question_posted, form_data=form_data, sender="test")

该建议的基本部分是模拟一个接收器,然后测试您的信号是否正在发送到该接收器,并且只调用一次。这很棒,特别是如果您有自定义信号,或者您已经编写了发送信号的方法,并且您希望在单元测试中确保它们被发送。

【讨论】:

  • AttributeError: 'function' 对象没有属性 'connect'
【解决方案2】:

完成您在 2015 年提出的要求的最简单方法:

from unittest.mock import patch

@patch('full.path.to.signals.question_posted.send')
def test_question_posted_signal_triggered(self, mock):
    form = YourForm()
    form.cleaned_data = {'name': 'Jan Nowak'}
    form.save()

    # Check that your signal was called.
    self.assertTrue(mock.called)

    # Check that your signal was called only once.
    self.assertEqual(mock.call_count, 1)

    # Do whatever else, like actually checking if your signal logic did well.

然后,您刚刚测试了您的信号是否被正确触发。

【讨论】:

  • 嗨 José,我无法让它工作 - 我认为这可能与 dispatch_uid 有关。我最终使用了 'with patch('xyz') as mocked_handler' 方法。
  • 我认为更准确的说法是断言不仅调用事实,而且调用参数,使用 mock.assert_call_once_with
【解决方案3】:

你需要:

  • 断言使用适当的参数发出信号
  • 特定次数
  • 按适当的顺序。

您可以使用提供mock for signalsmock_django 应用程序。

例子:

from mock import call


def test_install_dependency(self):
    with mock_signal_receiver(post_app_install) as install_receiver:
        self.env.install(self.music_app)
        self.assertEqual(install_receiver.call_args_list, [
            call(signal=post_app_install, sender=self.env,
                app=self.ukulele_app),
            call(signal=post_app_install, sender=self.env,
                app=self.music_app),
        ])

【讨论】:

  • 请注意,mock_django 1.6.5 版不适用于 Django 1.5 或更高版本。 (在撰写本文时,1.6.5 是最新的标记版本,也是 PyPI 中的版本。)
  • @jpic 您能否添加行“来自模拟导入调用”,因为它不明显?
  • 我使用 mock 库而不是 mock_django 发布了更新。见下文。
【解决方案4】:

这样做的目的不是测试底层的信号机制,而是一个重要的单元测试,以确保您的方法应该发出的任何信号实际上都是使用正确的参数发出的。在这种情况下,由于它是一个内部 django 信号,因此它似乎有点微不足道,但想象一下,如果您编写了发出自定义信号的方法。

【讨论】:

    【解决方案5】:

    你为什么要测试你的框架? Django 已经对信号调度器进行了单元测试。如果您不相信您的框架很好,只需将其单元测试附加到您的测试运行器。

    【讨论】:

    • 请注意,他不是在测试 Django 框架信号逻辑的行为方式,而是实际测试他的代码何时以及如何触发 Django 信号。
    【解决方案6】:

    我自己解决了这个问题。我认为最好的解决方案如下:

        def test_form_should_post_proper_data_via_signal(self):
            # define the local listener
            def question_posted_listener(sender, form_data, **kwargs):
                self.name = form_data['name']
    
            # prepare fake data
            form_data = {'name': 'Jan Nowak'}
    
            # connect & send the signal
            signals.question_posted.connect(question_posted_listener, sender='test')
            signals.question_posted.send(sender='test', form_data=form_data)
    
            # check results
            eq_(self.name, 'Jan Nowak')
    

    【讨论】:

    • 如果一个信号被发射两次或更多,这不会失败。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-10
    • 2015-01-28
    • 2017-06-06
    • 1970-01-01
    • 1970-01-01
    • 2020-01-10
    相关资源
    最近更新 更多