Khẳng định các cuộc gọi liên tiếp đến một phương thức giả


175

Mock có một phương pháp hữu íchassert_called_with() . Tuy nhiên, theo tôi hiểu điều này chỉ kiểm tra cuộc gọi cuối cùng đến một phương thức.
Nếu tôi có mã gọi phương thức giả định 3 lần liên tiếp, mỗi lần với các tham số khác nhau, làm thế nào tôi có thể xác nhận 3 cuộc gọi này bằng các tham số cụ thể của chúng?

Câu trả lời:


179

assert_has_calls là một cách tiếp cận khác cho vấn đề này.

Từ các tài liệu:

assert_has_calls (cuộc gọi, any_order = Sai)

khẳng định giả đã được gọi với các cuộc gọi được chỉ định. Danh sách mock_calls được kiểm tra cho các cuộc gọi.

Nếu any_order là Sai (mặc định) thì các cuộc gọi phải tuần tự. Có thể có các cuộc gọi thêm trước hoặc sau các cuộc gọi được chỉ định.

Nếu any_order là True thì các cuộc gọi có thể theo bất kỳ thứ tự nào, nhưng tất cả chúng phải xuất hiện trong mock_calls.

Thí dụ:

>>> from unittest.mock import call, Mock
>>> mock = Mock(return_value=None)
>>> mock(1)
>>> mock(2)
>>> mock(3)
>>> mock(4)
>>> calls = [call(2), call(3)]
>>> mock.assert_has_calls(calls)
>>> calls = [call(4), call(2), call(3)]
>>> mock.assert_has_calls(calls, any_order=True)

Nguồn: https://docs.python.org/3/l Library / unittest.mock.html # unittest.mock.Mock.assert_has_calls


9
Một điều kỳ lạ là họ đã chọn thêm một loại "cuộc gọi" mới mà họ cũng có thể vừa sử dụng một danh sách hoặc một tuple ...
jaapz

@jaapz Nó phân lớp tuple: isinstance(mock.call(1), tuple)cho True. Họ cũng đã thêm một số phương thức và thuộc tính.
jpmc26

13
Các phiên bản ban đầu của Mock đã sử dụng một tuple đơn giản, nhưng hóa ra nó rất khó sử dụng. Mỗi lệnh gọi hàm nhận được một tuple (args, kwargs), vì vậy để kiểm tra xem "foo (123)" đã được gọi đúng chưa, bạn cần "khẳng định mock.call_args == ((123,), {})", đó là một câu nói hay so với "cuộc gọi (123)"
Jonathan Hartley

Bạn làm gì khi trên mỗi trường hợp cuộc gọi bạn mong đợi một giá trị trả về khác nhau?
CodeWithPride

2
@CodeWithPride có vẻ như là một công việc choside_effect
Pigueiras

108

Thông thường, tôi không quan tâm đến thứ tự của các cuộc gọi, chỉ có điều chúng đã xảy ra. Trong trường hợp đó, tôi kết hợp assert_any_callvới một khẳng định về call_count.

>>> import mock
>>> m = mock.Mock()
>>> m(1)
<Mock name='mock()' id='37578160'>
>>> m(2)
<Mock name='mock()' id='37578160'>
>>> m(3)
<Mock name='mock()' id='37578160'>
>>> m.assert_any_call(1)
>>> m.assert_any_call(2)
>>> m.assert_any_call(3)
>>> assert 3 == m.call_count
>>> m.assert_any_call(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "[python path]\lib\site-packages\mock.py", line 891, in assert_any_call
    '%s call not found' % expected_string
AssertionError: mock(4) call not found

Tôi thấy làm theo cách này để dễ đọc và dễ hiểu hơn một danh sách lớn các cuộc gọi được truyền vào một phương thức duy nhất.

Nếu bạn quan tâm đến đơn hàng hoặc bạn mong đợi nhiều cuộc gọi giống hệt nhau, assert_has_callscó thể phù hợp hơn.

Biên tập

Vì tôi đã đăng câu trả lời này, tôi đã suy nghĩ lại cách tiếp cận của mình để kiểm tra nói chung. Tôi nghĩ điều đáng nói là nếu bài kiểm tra của bạn trở nên phức tạp, bạn có thể đang kiểm tra không phù hợp hoặc có vấn đề về thiết kế. Mocks được thiết kế để thử nghiệm giao tiếp giữa các đối tượng trong một thiết kế hướng đối tượng. Nếu thiết kế của bạn không được phản đối theo định hướng (như trong nhiều thủ tục hoặc chức năng hơn), giả có thể hoàn toàn không phù hợp. Bạn cũng có thể có quá nhiều thứ đang diễn ra bên trong phương thức, hoặc bạn có thể đang kiểm tra các chi tiết nội bộ tốt nhất không bị khóa. Tôi đã phát triển chiến lược được đề cập trong phương pháp này khi mã của tôi không hướng đối tượng và tôi tin rằng tôi cũng đang thử nghiệm các chi tiết nội bộ có thể không bị bỏ sót.


@ jpmc26 bạn có thể giải thích thêm về chỉnh sửa của mình không? Bạn có ý nghĩa gì bởi 'tốt nhất không bị bỏ lại'? Làm thế nào khác bạn sẽ kiểm tra nếu một cuộc gọi đã được thực hiện trong một phương thức
otgw

@memo Thông thường, tốt hơn là để phương thức thực sự được gọi. Nếu phương pháp khác bị hỏng, nó có thể phá vỡ thử nghiệm, nhưng giá trị tránh đó nhỏ hơn giá trị của việc thử nghiệm đơn giản hơn, dễ bảo trì hơn. Thời điểm tốt nhất để giả là khi cuộc gọi bên ngoài đến phương thức khác là điều bạn muốn kiểm tra (Thông thường, điều này có nghĩa là một loại kết quả nào đó được truyền vào nó và mã được kiểm tra không trả về kết quả.) Hoặc phương thức khác có phụ thuộc bên ngoài (cơ sở dữ liệu, trang web) mà bạn muốn loại bỏ. (Về mặt kỹ thuật, trường hợp cuối cùng còn nhiều sơ khai và tôi sẽ ngần ngại khẳng định về nó.)
jpmc26

@ jpmc26 mocking rất hữu ích khi bạn muốn tránh tiêm phụ thuộc hoặc chọn một số phương pháp chiến lược thời gian chạy khác. như bạn đã đề cập, kiểm tra logic bên trong của các phương thức, mà không gọi các dịch vụ bên ngoài, và quan trọng hơn là không nhận biết môi trường (không có mã tốt để có do() if TEST_ENV=='prod' else dont()), có thể dễ dàng đạt được bằng cách chế giễu cách bạn đề xuất. Một tác dụng phụ của việc này là duy trì các thử nghiệm cho mỗi phiên bản (giả sử thay đổi mã giữa tìm kiếm google api v1 và v2, mã của bạn sẽ kiểm tra phiên bản 1 bất kể là gì)
Daniel Dubovski

@DanielDubovski Hầu hết các thử nghiệm của bạn nên dựa trên đầu vào / đầu ra. Điều đó không phải lúc nào cũng có thể, nhưng nếu không phải lúc nào cũng có thể, bạn có thể gặp vấn đề về thiết kế. Khi bạn cần một số giá trị được trả về mà thường xuất phát từ một đoạn mã khác và bạn muốn cắt giảm một phụ thuộc, một sơ khai thường sẽ làm. Giả chỉ cần thiết khi bạn cần xác minh rằng một số chức năng sửa đổi trạng thái (có thể không có giá trị trả về) được gọi. (Sự khác biệt giữa giả và sơ khai là bạn không xác nhận cuộc gọi bằng cuống.) Sử dụng giả trong đó sơ khai sẽ làm cho các bài kiểm tra của bạn ít được bảo trì hơn.
jpmc26

@ jpmc26 không gọi dịch vụ bên ngoài là một loại đầu ra? tất nhiên bạn có thể cấu trúc lại mã xây dựng thông điệp sẽ được gửi và kiểm tra nó thay vì xác nhận các thông số cuộc gọi, nhưng IMHO, nó khá giống nhau. Bạn đồng ý rằng bạn nên cắt giảm mức tối thiểu, tôi nói rằng bạn không nên đi kiểm tra dữ liệu bạn gửi đến các dịch vụ bên ngoài để đảm bảo logic hoạt động như mong đợi.
Daniel Dubovski

46

Bạn có thể sử dụng Mock.call_args_listthuộc tính để so sánh các tham số với các cuộc gọi phương thức trước đó. Điều đó kết hợp với Mock.call_countthuộc tính sẽ cung cấp cho bạn toàn quyền kiểm soát.


9
assert_has_calls ()?
bavaza

5
assert_has_callschỉ kiểm tra nếu các cuộc gọi dự kiến ​​đã được thực hiện, nhưng không kiểm tra nếu đó là những cuộc gọi duy nhất.
xanh

17

Tôi luôn phải tìm cái này hết lần này đến lần khác, vì vậy đây là câu trả lời của tôi.


Khẳng định nhiều cuộc gọi phương thức trên các đối tượng khác nhau của cùng một lớp

Giả sử chúng ta có một lớp nhiệm vụ nặng nề (mà chúng ta muốn chế giễu):

In [1]: class HeavyDuty(object):
   ...:     def __init__(self):
   ...:         import time
   ...:         time.sleep(2)  # <- Spends a lot of time here
   ...:     
   ...:     def do_work(self, arg1, arg2):
   ...:         print("Called with %r and %r" % (arg1, arg2))
   ...:  

đây là một số mã sử dụng hai thể hiện của HeavyDutylớp:

In [2]: def heavy_work():
   ...:     hd1 = HeavyDuty()
   ...:     hd1.do_work(13, 17)
   ...:     hd2 = HeavyDuty()
   ...:     hd2.do_work(23, 29)
   ...:    


Bây giờ, đây là một trường hợp thử nghiệm cho heavy_workchức năng:

In [3]: from unittest.mock import patch, call
   ...: def test_heavy_work():
   ...:     expected_calls = [call.do_work(13, 17),call.do_work(23, 29)]
   ...:     
   ...:     with patch('__main__.HeavyDuty') as MockHeavyDuty:
   ...:         heavy_work()
   ...:         MockHeavyDuty.return_value.assert_has_calls(expected_calls)
   ...:  

Chúng tôi đang chế giễu HeavyDutylớp học với MockHeavyDuty. Để khẳng định các cuộc gọi phương thức đến từ mọi HeavyDutytrường hợp chúng ta phải tham khảo MockHeavyDuty.return_value.assert_has_calls, thay vì MockHeavyDuty.assert_has_calls. Ngoài ra, trong danh sách expected_callschúng tôi phải chỉ định tên phương thức mà chúng tôi quan tâm để xác nhận các cuộc gọi. Vì vậy, danh sách của chúng tôi được thực hiện các cuộc gọi đếncall.do_work , trái ngược với đơn giản call.

Thực hiện trường hợp thử nghiệm cho chúng ta thấy nó thành công:

In [4]: print(test_heavy_work())
None


Nếu chúng tôi sửa đổi heavy_workchức năng, kiểm tra thất bại và tạo ra một thông báo lỗi hữu ích:

In [5]: def heavy_work():
   ...:     hd1 = HeavyDuty()
   ...:     hd1.do_work(113, 117)  # <- call args are different
   ...:     hd2 = HeavyDuty()
   ...:     hd2.do_work(123, 129)  # <- call args are different
   ...:     

In [6]: print(test_heavy_work())
---------------------------------------------------------------------------
(traceback omitted for clarity)

AssertionError: Calls not found.
Expected: [call.do_work(13, 17), call.do_work(23, 29)]
Actual: [call.do_work(113, 117), call.do_work(123, 129)]


Khẳng định nhiều cuộc gọi đến một chức năng

Để tương phản với điều trên, đây là một ví dụ cho thấy cách giả định nhiều cuộc gọi đến một chức năng:

In [7]: def work_function(arg1, arg2):
   ...:     print("Called with args %r and %r" % (arg1, arg2))

In [8]: from unittest.mock import patch, call
   ...: def test_work_function():
   ...:     expected_calls = [call(13, 17), call(23, 29)]    
   ...:     with patch('__main__.work_function') as mock_work_function:
   ...:         work_function(13, 17)
   ...:         work_function(23, 29)
   ...:         mock_work_function.assert_has_calls(expected_calls)
   ...:    

In [9]: print(test_work_function())
None


Có hai sự khác biệt chính. Đầu tiên là khi chế nhạo một chức năng, chúng tôi sẽ thiết lập các cuộc gọi dự kiến ​​bằng cách sử dụng call, thay vì sử dụng call.some_method. Điều thứ hai là chúng ta gọi assert_has_callsvề mock_work_function, thay vì trên mock_work_function.return_value.

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.