"unittest.mock" --- 시작하기
****************************

버전 3.3에 추가.


모의 객체 사용하기
==================


메서드를 패치하는 모의 객체
---------------------------

"Mock" 객체의 일반적인 용도는 다음과 같습니다:

* 메서드 패치하기

* 객체에 대한 메서드 호출 기록하기

객체의 메서드를 대체하여 시스템의 다른 부분에서 올바른 인자로 호출되었
는지 확인할 수 있습니다:

>>> real = SomeClass()
>>> real.method = MagicMock(name='method')
>>> real.method(3, 4, 5, key='value')
<MagicMock name='method()' id='...'>

일단 모의 객체가 사용되면 (이 예제에서는 "real.method") 사용 방법에 대
한 어서션을 만들 수 있도록 하는 메서드와 어트리뷰트를 제공합니다.

참고:

  이 예의 대부분에서 "Mock"과 "MagicMock" 클래스는 교환할 수 있습니다.
  "MagicMock"이 더 유능한 클래스이기 때문에 기본 사용하기에 적합합니다
  .

일단 모의 객체가 호출되면 그것의 "called" 어트리뷰트가 "True"로 설정됩
니다. 더 중요하게 "assert_called_with()"나 "assert_called_once_with()"
메서드를 사용하여 올바른 인자로 호출되었는지 확인할 수 있습니다.

이 예제는 "ProductionClass().method"를 호출하면 "something" 메서드가
호출되는지 테스트합니다:

>>> class ProductionClass:
...     def method(self):
...         self.something(1, 2, 3)
...     def something(self, a, b, c):
...         pass
...
>>> real = ProductionClass()
>>> real.something = MagicMock()
>>> real.method()
>>> real.something.assert_called_once_with(1, 2, 3)


객체의 메서드 호출을 위한 모의 객체
-----------------------------------

마지막 예제에서 우리는 객체에 메서드를 직접 패치하여 올바르게 호출되었
는지 확인했습니다. 또 다른 일반적인 사용 사례는 객체를 메서드(또는 테
스트 중인 시스템의 일부)에 전달한 다음 올바른 방식으로 사용되는지 확인
하는 것입니다.

아래의 간단한 "ProductionClass"에는 "closer" 메서드가 있습니다. 객체로
호출되면 그것의 "close"를 호출합니다.

>>> class ProductionClass:
...     def closer(self, something):
...         something.close()
...

따라서 테스트하려면 "close" 메서드를 가진 객체를 전달하고 올바르게 호
출되었는지 확인해야 합니다.

>>> real = ProductionClass()
>>> mock = Mock()
>>> real.closer(mock)
>>> mock.close.assert_called_with()

모의 객체에 'close' 메서드를 제공하기 위해 어떤 작업도 수행할 필요가
없습니다. close에 액세스하면 만들어집니다. 따라서, 'close'가 아직 호출
되지 않았다면 테스트에서 액세스할 때 만들어지지만,
"assert_called_with()"는 실패 예외를 발생시킵니다.


클래스 모킹하기
---------------

일반적인 사용 사례는 테스트 중인 코드가 인스턴스 화하는 클래스를 모킹
하는 것입니다. 클래스를 패치하면, 해당 클래스가 모의 객체로 바뀝니다.
인스턴스는 *클래스를 호출해서* 만들어집니다. 이는 모킹된 클래스의 반환
값을 확인하여 "모의 인스턴스"에 액세스한다는 것을 뜻합니다.

아래 예제에는 "Foo"를 인스턴스 화하고 그것의 메서드를 호출하는
"some_function" 함수가 있습니다. "patch()"에 대한 호출은 클래스 "Foo"
를 모의 객체로 대체합니다. "Foo" 인스턴스는 모의 객체를 호출한 결과라
서, 모의 객체 "return_value"를 수정하여 구성됩니다.

   >>> def some_function():
   ...     instance = module.Foo()
   ...     return instance.method()
   ...
   >>> with patch('module.Foo') as mock:
   ...     instance = mock.return_value
   ...     instance.method.return_value = 'the result'
   ...     result = some_function()
   ...     assert result == 'the result'


모의 객체 이름 붙이기
---------------------

모의 객체에 이름을 지정하는 것이 유용할 수 있습니다. 이름은 모의 객체
의 repr에 표시되며 모의 객체가 테스트 실패 메시지에 나타날 때 유용할
수 있습니다. 이 이름은 모의 객체의 어트리뷰트나 메서드에도 전파됩니다:

>>> mock = MagicMock(name='foo')
>>> mock
<MagicMock name='foo' id='...'>
>>> mock.method
<MagicMock name='foo.method' id='...'>


모든 호출 추적하기
------------------

메서드에 대한 단일 호출 이상을 추적하려는 경우가 종종 있습니다.
"mock_calls" 어트리뷰트는 모의 객체의 자식 어트리뷰트에 대한 모든 호출
을 기록합니다 - 그리고 그들의 자식에 대해서도 마찬가지입니다.

>>> mock = MagicMock()
>>> mock.method()
<MagicMock name='mock.method()' id='...'>
>>> mock.attribute.method(10, x=53)
<MagicMock name='mock.attribute.method()' id='...'>
>>> mock.mock_calls
[call.method(), call.attribute.method(10, x=53)]

"mock_calls"에 대한 어서션을 만들고 예기치 않은 메서드가 호출되면, 어
서션이 실패합니다. 이 기능은 예상한 호출이 이루어졌음을 어서트 할 뿐만
아니라, 추가 호출 없이 올바른 순서로 호출되었는지 확인하기 때문에 유용
합니다:

"call" 객체를 사용하여 "mock_calls"와 비교할 리스트를 구성합니다:

>>> expected = [call.method(), call.attribute.method(10, x=53)]
>>> mock.mock_calls == expected
True

그러나, 모의 객체를 반환하는 호출에 대한 매개 변수는 기록되지 않아서,
조상을 만드는 데 사용되는 매개 변수가 중요한 중첩된 호출을 추적할 수
없습니다:

>>> m = Mock()
>>> m.factory(important=True).deliver()
<Mock name='mock.factory().deliver()' id='...'>
>>> m.mock_calls[-1] == call.factory(important=False).deliver()
True


반환 값과 어트리뷰트 설정하기
-----------------------------

모의 객체에서 반환 값을 설정하는 것은 아주 간단합니다:

>>> mock = Mock()
>>> mock.return_value = 3
>>> mock()
3

물론 모의 객체의 메서드에 대해서도 마찬가지입니다:

>>> mock = Mock()
>>> mock.method.return_value = 3
>>> mock.method()
3

생성자에서 반환 값을 설정할 수도 있습니다:

>>> mock = Mock(return_value=3)
>>> mock()
3

모의 객체에 어트리뷰트 설정이 필요하면, 그냥 하면 됩니다:

>>> mock = Mock()
>>> mock.x = 3
>>> mock.x
3

때로는 예를 들어 "mock.connection.cursor().execute("SELECT 1")"와 같은
더 복잡한 상황을 모킹하고 싶을 수도 있습니다. 이 호출이 리스트를 반환
하도록 하려면, 중첩 호출의 결과를 구성해야 합니다.

"call"을 사용하여 다음과 같이 "연쇄 호출(chained call)"로 일련의 호출
집합을 구성할 수 있습니다:

>>> mock = Mock()
>>> cursor = mock.connection.cursor.return_value
>>> cursor.execute.return_value = ['foo']
>>> mock.connection.cursor().execute("SELECT 1")
['foo']
>>> expected = call.connection.cursor().execute("SELECT 1").call_list()
>>> mock.mock_calls
[call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]
>>> mock.mock_calls == expected
True

".call_list()" 호출이 호출 객체를 연쇄 호출을 나타내는 호출 리스트로
변환합니다.


모의 객체로 예외 발생시키기
---------------------------

유용한 어트리뷰트는 "side_effect"입니다. 이것을 예외 클래스나 인스턴스
로 설정하면 모의 객체가 호출될 때 예외가 발생합니다.

>>> mock = Mock(side_effect=Exception('Boom!'))
>>> mock()
Traceback (most recent call last):
  ...
Exception: Boom!


부작용 함수와 이터러블
----------------------

"side_effect"는 함수나 이터러블로 설정할 수도 있습니다. "side_effect"
의 이터러블로서의 사용 사례는 모의 객체가 여러 번 호출되고, 각 호출이
다른 값을 반환하기를 원하는 곳입니다. "side_effect"를 이터러블로 설정
하면 모의 객체에 대한 모든 호출은 이터러블에서 다음 값을 반환합니다:

>>> mock = MagicMock(side_effect=[4, 5, 6])
>>> mock()
4
>>> mock()
5
>>> mock()
6

모의 객체 호출에 전달되는 것에 따라 반환 값을 동적으로 변경하는 것과
같은 고급 사용 사례의 경우, "side_effect"는 함수가 될 수 있습니다. 함
수는 모의 객 체와 같은 인자로 호출됩니다. 함수가 반환하는 것을 호출이
반환합니다:

>>> vals = {(1, 2): 1, (2, 3): 2}
>>> def side_effect(*args):
...     return vals[args]
...
>>> mock = MagicMock(side_effect=side_effect)
>>> mock(1, 2)
1
>>> mock(2, 3)
2


비동기 이터레이터 모킹하기
--------------------------

파이썬 3.8부터, "AsyncMock"과 "MagicMock"은 "__aiter__"를 통해 비동기
이터레이터(Asynchronous Iterators)를 모킹하는 것을 지원합니다.
"__aiter__"의 "return_value" 어트리뷰트를 사용하여 이터레이션에 사용될
반환 값을 설정할 수 있습니다.

>>> mock = MagicMock()  # AsyncMock also works here
>>> mock.__aiter__.return_value = [1, 2, 3]
>>> async def main():
...     return [i async for i in mock]
...
>>> asyncio.run(main())
[1, 2, 3]


비동기 컨텍스트 관리자 모킹하기
-------------------------------

파이썬 3.8부터, "AsyncMock"과 "MagicMock"은 "__aenter__"와 "__aexit__"
를 통해 비동기 컨텍스트 관리자를 모킹하는 것을 지원합니다. 기본적으로,
"__aenter__"와 "__aexit__"는 비동기 함수를 반환하는 "AsyncMock" 인스턴
스입니다.

>>> class AsyncContextManager:
...     async def __aenter__(self):
...         return self
...     async def __aexit__(self, exc_type, exc, tb):
...         pass
...
>>> mock_instance = MagicMock(AsyncContextManager())  # AsyncMock also works here
>>> async def main():
...     async with mock_instance as result:
...         pass
...
>>> asyncio.run(main())
>>> mock_instance.__aenter__.assert_awaited_once()
>>> mock_instance.__aexit__.assert_awaited_once()


기존 객체에서 모의 객체 만들기
------------------------------

모킹을 과도하게 사용하는 한 가지 문제는 테스트를 실제 코드가 아닌 모킹
의 구현에 연결한다는 것입니다. "some_method"를 구현하는 클래스가 있다
고 가정해봅시다. 다른 클래스에 대한 테스트에서, "some_method"*도* 제공
하는 이 객체의 모의 객체를 제공합니다. 나중에 첫 번째 클래스를 리팩토
링하여, 더는 "some_method"를 갖지 않습니다 - 그러면 이제 코드가 망가졌
음에도 테스트는 계속 통과합니다!

"Mock"은 *spec* 키워드 인자를 사용하여, 모의 객체를 위한 사양으로 객체
를 제공할 수 있도록 합니다. 사양 객체에 존재하지 않는 모의 객체의 메서
드/어트리뷰트에 액세스하면 어트리뷰트 에러가 즉시 발생합니다. 사양의
구현을 변경하면, 해당 클래스를 사용하는 테스트는 해당 테스트에서 클래
스를 인스턴스 화하지 않고도 즉시 실패하기 시작합니다.

>>> mock = Mock(spec=SomeClass)
>>> mock.old_method()
Traceback (most recent call last):
   ...
AttributeError: object has no attribute 'old_method'

또한 사양을 사용하면 일부 매개 변수가 위치 인자나 이름 붙인 인자 중 어
느 것으로 전달되는지와 관계없이 모의 객체에 대한 호출을 더 스마트하게
일치시킬 수 있도록 합니다:

   >>> def f(a, b, c): pass
   ...
   >>> mock = Mock(spec=f)
   >>> mock(1, 2, 3)
   <Mock name='mock()' id='140161580456576'>
   >>> mock.assert_called_with(a=1, b=2, c=3)

이 더 스마트한 인치가 모의 객체에 대한 메서드 호출에서도 작동하게 하려
면, 자동 사양을 사용할 수 있습니다.

임의의 어트리뷰트를 읽는 것뿐만 아니라 설정하지 못하게 하는 더 강력한
사양 형식을 원하면 *spec* 대신 *spec_set*을 사용할 수 있습니다.


패치 데코레이터
===============

참고:

  "patch()"를 사용할 때는 그것이 조회되는 이름 공간에서 객체를 패치하
  는 것이 중요합니다. 이것은 일반적으로 간단하지만, 빠른 안내를 위해
  패치할 곳을 읽으십시오.

테스트에서 흔히 필요한 것은 클래스 어트리뷰트나 모듈 어트리뷰트를 패치
하는 것입니다, 예를 들어 내장(builtin)을 패치하거나 모듈에 있는 인스턴
스 화 되는 클래스를 패치하는 것. 모듈과 클래스는 사실상 전역이라서, 테
스트 후에 패치를 실행 취소해야 합니다, 그렇지 않으면 패치가 다른 테스
트로 지속하여, 진단하기 어려운 문제를 일으킵니다.

mock은 세 가지 편리한 데코레이터를 제공합니다: "patch()",
"patch.object()" 및 "patch.dict()". "patch"는 패치 할 어트리뷰트를 지
정하기 위해 "package.module.Class.attribute" 형식의 단일 문자열을 취합
니다. 또한 선택적으로 어트리뷰트(또는 클래스나 무엇이건)를 바꾸려는 값
을 취합니다. 'patch.object'는 객체와 패치하려는 어트리뷰트의 이름 및
선택적으로 패치 할 값을 취합니다.

"patch.object":

   >>> original = SomeClass.attribute
   >>> @patch.object(SomeClass, 'attribute', sentinel.attribute)
   ... def test():
   ...     assert SomeClass.attribute == sentinel.attribute
   ...
   >>> test()
   >>> assert SomeClass.attribute == original

   >>> @patch('package.module.attribute', sentinel.attribute)
   ... def test():
   ...     from package.module import attribute
   ...     assert attribute is sentinel.attribute
   ...
   >>> test()

모듈("builtins"를 포함하는)을 패치하려면 "patch.object()" 대신
"patch()"를 사용하십시오:

>>> mock = MagicMock(return_value=sentinel.file_handle)
>>> with patch('builtins.open', mock):
...     handle = open('filename', 'r')
...
>>> mock.assert_called_with('filename', 'r')
>>> assert handle == sentinel.file_handle, "incorrect file handle returned"

필요하면 "package.module" 형식으로 모듈 이름을 '점으로 표시'할 수 있습
니다:

   >>> @patch('package.module.ClassName.attribute', sentinel.attribute)
   ... def test():
   ...     from package.module import ClassName
   ...     assert ClassName.attribute == sentinel.attribute
   ...
   >>> test()

좋은 패턴은 실제로 테스트 메서드 자체를 데코레이트 하는 것입니다:

>>> class MyTest(unittest.TestCase):
...     @patch.object(SomeClass, 'attribute', sentinel.attribute)
...     def test_something(self):
...         self.assertEqual(SomeClass.attribute, sentinel.attribute)
...
>>> original = SomeClass.attribute
>>> MyTest('test_something').test_something()
>>> assert SomeClass.attribute == original

Mock으로 패치하려면, 하나의 인자만으로 "patch()"를 사용할 수 있습니다
(또는 두 개의 인자로 "patch.object()"). 모의 객체가 여러분을 위해 만들
어지고 테스트 함수 / 메서드로 전달됩니다:

>>> class MyTest(unittest.TestCase):
...     @patch.object(SomeClass, 'static_method')
...     def test_something(self, mock_method):
...         SomeClass.static_method()
...         mock_method.assert_called_with()
...
>>> MyTest('test_something').test_something()

이 패턴을 사용하여 여러 패치 데코레이터를 쌓을 수 있습니다:

   >>> class MyTest(unittest.TestCase):
   ...     @patch('package.module.ClassName1')
   ...     @patch('package.module.ClassName2')
   ...     def test_something(self, MockClass2, MockClass1):
   ...         self.assertIs(package.module.ClassName1, MockClass1)
   ...         self.assertIs(package.module.ClassName2, MockClass2)
   ...
   >>> MyTest('test_something').test_something()

패치 데코레이터를 중첩할 때 모의 객체는 적용한 순서와 같은 순서(데코레
이터가 적용되는 일반적인 *파이썬* 순서)로 데코레이트 된 함수로 전달됩
니다. 이것은 밑에서 위로 올라가는 순서를 뜻해서, 위의 예에서
"test_module.ClassName2"의 모의 객체가 먼저 전달됩니다.

스코프 도중 딕셔너리에 값을 설정하고 테스트가 끝날 때 딕셔너리를 원래
상태로 복원하기 위한 "patch.dict()"도 있습니다:

>>> foo = {'key': 'value'}
>>> original = foo.copy()
>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
...     assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == original

"patch", "patch.object" 및 "patch.dict"는 모두 컨텍스트 관리자로 사용
할 수 있습니다.

"patch()"를 사용하여 모의 객체를 만드는 곳에서, with 문의 "as" 형식을
사용하여 모의 객체에 대한 참조를 얻을 수 있습니다:

>>> class ProductionClass:
...     def method(self):
...         pass
...
>>> with patch.object(ProductionClass, 'method') as mock_method:
...     mock_method.return_value = None
...     real = ProductionClass()
...     real.method(1, 2, 3)
...
>>> mock_method.assert_called_with(1, 2, 3)

대안으로 "patch", "patch.object" 및 "patch.dict"는 클래스 데코레이터로
사용될 수 있습니다. 이런 식으로 사용될 때 이름이 "test"로 시작하는 모
든 메서드에 데코레이터를 개별적으로 적용하는 것과 같습니다.


추가 예
=======

다음은 약간 더 고급 시나리오에 대한 몇 가지 예입니다.


연쇄 호출 모킹하기
------------------

일단 "return_value" 어트리뷰트를 이해하면 연쇄 호출 모킹은 모의 객체를
사용하면 실제로 간단합니다. 모의 객체가 처음 호출되거나 호출되기 전에
"return_value"를 가져오면, 새 "Mock"이 만들어집니다.

이것은 "return_value" 모의 객체를 조사하여 모킹 된 객체에 대한 호출에
서 반환된 객체가 어떻게 사용되었는지 확인할 수 있음을 뜻합니다:

>>> mock = Mock()
>>> mock().foo(a=2, b=3)
<Mock name='mock().foo()' id='...'>
>>> mock.return_value.foo.assert_called_with(a=2, b=3)

여기서부터는 구성하고 연쇄 호출에 대한 어서션을 만드는 간단한 단계입니
다. 물론 또 다른 대안은 처음부터 더 테스트하기 쉬운 방식으로 코드를 작
성하는 것입니다...

그래서, 다음과 같은 코드가 있다고 가정합시다:

>>> class Something:
...     def __init__(self):
...         self.backend = BackendProvider()
...     def method(self):
...         response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
...         # more code

"BackendProvider" 가 이미 잘 테스트 되었다고 가정하면, "method()"를 어
떻게 테스트해야 할까요? 특히, 코드 섹션 "# more code"가 response 객체
를 올바른 방식으로 사용하는지 테스트하고 싶습니다.

이 호출의 연쇄는 인스턴스 어트리뷰트에서 이루어지기 때문에 "Something"
인스턴스에서 "backend" 어트리뷰트를 몽키 패치 할 수 있습니다. 이 특별
한 경우에는 "start_call"에 대한 최종 호출의 반환 값에만 관심이 있어서
해야 할 구성이 많지 않습니다. 반환하는 객체가 '파일류(file-like)'라고
가정하고, response 객체가 "spec"으로 내장 "open()"을 사용하도록 할 것
입니다.

이를 위해 모의 백 엔드로 모의 인스턴스를 만들고 모의 response 객체를
만듭니다. 응답을 최종 "start_call"의 반환 값으로 설정하기 위해 다음을
수행할 수 있습니다:

   mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response

"configure_mock()" 메서드를 사용하여 약간 더 좋은 방법으로 반환 값을
직접 설정할 수 있습니다:

   >>> something = Something()
   >>> mock_response = Mock(spec=open)
   >>> mock_backend = Mock()
   >>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response}
   >>> mock_backend.configure_mock(**config)

이것들로 우리는 "모의 백 엔드"를 몽키 패치하고 실제 호출을 할 수 있습
니다:

   >>> something.backend = mock_backend
   >>> something.method()

"mock_calls"를 사용하면 단일 어서션으로 연쇄 호출을 확인할 수 있습니다
. 연쇄 호출은 한 줄의 코드에 있는 여러 번의 호출이라서, "mock_calls"에
는 여러 항목이 있게 됩니다. "call.call_list()"를 사용하여 이 호출의 리
스트를 만들 수 있습니다:

   >>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
   >>> call_list = chained.call_list()
   >>> assert mock_backend.mock_calls == call_list


부분 모킹
---------

어떤 테스트에서 "datetime.date.today()"에 대한 호출이 알려진 날짜를 반
환하도록 모킹하려고 했지만, 테스트 중인 코드가 새로운 date 객체를 만들
지 못하도록 막고 싶지 않았습니다. 불행히도 "datetime.date"는 C로 작성
되었고, 그래서 정적 "date.today()" 메서드를 그저 몽키 패치 할 수 없었
습니다.

date 클래스를 모의 객체로 효과적으로 래핑하지만, 생성자 호출은 실제 클
래스로 전달하는 (그리고 진짜 인스턴스를 반환하는) 간단한 방법을 찾았습
니다.

"patch 데코레이터"는 여기서 테스트 중인 모듈에서 "date" 클래스를 모킹
하는 데 사용됩니다. 그런 다음 모의 date 클래스의 "side_effect" 어트리
뷰트는 실제 날짜를 반환하는 람다 함수로 설정됩니다. 모의 date 클래스가
호출되면 진짜 date가 생성되어 "side_effect"에 의해 반환됩니다.

   >>> from datetime import date
   >>> with patch('mymodule.date') as mock_date:
   ...     mock_date.today.return_value = date(2010, 10, 8)
   ...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
   ...
   ...     assert mymodule.date.today() == date(2010, 10, 8)
   ...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)

"datetime.date"를 전역적으로 패치하지 않았음에 유의하십시오. "date"를
*사용하는* 모듈에서 패치했습니다. 패치할 곳을 참조하십시오.

"date.today()"가 호출되면 알려진 날짜가 반환되지만, "date(...)" 생성자
에 대한 호출은 여전히 일반 date를 반환합니다. 이렇게 하지 않으면 테스
트 중인 코드와 정확히 같은 알고리즘을 사용하여 예상 결과를 계산해야 할
수 있습니다, 이는 고전적인 테스트 안티 패턴입니다.

date 생성자에 대한 호출은 "mock_date" 어트리뷰트("call_count"와 그 친
구들)에 기록되며 테스트에 유용할 수 있습니다.

date나 기타 내장 클래스 모킹을 처리하는 다른 방법은 이 블로그 페이지에
서 다루고 있습니다.


제너레이터 메서드 모킹하기
--------------------------

파이썬 제너레이터는 "yield" 문을 사용하는 함수나 메서드로, 이터레이트
할 때 일련의 값을 반환합니다 [1].

제너레이터 메서드 / 함수가 호출되면 제너레이터 객체를 반환합니다. 이터
레이트 하는 대상은 제너레이터 객체입니다. 이터레이션을 위한 프로토콜
메서드는 "__iter__()"이고, "MagicMock"을 사용하여 이를 모킹 할 수 있습
니다.

다음은 제너레이터로 구현된 "iter" 메서드를 갖는 예제 클래스입니다:

>>> class Foo:
...     def iter(self):
...         for i in [1, 2, 3]:
...             yield i
...
>>> foo = Foo()
>>> list(foo.iter())
[1, 2, 3]

이 클래스, 특히 "iter" 메서드를 어떻게 모킹할까요?

이터레이션("list" 호출로 인해 묵시적으로 이루어집니다)에서 반환된 값을
구성하려면, "foo.iter()" 호출에 의해 반환된 객체를 구성해야 합니다.

>>> mock_foo = MagicMock()
>>> mock_foo.iter.return_value = iter([1, 2, 3])
>>> list(mock_foo.iter())
[1, 2, 3]

[1] 제너레이터 표현식과 제너레이터의 더 고급 사용도 있지만, 여기서는
    다루지 않습니다. 제너레이터와 이것이 얼마나 강력한지에 대한 아주
    좋은 소개: Generator Tricks for Systems Programmers.


모든 테스트 메서드에 같은 패치 적용하기
---------------------------------------

여러 테스트 메서드에 대해 여러 패치를 적용하려면 모든 메서드에 패치 데
코레이터를 적용하는 것이 가장 확실한 방법입니다. 이것은 불필요한 반복
처럼 느낄 수 있습니다. 파이썬 2.6 이상에서는 "patch()"(이것의 모든 다
양한 형태)를 클래스 데코레이터로 사용할 수 있습니다. 이것은 클래스의
모든 테스트 메서드에 패치를 적용합니다. 테스트 메서드는 이름이 "test"
로 시작하는 메서드로 식별됩니다:

   >>> @patch('mymodule.SomeClass')
   ... class MyTest(unittest.TestCase):
   ...
   ...     def test_one(self, MockSomeClass):
   ...         self.assertIs(mymodule.SomeClass, MockSomeClass)
   ...
   ...     def test_two(self, MockSomeClass):
   ...         self.assertIs(mymodule.SomeClass, MockSomeClass)
   ...
   ...     def not_a_test(self):
   ...         return 'something'
   ...
   >>> MyTest('test_one').test_one()
   >>> MyTest('test_two').test_two()
   >>> MyTest('test_two').not_a_test()
   'something'

패치를 관리하는 다른 방법은 패처 메서드: start와 stop을 사용하는 것입
니다. 이를 통해 패치를 "setUp"과 "tearDown" 메서드로 옮길 수 있습니다.

   >>> class MyTest(unittest.TestCase):
   ...     def setUp(self):
   ...         self.patcher = patch('mymodule.foo')
   ...         self.mock_foo = self.patcher.start()
   ...
   ...     def test_foo(self):
   ...         self.assertIs(mymodule.foo, self.mock_foo)
   ...
   ...     def tearDown(self):
   ...         self.patcher.stop()
   ...
   >>> MyTest('test_foo').run()

이 기법을 사용하면 "stop"을 호출하여 패치가 "실행 취소"되도록 해야 합
니다. setUp에서 예외가 발생하면 tearDown이 호출되지 않기 때문에, 생각
보다 복잡할 수 있습니다. "unittest.TestCase.addCleanup()"은 이것을 더
쉽게 만듭니다:

   >>> class MyTest(unittest.TestCase):
   ...     def setUp(self):
   ...         patcher = patch('mymodule.foo')
   ...         self.addCleanup(patcher.stop)
   ...         self.mock_foo = patcher.start()
   ...
   ...     def test_foo(self):
   ...         self.assertIs(mymodule.foo, self.mock_foo)
   ...
   >>> MyTest('test_foo').run()


연결되지 않은 메서드 모킹하기
-----------------------------

오늘 테스트를 작성하는 동안 *연결되지 않은 메서드(unbound method)*를
패치해야 했습니다 (인스턴스가 아닌 클래스의 메서드를 패치하는 것입니다
). 어떤 객체가 이 특정 메서드를 호출했는지에 대한 어서션을 하고 싶기
때문에 첫 번째 인자로 self가 전달되는 것이 필요했습니다. 문제는 이것을
위해 모의 객체로 패치 할 수 없다는 것인데, 연결되지 않은 메서드를 모의
객체로 바꾸면 인스턴스에서 가져올 때 연결된 메서드가 되지 않아서, self
가 전달되지 않기 때문입니다. 해결 방법은 연결되지 않은 메서드를 실제
함수로 대신 패치하는 것입니다. "patch()" 데코레이터는 메서드를 모의 객
체로 패치하기 너무 쉽게 만들어서 실제 함수를 만들어야 하는 것이 성가십
니다.

"autospec=True"를 패치로 전달하면 *실제* 함수 객체로 패치가 수행됩니다
. 이 함수 객체는 그것이 교체하는 것과 같은 서명을 갖지만, 수면 아래에
서 모의 객체로 위임합니다. 전과 똑같은 방식으로 여전히 자동 생성된 모
의 객체를 얻습니다. 그것이 의미하는 것은, 클래스에서 연결되지 않은 메
서드를 패치하는데 사용하면 모킹 된 함수가 인스턴스에서 꺼낼 때 연결된
메서드로 바뀐다는 것입니다. "self"가 첫 번째 인자로 전달되고, 정확히
제가 원하는 것입니다:

>>> class Foo:
...   def foo(self):
...     pass
...
>>> with patch.object(Foo, 'foo', autospec=True) as mock_foo:
...   mock_foo.return_value = 'foo'
...   foo = Foo()
...   foo.foo()
...
'foo'
>>> mock_foo.assert_called_once_with(foo)

"autospec=True"를 사용하지 않으면 연결되지 않은 메서드가 대신 Mock 인
스턴스로 패치되고, "self"로 호출되지 않습니다.


모의 객체로 여러 호출 확인하기
------------------------------

mock에는 모의 객체가 어떻게 사용되는지에 대한 어서션을 만드는 데 유용
한 API가 있습니다.

>>> mock = Mock()
>>> mock.foo_bar.return_value = None
>>> mock.foo_bar('baz', spam='eggs')
>>> mock.foo_bar.assert_called_with('baz', spam='eggs')

모의 객체가 한 번만 호출되면 "call_count"가 1이라는 것도 어서트하는
"assert_called_once_with()" 메서드를 사용할 수 있습니다.

>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
>>> mock.foo_bar()
>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
Traceback (most recent call last):
    ...
AssertionError: Expected to be called once. Called 2 times.

"assert_called_with"와 "assert_called_once_with"는 모두 *가장 최근* 호
출에 대한 어서션을 합니다. 모의 객체가 여러 번 호출될 것이고, 이 호출
*모두*에 대해 어서션을 하고 싶다면 "call_args_list"를 사용할 수 있습니
다:

>>> mock = Mock(return_value=None)
>>> mock(1, 2, 3)
>>> mock(4, 5, 6)
>>> mock()
>>> mock.call_args_list
[call(1, 2, 3), call(4, 5, 6), call()]

"call" 도우미를 사용하면 이러한 호출에 대한 어서션을 쉽게 할 수 있습니
다. 예상 호출 리스트를 만들어서 "call_args_list"와 비교할 수 있습니다.
이것은 "call_args_list"의 repr과 매우 유사합니다:

>>> expected = [call(1, 2, 3), call(4, 5, 6), call()]
>>> mock.call_args_list == expected
True


가변 인자에 대처하기
--------------------

드물지만 여러분을 괴롭힐 수 있는 또 다른 상황은 모의 객체가 가변 인자
로 호출되는 경우입니다. "call_args"와 "call_args_list"는 인자에 대한 *
참조*를 저장합니다. 테스트 중인 코드에 의해 인자가 변경되면 모의 객체
가 호출되었을 때의 값에 대해 더는 어서션 할 수 없습니다.

다음은 문제를 보여주는 예제 코드입니다. 'mymodule'에 정의된 다음 함수
를 상상해보십시오:

   def frob(val):
       pass

   def grob(val):
       "First frob and then clear val"
       frob(val)
       val.clear()

"grob"이 올바른 인자로 "frob"을 호출하는지 테스트하려고 할 때 어떤 일
이 발생하는지 보십시오:

   >>> with patch('mymodule.frob') as mock_frob:
   ...     val = {6}
   ...     mymodule.grob(val)
   ...
   >>> val
   set()
   >>> mock_frob.assert_called_with({6})
   Traceback (most recent call last):
       ...
   AssertionError: Expected: (({6},), {})
   Called with: ((set(),), {})

한가지 가능성은 모의 객체가 전달한 인자를 복사하는 것일 수 있습니다.
그러면 동일성(equality)을 위해 객체 아이덴티티에 의존하는 어서션을 수
행하면 문제가 발생할 수 있습니다.

여기에 "side_effect" 함수를 사용하는 한 가지 해결책이 있습니다. 모의
객체에 "side_effect" 함수를 제공하면 모의 객체와 같은 인자로
"side_effect"가 호출됩니다. 이는 인자를 복사하여 나중에 어서션을 위해
저장할 기회를 제공합니다. 이 예제에서는 *다른* 모의 객체를 사용하여 인
자를 저장해서 어서션을 수행하기 위해 모의 객체 메서드를 사용할 수 있습
니다. 다시 한번 도우미 함수가 이것을 설정합니다.

   >>> from copy import deepcopy
   >>> from unittest.mock import Mock, patch, DEFAULT
   >>> def copy_call_args(mock):
   ...     new_mock = Mock()
   ...     def side_effect(*args, **kwargs):
   ...         args = deepcopy(args)
   ...         kwargs = deepcopy(kwargs)
   ...         new_mock(*args, **kwargs)
   ...         return DEFAULT
   ...     mock.side_effect = side_effect
   ...     return new_mock
   ...
   >>> with patch('mymodule.frob') as mock_frob:
   ...     new_mock = copy_call_args(mock_frob)
   ...     val = {6}
   ...     mymodule.grob(val)
   ...
   >>> new_mock.assert_called_with({6})
   >>> new_mock.call_args
   call({6})

"copy_call_args"는 호출될 모의로 호출됩니다. 어서션을 수행할 새로운 모
의 객체를 반환합니다. "side_effect" 함수는 인자의 사본을 만들고 사본으
로 "new_mock"을 호출합니다.

참고:

  모의 객체를 한 번만 사용하려는 경우 호출 시점에서 인자를 확인하는 쉬
  운 방법이 있습니다. 단순히 "side_effect" 함수 내에서 확인할 수 있습
  니다.

  >>> def side_effect(arg):
  ...     assert arg == {6}
  ...
  >>> mock = Mock(side_effect=side_effect)
  >>> mock({6})
  >>> mock(set())
  Traceback (most recent call last):
      ...
  AssertionError

다른 접근법은 인자를 복사("copy.deepcopy()"를 사용해서)하는 "Mock"이나
"MagicMock"의 서브 클래스를 만드는 것입니다. 구현 예는 다음과 같습니다
:

>>> from copy import deepcopy
>>> class CopyingMock(MagicMock):
...     def __call__(self, /, *args, **kwargs):
...         args = deepcopy(args)
...         kwargs = deepcopy(kwargs)
...         return super().__call__(*args, **kwargs)
...
>>> c = CopyingMock(return_value=None)
>>> arg = set()
>>> c(arg)
>>> arg.add(1)
>>> c.assert_called_with(set())
>>> c.assert_called_with(arg)
Traceback (most recent call last):
    ...
AssertionError: Expected call: mock({1})
Actual call: mock(set())
>>> c.foo
<CopyingMock name='mock.foo' id='...'>

"Mock"이나 "MagicMock"을 서브 클래싱할 때 모든 동적으로 만들어진 어트
리뷰트와 "return_value"는 자동으로 서브 클래스를 사용합니다. 즉,
"CopyingMock"의 모든 자식도 "CopyingMock" 형이 됩니다.


중첩 패치
---------

patch를 컨텍스트 관리자로 것이 멋지기는 하지만, 여러 패치를 수행하면
오른쪽으로 점점 더 들여쓰기 되는 문장으로 중첩될 수 있습니다:

   >>> class MyTest(unittest.TestCase):
   ...
   ...     def test_foo(self):
   ...         with patch('mymodule.Foo') as mock_foo:
   ...             with patch('mymodule.Bar') as mock_bar:
   ...                 with patch('mymodule.Spam') as mock_spam:
   ...                     assert mymodule.Foo is mock_foo
   ...                     assert mymodule.Bar is mock_bar
   ...                     assert mymodule.Spam is mock_spam
   ...
   >>> original = mymodule.Foo
   >>> MyTest('test_foo').test_foo()
   >>> assert mymodule.Foo is original

unittest "cleanup" 함수와 패처 메서드: start와 stop을 사용하면 중첩된
들여쓰기 없이 같은 효과를 얻을 수 있습니다. 간단한 도우미 메서드인
"create_patch"는 제 자리에서 패치를 설치하고 만들어진 모의 객체를 반환
합니다:

   >>> class MyTest(unittest.TestCase):
   ...
   ...     def create_patch(self, name):
   ...         patcher = patch(name)
   ...         thing = patcher.start()
   ...         self.addCleanup(patcher.stop)
   ...         return thing
   ...
   ...     def test_foo(self):
   ...         mock_foo = self.create_patch('mymodule.Foo')
   ...         mock_bar = self.create_patch('mymodule.Bar')
   ...         mock_spam = self.create_patch('mymodule.Spam')
   ...
   ...         assert mymodule.Foo is mock_foo
   ...         assert mymodule.Bar is mock_bar
   ...         assert mymodule.Spam is mock_spam
   ...
   >>> original = mymodule.Foo
   >>> MyTest('test_foo').run()
   >>> assert mymodule.Foo is original


MagicMock으로 딕셔너리 모킹하기
-------------------------------

딕셔너리나 다른 컨테이너 객체를 모킹하여, 여전히 딕셔너리처럼 동작하면
서 이에 대한 모든 액세스를 기록하고 싶을 수 있습니다.

딕셔너리처럼 동작하는 "MagicMock"을 사용하고 "side_effect"가 딕셔너리
액세스를 우리의 제어하에 있는 실제 하부 딕셔너리로 위임하게 해서 목적
을 달성할 수 있습니다.

"MagicMock"의 "__getitem__()"과 "__setitem__()" 메서드가 호출될 때 (일
반 딕셔너리 액세스), "side_effect"는 키로 호출됩니다 (그리고
"__setitem__"의 경우는 값도). 반환되는 것을 제어 할 수도 있습니다.

"MagicMock"이 사용된 후에 "call_args_list"와 같은 어트리뷰트를 사용하
여 딕셔너리가 어떻게 사용되었는지를 어서트할 수 있습니다:

>>> my_dict = {'a': 1, 'b': 2, 'c': 3}
>>> def getitem(name):
...      return my_dict[name]
...
>>> def setitem(name, val):
...     my_dict[name] = val
...
>>> mock = MagicMock()
>>> mock.__getitem__.side_effect = getitem
>>> mock.__setitem__.side_effect = setitem

참고:

  "MagicMock"을 사용하는 대신 "Mock"을 사용하고 *오직* 원하는 매직 메
  서드만 제공할 수 있습니다:

  >>> mock = Mock()
  >>> mock.__getitem__ = Mock(side_effect=getitem)
  >>> mock.__setitem__ = Mock(side_effect=setitem)

  *세 번째* 옵션은 "MagicMock"을 사용하지만, "dict"를 *spec* (또는
  *spec_set*) 인자로 전달하여 만들어진 "MagicMock"이 딕셔너리 매직 메
  서드 만 갖도록 하는 것입니다:

  >>> mock = MagicMock(spec_set=dict)
  >>> mock.__getitem__.side_effect = getitem
  >>> mock.__setitem__.side_effect = setitem

이러한 부작용 함수가 제자리에 들어가면, "mock"은 일반 딕셔너리처럼 작
동하지만, 액세스를 기록합니다. 존재하지 않는 키에 액세스하려고 하면
"KeyError"를 발생시키기조차 합니다.

>>> mock['a']
1
>>> mock['c']
3
>>> mock['d']
Traceback (most recent call last):
    ...
KeyError: 'd'
>>> mock['b'] = 'fish'
>>> mock['d'] = 'eggs'
>>> mock['b']
'fish'
>>> mock['d']
'eggs'

사용된 후에 일반적인 모의 객체 메서드와 어트리뷰트를 사용하여 액세스에
대한 어서션을 할 수 있습니다:

>>> mock.__getitem__.call_args_list
[call('a'), call('c'), call('d'), call('b'), call('d')]
>>> mock.__setitem__.call_args_list
[call('b', 'fish'), call('d', 'eggs')]
>>> my_dict
{'a': 1, 'b': 'fish', 'c': 3, 'd': 'eggs'}


Mock 서브 클래스와 그 어트리뷰트
--------------------------------

"Mock"을 서브 클래싱하려는 여러 가지 이유가 있습니다. 한 가지 이유는
도우미 메서드를 추가하는 것입니다. 다음은 시시한 예입니다:

>>> class MyMock(MagicMock):
...     def has_been_called(self):
...         return self.called
...
>>> mymock = MyMock(return_value=None)
>>> mymock
<MyMock id='...'>
>>> mymock.has_been_called()
False
>>> mymock()
>>> mymock.has_been_called()
True

"Mock" 인스턴스의 표준 동작은 어트리뷰트와 반환 값 모의 객체가 액세스
되는 모의 객체의 형과 같은 형이라는 것입니다. 이를 통해 "Mock" 어트리
뷰트는 "Mock"이고 "MagicMock" 어트리뷰트는 "MagicMock"이 됩니다 [2].
따라서 도우미 메서드를 추가하기 위해 서브 클래싱하면 이 메서드는 서브
클래스 인스턴스의 어트리뷰트와 반환 값 모의 객체에서도 사용할 수 있습
니다.

>>> mymock.foo
<MyMock name='mock.foo' id='...'>
>>> mymock.foo.has_been_called()
False
>>> mymock.foo()
<MyMock name='mock.foo()' id='...'>
>>> mymock.foo.has_been_called()
True

때때로 이것은 불편합니다. 예를 들어, 한 사용자가 Twisted adaptor 를 만
들기 위해 Mock을 서브 클래싱했습니다. 이것을 어트리뷰트에도 적용하면
실제로 에러가 발생합니다.

"Mock"(이것의 모든 종류에서)은 "_get_child_mock"이라는 메서드를 사용하
여 어트리뷰트와 반환 값을 위한 이러한 "서브 모의 객체"를 만듭니다. 이
메서드를 재정의하여 서브 클래스가 어트리뷰트에 사용되지 않도록 할 수
있습니다. 서명은 모의 객체 생성자에 전달되는 임의의 키워드 인자
("**kwargs")를 취하는 것입니다:

>>> class Subclass(MagicMock):
...     def _get_child_mock(self, /, **kwargs):
...         return MagicMock(**kwargs)
...
>>> mymock = Subclass()
>>> mymock.foo
<MagicMock name='mock.foo' id='...'>
>>> assert isinstance(mymock, Subclass)
>>> assert not isinstance(mymock.foo, Subclass)
>>> assert not isinstance(mymock(), Subclass)

[2] 이 규칙의 예외는 콜러블이 아닌 모의 객체입니다. 어트리뷰트는 콜러
    블 변형을 사용하는데, 그렇지 않으면 콜러블이 아닌 모의 객체가 콜러
    블 메서드를 가질 수 없기 때문입니다.


patch.dict로 임포트를 모킹하기
------------------------------

모킹이 어려울 수 있는 한 가지 상황은 함수 내부에 지역 임포트가 있는 경
우입니다. 이것들은 모킹하기가 더 어려운데, 우리가 패치 할 수 있는 모듈
이름 공간의 객체를 사용하지 않기 때문입니다.

일반적으로 지역 임포트는 피해야 합니다. 그것들은 때때로 순환 의존성을
막기 위해 수행되는데, *보통* 문제를 해결하는 더 좋은 방법(코드를 리팩
터 하십시오)이 있습니다. 또는 임포트를 지연시켜서 "선불 비용"을 방지하
기 위해 수행합니다. 이 또한 무조건적인 지역 임포트보다 더 나은 방법으
로 해결할 수 있습니다 (모듈을 클래스나 모듈 어트리뷰트로 저장하고 처음
사용할 때만 임포트 합니다).

That aside there is a way to use "mock" to affect the results of an
import. Importing fetches an *object* from the "sys.modules"
dictionary. Note that it fetches an *object*, which need not be a
module. Importing a module for the first time results in a module
object being put in "sys.modules", so usually when you import
something you get a module back. This need not be the case however.

즉, "patch.dict()"를 사용하여 *임시로* "sys.modules"에 모의 객체를 넣
을 수 있습니다. 이 패치가 활성화되어있는 동안 모든 임포트는 모의 객체
를 가져옵니다. 패치가 완료되면 (데코레이트 된 함수가 종료되거나, with
문 본문이 완료되거나 "patcher.stop()"이 호출되면) 이전에 있던 모든 것
이 안전하게 복원됩니다.

다음은 'fooble' 모듈을 모킹하는 예입니다.

>>> import sys
>>> mock = Mock()
>>> with patch.dict('sys.modules', {'fooble': mock}):
...    import fooble
...    fooble.blob()
...
<Mock name='mock.blob()' id='...'>
>>> assert 'fooble' not in sys.modules
>>> mock.blob.assert_called_once_with()

보시다시피 "import fooble"은 성공하지만, 끝났을 때 "sys.modules"에는
'fooble'이 남아 있지 않습니다.

이것은 "from module import name" 형식에서도 작동합니다:

>>> mock = Mock()
>>> with patch.dict('sys.modules', {'fooble': mock}):
...    from fooble import blob
...    blob.blip()
...
<Mock name='mock.blob.blip()' id='...'>
>>> mock.blob.blip.assert_called_once_with()

약간의 추가 작업으로 패키지 임포트를 모킹 할 수도 있습니다:

>>> mock = Mock()
>>> modules = {'package': mock, 'package.module': mock.module}
>>> with patch.dict('sys.modules', modules):
...    from package.module import fooble
...    fooble()
...
<Mock name='mock.module.fooble()' id='...'>
>>> mock.module.fooble.assert_called_once_with()


호출 순서 추적과 덜 상세한 호출 어서션
--------------------------------------

"Mock" 클래스를 사용하면 "method_calls" 어트리뷰트를 통해 모의 객체에
서 메서드 호출의 *순서*를 추적할 수 있습니다. 개별 모의 객체 간의 호출
순서를 추적할 수는 없지만, "mock_calls"를 사용하여 같은 효과를 얻을 수
있습니다.

모의 객체들은 "mock_calls"에서 자식 모의 객체에 대한 호출을 추적하고,
모의 객체의 임의 어트리뷰트에 액세스하면 자식 모의 객체를 만들기 때문
에, 부모 모의 객체로부터 개별 모의 객체를 만들 수 있습니다. 그러면 해
당 자식 모의 객체에 대한 호출은 부모의 "mock_calls"에 순서대로 기록됩
니다:

>>> manager = Mock()
>>> mock_foo = manager.foo
>>> mock_bar = manager.bar

>>> mock_foo.something()
<Mock name='mock.foo.something()' id='...'>
>>> mock_bar.other.thing()
<Mock name='mock.bar.other.thing()' id='...'>

>>> manager.mock_calls
[call.foo.something(), call.bar.other.thing()]

그런 다음 관리자 모의 객체의 "mock_calls" 어트리뷰트와 비교하여 순서를
포함하여 호출에 대해 어서션 할 수 있습니다:

>>> expected_calls = [call.foo.something(), call.bar.other.thing()]
>>> manager.mock_calls == expected_calls
True

"patch"가 모의 객체를 만들고 제자리에 배치하는 경우, "attach_mock()"
메서드를 사용하여 모의 객체를 관리자 모의 객체에 연결할 수 있습니다.
연결 후 호출은 관리자의 "mock_calls"에 기록됩니다.

   >>> manager = MagicMock()
   >>> with patch('mymodule.Class1') as MockClass1:
   ...     with patch('mymodule.Class2') as MockClass2:
   ...         manager.attach_mock(MockClass1, 'MockClass1')
   ...         manager.attach_mock(MockClass2, 'MockClass2')
   ...         MockClass1().foo()
   ...         MockClass2().bar()
   <MagicMock name='mock.MockClass1().foo()' id='...'>
   <MagicMock name='mock.MockClass2().bar()' id='...'>
   >>> manager.mock_calls
   [call.MockClass1(),
   call.MockClass1().foo(),
   call.MockClass2(),
   call.MockClass2().bar()]

많은 호출이 이루어졌지만, 특정 시퀀스에만 관심이 있다면 대안은
"assert_has_calls()" 메서드를 사용하는 것입니다. 이것은 호출 리스트를
취합니다 ("call" 객체로 구성됩니다). 해당 호출 시퀀스가 "mock_calls"에
있으면 어서션이 성공합니다.

>>> m = MagicMock()
>>> m().foo().bar().baz()
<MagicMock name='mock().foo().bar().baz()' id='...'>
>>> m.one().two().three()
<MagicMock name='mock.one().two().three()' id='...'>
>>> calls = call.one().two().three().call_list()
>>> m.assert_has_calls(calls)

연쇄 호출 "m.one().two().three()"가 모의 객체에 대한 호출의 전부는 아
니지만, 어서션이 여전히 성공합니다.

때로는 모의 객체에 여러 번의 호출이 있을 수 있고, 그 호출들의 *일부*에
대만 어서션에만 관심이 있을 수 있습니다. 순서에 신경 쓰지 않을 수도 있
습니다. 이 경우 "any_order=True"를 "assert_has_calls"로 전달할 수 있습
니다:

>>> m = MagicMock()
>>> m(1), m.two(2, 3), m.seven(7), m.fifty('50')
(...)
>>> calls = [call.fifty('50'), call(1), call.seven(7)]
>>> m.assert_has_calls(calls, any_order=True)


더 복잡한 인자 일치
-------------------

"ANY"와 같은 기본 개념을 사용하여 모의 객체에 인자로 사용되는 객체에
대해 더 복잡한 어서션을 수행하도록 매처(matchers)를 구현할 수 있습니다
.

기본적으로 객체 아이덴티티에 기반하여 를 기준으로 같다고 비교되는 (이
것이 사용자 정의 클래스의 파이썬 기본 동작입니다) 어떤 객체가 모의 객
체로 전달되기를 기대한다고 가정합시다. "assert_called_with()"를 사용하
려면 정확히 같은 객체를 전달해야 합니다. 이 객체의 일부 어트리뷰트에만
관심이 있다면 이러한 어트리뷰트를 확인하는 매처를 만들 수 있습니다.

이 예에서 "assert_called_with"에 대한 '표준' 호출이 충분하지 않다는 것
을 볼 수 있습니다:

>>> class Foo:
...     def __init__(self, a, b):
...         self.a, self.b = a, b
...
>>> mock = Mock(return_value=None)
>>> mock(Foo(1, 2))
>>> mock.assert_called_with(Foo(1, 2))
Traceback (most recent call last):
    ...
AssertionError: Expected: call(<__main__.Foo object at 0x...>)
Actual call: call(<__main__.Foo object at 0x...>)

"Foo" 클래스를 위한 비교 함수는 다음과 같습니다:

>>> def compare(self, other):
...     if not type(self) == type(other):
...         return False
...     if self.a != other.a:
...         return False
...     if self.b != other.b:
...         return False
...     return True
...

그리고 동등 비교 연산에 이와 같은 비교 함수를 사용할 수 있는 매처 객체
는 다음과 같습니다:

>>> class Matcher:
...     def __init__(self, compare, some_obj):
...         self.compare = compare
...         self.some_obj = some_obj
...     def __eq__(self, other):
...         return self.compare(self.some_obj, other)
...

이 모든 것을 종합하면:

>>> match_foo = Matcher(compare, Foo(1, 2))
>>> mock.assert_called_with(match_foo)

"Matcher"는 compare 함수와 비교하려는 "Foo" 객체로 인스턴스 화 됩니다.
"assert_called_with"에서는 "Matcher" 동등 비교 메서드가 호출되는데, 이
메서드는 모의 객체가 호출된 객체와 우리가 매처를 만들 때 제공한 객체를
비교합니다. 일치하면 "assert_called_with"가 통과하고, 그렇지 않으면
"AssertionError"가 발생합니다:

>>> match_wrong = Matcher(compare, Foo(3, 4))
>>> mock.assert_called_with(match_wrong)
Traceback (most recent call last):
    ...
AssertionError: Expected: ((<Matcher object at 0x...>,), {})
Called with: ((<Foo object at 0x...>,), {})

약간의 조정만으로 비교 함수가 "AssertionError"를 직접 발생시키고 더 유
용한 실패 메시지를 제공할 수 있습니다.

버전 1.5부터, 파이썬 테스트 라이브러리 PyHamcrest는 여기에서 유용할 수
있는 유사한 기능을 동등 비교 매처의 형태로 제공합니다
(hamcrest.library.integration.match_equality).
