Khẳng định một hàm / phương thức không được gọi bằng Mock


131

Tôi đang sử dụng thư viện Mock để kiểm tra ứng dụng của mình, nhưng tôi muốn khẳng định rằng một số chức năng không được gọi. Tài liệu giả nói về các phương thức như mock.assert_called_withmock.assert_called_once_with, nhưng tôi không tìm thấy bất cứ điều gì giống mock.assert_not_calledhoặc điều gì đó liên quan đến xác minh giả không được gọi .

Tôi có thể đi với một cái gì đó như sau, mặc dù nó không có vẻ mát mẻ hay pythonic:

def test_something:
    # some actions
    with patch('something') as my_var:
        try:
            # args are not important. func should never be called in this test
            my_var.assert_called_with(some, args)
        except AssertionError:
            pass  # this error being raised means it's ok
    # other stuff

Bất kỳ ý tưởng làm thế nào để thực hiện điều này?


Như @Ahmet chỉ ra trong câu trả lời của mình, assert_not_called hiện đang được hỗ trợ, cũng trong backport ( docs.python.org/3/library/... ).
Martin

Câu trả lời:


144

Điều này sẽ làm việc cho trường hợp của bạn;

assert not my_var.called, 'method should not have been called'

Mẫu vật;

>>> mock=Mock()
>>> mock.a()
<Mock name='mock.a()' id='4349129872'>
>>> assert not mock.b.called, 'b was called and should not have been'
>>> assert not mock.a.called, 'a was called and should not have been'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: a was called and should not have been

Câu trả lời này có cần Django không? Tôi đang gặp lỗi:AttributeError: MockCallable instance has no attribute 'called'
Nathan Arthur

@NathanArthur Hừm, tôi không nghĩ vậy, sau sudo easy_install -U mockfrom mock import Mocktrên MacOS, những thứ trên chạy mà không gặp trở ngại nào. Chưa bao giờ cài đặt Django :)
Joachim Isaksson

Hừm. Thật ki quặc. Tôi đang chạy Python 2.7.1 và đang sử dụng unittest và from mock import Mockvới Python Mock 0.1.0 cho các thử nghiệm của tôi. Có ai trong số đó có vấn đề?
Nathan Arthur

Tôi đang chế nhạo một lớp có thể gọi được từ một mô-đun khác, vì vậy có vẻ như module_to_test.another_module.class = mock.Mock(), bạn có thể xác nhận điều này không nhớ các cuộc gọi qua các trường hợp thử nghiệm khác nhau (trường hợp unittest.TestCase) không? Tôi nghĩ rằng số lượng cuộc gọi không được đặt lại trong trường hợp này
0xc0de

66

Mặc dù là một câu hỏi cũ, tôi muốn thêm phương thức mockhỗ trợ thư viện hiện tại (backport of unittest.mock) assert_not_called.

Chỉ cần nâng cấp của bạn;

pip install mock --upgrade


29

Bạn có thể kiểm tra calledthuộc tính, nhưng nếu xác nhận của bạn thất bại, điều tiếp theo bạn muốn biết là điều gì đó về cuộc gọi bất ngờ, vì vậy bạn cũng có thể sắp xếp để thông tin đó được hiển thị từ đầu. Sử dụng unittest, bạn có thể kiểm tra nội dung call_args_listthay thế:

self.assertItemsEqual(my_var.call_args_list, [])

Khi thất bại, nó đưa ra một thông báo như thế này:

AssertsError: Số phần tử không bằng nhau:
Đầu tiên có 0, Thứ hai có 1: cuộc gọi ('đối số đầu tiên', 4)

14

Khi bạn kiểm tra bằng cách sử dụng lớp kế thừa unittest.TestCase, bạn có thể chỉ cần sử dụng các phương thức như:

  • khẳng định
  • khẳng định
  • khẳng định

và tương tự (trong tài liệu python bạn tìm thấy phần còn lại).

Trong ví dụ của bạn, chúng tôi chỉ có thể xác nhận nếu thuộc tính mock_method.calledSai , có nghĩa là phương thức đó không được gọi.

import unittest
from unittest import mock

import my_module

class A(unittest.TestCase):
    def setUp(self):
        self.message = "Method should not be called. Called {times} times!"

    @mock.patch("my_module.method_to_mock")
    def test(self, mock_method):
        my_module.method_to_mock()

        self.assertFalse(mock_method.called,
                         self.message.format(times=mock_method.call_count))

10

Với python >= 3.5bạn có thể sử dụng mock_object.assert_not_called().


1

Đánh giá từ các câu trả lời khác, không ai ngoại trừ @ rob-kennedy nói về call_args_list.

Đây là một công cụ mạnh mẽ để bạn có thể thực hiện ngược lại chính xác với MagicMock.assert_called_with()

call_args_listlà một danh sách các callđối tượng. Mỗi callđối tượng đại diện cho một cuộc gọi được thực hiện trên một cuộc gọi giả.

>>> from unittest.mock import MagicMock
>>> m = MagicMock()
>>> m.call_args_list
[]
>>> m(42)
<MagicMock name='mock()' id='139675158423872'>
>>> m.call_args_list
[call(42)]
>>> m(42, 30)
<MagicMock name='mock()' id='139675158423872'>
>>> m.call_args_list
[call(42), call(42, 30)]

Việc sử dụng một callđối tượng là dễ dàng, vì bạn có thể so sánh nó với một tuple có độ dài 2 trong đó thành phần đầu tiên là một tuple chứa tất cả các đối số vị trí của cuộc gọi liên quan, trong khi thành phần thứ hai là một từ điển của các đối số từ khóa.

>>> ((42,),) in m.call_args_list
True
>>> m(42, foo='bar')
<MagicMock name='mock()' id='139675158423872'>
>>> ((42,), {'foo': 'bar'}) in m.call_args_list
True
>>> m(foo='bar')
<MagicMock name='mock()' id='139675158423872'>
>>> ((), {'foo': 'bar'}) in m.call_args_list
True

Vì vậy, một cách để giải quyết vấn đề cụ thể của OP là

def test_something():
    with patch('something') as my_var:
        assert ((some, args),) not in my_var.call_args_list

Lưu ý rằng theo cách này, thay vì chỉ kiểm tra xem có thể gọi được một cuộc gọi giả, thông qua MagicMock.called, bây giờ bạn có thể kiểm tra xem nó có được gọi với một bộ đối số cụ thể không.

Điều đó hữu ích. Giả sử bạn muốn kiểm tra một chức năng lấy danh sách và gọi một chức năng khác,compute() , đối với mỗi giá trị của danh sách chỉ khi chúng thỏa mãn một điều kiện cụ thể.

Bây giờ bạn có thể giả compute, và kiểm tra xem nó đã được gọi trên một số giá trị nhưng không phải trên các giá trị khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.