Python에서 기본적으로 존재하는 unittest 라이브러리의
mock 클래스에 대해 알아보겠습니다.

1. Mock

Mock 클래스를 이용해 다음과 같은 일을 할 수 있습니다.

>>> from unittest.mock import Mock

>>> def foo(value):
...     return 2*value
...
>>> foo(1)
2
>>> foo(2)
4
>>> mock_foo = Mock(foo, return_value='mocked return')

>>> mock_foo(1)
'mocked return'
>>> mock_foo(2)
'mocked return'

또한, side_effect를 지정해줘서 Error를 일으킬 수 있습니다.

>>> mock = Mock(foo, return_value='mocked return', side_effect=KeyError('key Error'))
>>> mock()
Traceback (most recent call last):
  ~
KeyError: 'key Error'

>>> mock.side_effect = None
>>> mock()
'mocked return'

side_effect는 말그대로 해당 Mock 객체를 실행할 때, 선행되는 것입니다.

>>> mock.side_effect = (lambda : "side effect's return")
>>> mock()
"side effect's return"

2. MagicMock

또한, MagicMock을 이용해 mock 메서드를 만들 수 있습니다.

>>> from unittest.mock import MagicMock

>>> class testClass():
...     def method(self, arg):
...         return 1
... 
>>> test_class = testClass()
>>> test_class.method = MagicMock(return_value=10)
>>> test_class.method(1)
10

MagicMock을 이용해 mock 메서드를 만들 수 있습니다.

그런데 Mock과 MagicMock은 왜 나뉘어져 있을까요?

MagicMock은 Mock의 Subclass이며
여러 메서드들(magic methods)을 가지고 있습니다.

즉, MagicMock은 SuperSet,
Mock은 MagicMock의 SubSet으로 받아들일 수 있습니다.

mock_and_magic_mock.png

만약 테스트 시 magic methods가 필요없다면 Mock을 사용하면 됩니다.
테스트에 불필요한 메서드들을 굳이 가져올 필요는 없으니까요.

3. patch

patch의 사전적 의미를 찾아보면 "구멍 난 데를 때우거나 장식용으로 덧대는 데 쓰이는 조각"입니다.
임시로 덧붙이는 느낌으로 알고 있으면 됩니다.

patch decorator는 module을 patching합니다.
이게 무슨 뜻이냐면
해당 모듈을 임시로 Mocking해서 사용한다는 뜻입니다.
(patch는 MagicMock을 합니다.)

예제를 살펴보면 느낌을 알 수 있습니다.

다음과 같은 내용의 foo_module.py라는 module이 있다고 가정합니다.

# foo_module.py
class FooClass():
    def foo_method(value):
        return 2 * value

foo_module.py에 있는 FooClass의 foo_method를 patch해보겠습니다.

지정한 object는 테스트 중에 모의 객체(mock)로 치환되고 테스트가 끝나면 복원됩니다.

>>> from unittest.mock import patch
>>> import foo_module

>>> @patch('foo_module.FooClass.foo_method')
>>> def test(mock_foo_method):
       # We assign return value to the mock object
>>>    mock_foo_method.return_value = 'mocked return value'
>>>    f = foo_module.FooClass()
>>>    return f.foo_method(1)

>>> test()
'mocked return value'

>>> not_mocked_foo = foo_module.FooClass()
>>> not_mocked_foo.foo_method(1)
2

patch decorator로 꾸며준 test 함수는
foo_method가 mocking된 뒤에
mocking된 foo_method의 return_value가 바뀐 상태로 호출되었습니다.
그리고 테스트가 끝나면 foo_method는 복원됩니다.

그러므로 not_mocked_foo의 경우,
복원된 foo_method이므로 원래 foo_module.py에서 정의된 대로 return 값이 출력됩니다.