모의 메소드에 대한 연속 호출 주장


175

모의에게 유용한 assert_called_with()방법이 있습니다. 그러나 내가 이해 하는 한 메소드에 대한 마지막 호출 만 확인합니다 .
매번 다른 매개 변수를 사용하여 mocked 메서드를 3 번 ​​연속적으로 호출하는 코드가있는 경우 특정 매개 변수를 사용 하여이 3 호출을 어설 션하려면 어떻게해야합니까?

답변:


179

assert_has_calls 이 문제에 대한 또 다른 접근법입니다.

문서에서 :

assert_has_calls (호출, any_order = False)

지정된 호출로 모의가 호출되었다고 주장하십시오. mock_calls 목록에서 호출을 확인합니다.

any_order가 False (기본값)이면 호출은 순차적이어야합니다. 지정된 통화 전후에 추가 통화가있을 수 있습니다.

any_order가 True이면 호출 순서는 상관 없지만 모두 mock_calls에 나타나야합니다.

예:

>>> 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)

출처 : https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_has_calls


9
조금 이상해 그들은 방금 목록이나 튜플을 사용할 수도있는 새로운 "호출"유형을 추가하기로 결정했습니다.
jaapz

@jaapz 서브 클래스 tuple: isinstance(mock.call(1), tuple)gives True. 또한 몇 가지 방법과 속성을 추가했습니다.
jpmc26

13
초기 버전의 Mock은 일반 튜플을 사용했지만 사용하기 어색한 것으로 나타났습니다. 각 함수 호출은 (args, kwargs)의 튜플을 수신하므로 "foo (123)"가 올바르게 호출되었는지 확인하려면 "mock.call_args == ((123,), {})"를 확인해야합니다. "call (123)"에 비해 한 입
Jonathan Hartley

각 호출 인스턴스에서 다른 반환 값을 기대할 때 어떻게해야합니까?
CodeWithPride

2
@CodeWithPride 그것은 더 많은 직업을 찾습니다side_effect
Pigueiras

108

보통, 나는 통화 순서에 신경 쓰지 않고 단지 그들이 일어난 일에만 신경 쓰지 않습니다. 이 경우에 assert_any_call대한 어설 션과 결합 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

단일 메소드에 전달 된 많은 호출 목록보다 읽기 쉽고 이해하기 쉽도록이 방법을 사용합니다.

주문에 관심이 있거나 동일한 전화가 여러 번 예상되는 assert_has_calls것이 더 적절할 수 있습니다.

편집하다

이 답변을 게시 한 이후 일반적인 테스트 방법을 다시 생각했습니다. 테스트가 복잡해지면 부적절하게 테스트 중이거나 디자인 문제가있을 수 있습니다. Mocks는 객체 지향 디자인에서 객체 간 통신을 테스트하도록 설계되었습니다. 디자인이 객체 지향적이지 않은 경우 (더 절차 적 또는 기능적) 모의는 완전히 부적절 할 수 있습니다. 메소드 내부에서 너무 많은 일이 발생했거나, 가장 잘 드러나지 않은 내부 세부 사항을 테스트하는 중일 수 있습니다. 내 코드가 객체 지향적이지 않을 때이 방법에서 언급 된 전략을 개발했으며, 가장 잘 풀리지 않은 내부 세부 정보도 테스트하고 있다고 생각합니다.


@ jpmc26 편집에 대해 더 자세히 설명해 주시겠습니까? '최고의 비 조롱'이란 무엇입니까? 메소드 내에서 전화가 걸
렸는지

@memo 종종 실제 메소드를 호출하는 것이 좋습니다. 다른 방법이 중단되면 테스트가 중단 될 수 있지만이를 피하는 값은 더 단순하고 유지 보수가 쉬운 테스트를 수행하는 값보다 작습니다. 가장 좋은 방법은 다른 메소드에 대한 외부 호출이 테스트하려는 것입니다 (보통, 이는 일종의 결과가 전달되고 테스트중인 코드가 결과를 반환하지 않음을 의미합니다) 또는 다른 메소드 제거하려는 외부 종속성 (데이터베이스, 웹 사이트)이 있습니다. (기술적으로, 마지막 경우는 스텁에
가깝

@ jpmc26 조롱은 의존성 주입이나 다른 런타임 전략 선택 방법을 피하고 싶을 때 유용합니다. 앞에서 언급했듯이 외부 서비스를 호출하지 않고 메소드의 내부 로직을 테스트하는 것이 중요합니다. 더 중요한 것은 환경을 인식하지 않고 (좋은 코드를 가질 do() if TEST_ENV=='prod' else dont()수있는 사람이 아닙니다) 제안한 방식을 조롱하여 쉽게 달성 할 수 있습니다. 이것의 부작용은 버전마다 테스트를 유지하는 것입니다 (Google 검색 API v1과 v2 사이의 코드 변경, 코드는 버전 1을 테스트합니다)
Daniel Dubovski

@DanielDubovski 대부분의 테스트는 입력 / 출력 기반이어야합니다. 항상 가능하지는 않지만 대부분 가능하지 않은 경우 디자인 문제가있을 수 있습니다. 일반적으로 다른 코드 조각에서 나온 값이 반환되고 종속성을 잘라내려는 경우 스텁이 일반적으로 수행됩니다. Mocks는 일부 상태 수정 함수 (아마도 반환 값이 없음)가 호출되었는지 확인해야 할 때만 필요합니다. (모의와 스텁의 차이점은 스텁이있는 호출을 주장하지 않는다는 것입니다.) 스텁이있는 모의를 사용하면 테스트를 덜 유지하기가 쉽습니다.
jpmc26

@ jpmc26은 외부 서비스를 일종의 출력으로 부르지 않습니까? 물론 호출 매개 변수를 주장하는 대신 보낼 메시지를 작성하고 테스트 할 코드를 리팩터링 할 수 있지만 IMHO는 거의 동일합니다. 외부 API 호출을 다시 디자인하는 방법은 무엇입니까? 모의를 최소화해야한다는 데 동의합니다. 임시 말에 따르면 외부 서비스로 보내는 데이터를 테스트하여 논리가 예상대로 작동하는지 확인할 수는 없습니다.
Daniel Dubovski

46

Mock.call_args_list속성 을 사용하여 매개 변수를 이전 메소드 호출과 비교할 수 있습니다 . Mock.call_count속성 과 관련하여 모든 권한을 부여해야합니다.


9
assert_has_calls ()?
bavaza

5
assert_has_calls예상 통화가 완료되었는지 확인하지만 이것이 유일한 통화인지는 확인하지 않습니다.
blueyed

17

나는 항상 이것을 한 번에 다시 찾아야하므로 여기에 내 대답이 있습니다.


같은 클래스의 다른 객체에 대해 여러 개의 메소드 호출

우리가 무거운 클래스를 가지고 있다고 가정 해보십시오 (우리가 조롱하고 싶어).

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))
   ...:  

다음은 HeavyDuty클래스의 두 인스턴스를 사용하는 코드입니다 .

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


이제 heavy_work함수에 대한 테스트 사례가 있습니다.

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)
   ...:  

우리는로 HeavyDuty수업을 조롱하고 있습니다 MockHeavyDuty. 모든 HeavyDuty인스턴스 에서 오는 메소드 호출을 주장하기 위해 MockHeavyDuty.return_value.assert_has_calls대신에 참조해야한다 MockHeavyDuty.assert_has_calls. 또한 목록 expected_calls에서 호출 주장에 관심있는 메소드 이름을 지정해야합니다. 따라서 우리의 목록은 call.do_work단순히와는 반대로에 대한 호출로 구성 됩니다 call.

테스트 사례를 실행하면 성공한 것으로 나타납니다.

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


heavy_work함수 를 수정하면 테스트가 실패하고 유용한 오류 메시지가 생성됩니다.

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)]


함수에 대한 여러 호출 주장

위와 달리 함수에 대한 여러 호출을 조롱하는 방법을 보여주는 예는 다음과 같습니다.

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


두 가지 주요 차이점이 있습니다. 첫 번째는 함수를 조롱 할 때을 사용하는 call대신을 사용 하여 예상 호출을 설정 한다는 것입니다 call.some_method. 두 번째는 assert_has_callson mock_work_function대신에 호출 하는 것입니다 mock_work_function.return_value.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.