모의에게 유용한 assert_called_with()
방법이 있습니다. 그러나 내가 이해 하는 한 메소드에 대한 마지막 호출 만 확인합니다 .
매번 다른 매개 변수를 사용하여 mocked 메서드를 3 번 연속적으로 호출하는 코드가있는 경우 특정 매개 변수를 사용 하여이 3 호출을 어설 션하려면 어떻게해야합니까?
모의에게 유용한 assert_called_with()
방법이 있습니다. 그러나 내가 이해 하는 한 메소드에 대한 마지막 호출 만 확인합니다 .
매번 다른 매개 변수를 사용하여 mocked 메서드를 3 번 연속적으로 호출하는 코드가있는 경우 특정 매개 변수를 사용 하여이 3 호출을 어설 션하려면 어떻게해야합니까?
답변:
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
tuple
: isinstance(mock.call(1), tuple)
gives True
. 또한 몇 가지 방법과 속성을 추가했습니다.
side_effect
보통, 나는 통화 순서에 신경 쓰지 않고 단지 그들이 일어난 일에만 신경 쓰지 않습니다. 이 경우에 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는 객체 지향 디자인에서 객체 간 통신을 테스트하도록 설계되었습니다. 디자인이 객체 지향적이지 않은 경우 (더 절차 적 또는 기능적) 모의는 완전히 부적절 할 수 있습니다. 메소드 내부에서 너무 많은 일이 발생했거나, 가장 잘 드러나지 않은 내부 세부 사항을 테스트하는 중일 수 있습니다. 내 코드가 객체 지향적이지 않을 때이 방법에서 언급 된 전략을 개발했으며, 가장 잘 풀리지 않은 내부 세부 정보도 테스트하고 있다고 생각합니다.
do() if TEST_ENV=='prod' else dont()
수있는 사람이 아닙니다) 제안한 방식을 조롱하여 쉽게 달성 할 수 있습니다. 이것의 부작용은 버전마다 테스트를 유지하는 것입니다 (Google 검색 API v1과 v2 사이의 코드 변경, 코드는 버전 1을 테스트합니다)
Mock.call_args_list
속성 을 사용하여 매개 변수를 이전 메소드 호출과 비교할 수 있습니다 . Mock.call_count
속성 과 관련하여 모든 권한을 부여해야합니다.
assert_has_calls
예상 통화가 완료되었는지 확인하지만 이것이 유일한 통화인지는 확인하지 않습니다.
나는 항상 이것을 한 번에 다시 찾아야하므로 여기에 내 대답이 있습니다.
우리가 무거운 클래스를 가지고 있다고 가정 해보십시오 (우리가 조롱하고 싶어).
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_calls
on mock_work_function
대신에 호출 하는 것입니다 mock_work_function.return_value
.