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) 사용 방법에 대한 어서션을 만들 수 있도록 하는 메서드와 어트리뷰트를 제공합니다.

참고

이 예의 대부분에서 MockMagicMock 클래스는 교환할 수 있습니다. 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부터, AsyncMockMagicMock__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부터, AsyncMockMagicMock__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를 갖지 않습니다 - 그러면 이제 코드가 망가졌음에도 테스트는 계속 통과합니다!

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

>>> 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.objectpatch.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.objectpatch.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을 사용하는 것입니다. 이를 통해 패치를 setUptearDown 메서드로 옮길 수 있습니다.

>>> 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_withassert_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_argscall_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을 사용하지만, dictspec (또는 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로 임포트를 모킹하기

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

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

그 외에도 mock을 사용하여 임포트 결과에 영향을 주는 방법이 있습니다. 임포트는 sys.modules 딕셔너리에서 객체를 가져옵니다. 객체를 가져온다는 것에 유의하십시오, 이것이 모듈일 필요는 없습니다. 처음으로 모듈을 임포트 하면 sys.modules에 모듈 객체가 배치되어서, 일반적으로 무언가를 임포트 할 때 모듈을 다시 받습니다. 그러나 반드시 그런 것은 아닙니다.

즉, 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=Trueassert_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).