Contents

Unittest.Mock

Introduction

단위 테스트를 작성하다보면 데이터베이스 또는 외부 API에 의존하는 코드를 테스트해야 할 일이 생긴다. 운영 환경 대비 제약이 많은 테스트 환경에서는 실제 데이터베이스와 연동하거나 실제 외부 API를 호출하기가 불가능한 경우가 많다. 가령 가능하더라도, 이렇게 외부 서비스에 의존하는 테스는 해당 서비스에 문제가 생길 경우 실패할 수 있으며 실행 속도도 당연히 느리다.

따라서 단위 테스트를 작성할 때 외부에 의존하는 부분을 임의의 가짜로 대체하는 기법이 자주 사용되는데 이를 Mocking이라고 한다. 즉, Mocking은 외부 서비스에 의존하지 않고 독립적으로 실행이 가능한 단위 테스트를 작성하기 위해서 사용되는 테스팅 기법이다.

unittest.mock 모듈

unittest.mock 모듈은 파이썬 3.3부터 언어에 기본 내장되어 있는 모킹 라이브러리이다. 따라서 별도의 외부 라이브러리 설치 없이 파이썬 인터프리터에서 다음과 같이 임포트해서 바로 사용할 수 있다. 이 모듈을 이용하면 단위 테스트를 작성할 때 코드의 특정 부분을 mock 객체로 대체할 수 있으며, 해당 mock 객체가 어떻게 사용되 었는지 검증할 수 있다.

1
from unittest.mock import Mock, MagicMock, call

Mock 객체 설정

mocking은 소위 mock이라고 불리는 가짜 객체를 생성하는 것부터 시작한다. 우리는 이 mock 객체가 어떻게 작동을 할지르 지정해 줄 수 있으며, 이 mock 객체는 자신을 상대로 어떤 작업이 일어났는지를 기억한다.

먼저 호출되었을 때 특정 값을 리턴하는 mock객체는 return_value 옵션을 이용해서 생성할 수 있다.

1
2
3
4
from unittest.mock import Mock
mock = Mock(return_value='Hello, Mock!')
mock()
'Hello, Mock!'

호출되었을 때 예외가 발생하는 mock 객체는 side_effect 옵션을 사용해서 생성할 수 있다.

1
2
3
4
5
6
7
8
9
mock = Mock(side_effect=Exception('Oops!'))
mock()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dale/.pyenv/versions/3.7.6/lib/python3.7/unittest/mock.py", line 1011, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/Users/dale/.pyenv/versions/3.7.6/lib/python3.7/unittest/mock.py", line 1071, in _mock_call
    raise effect
Exception: Oops!

side_effect 옵션에 리스트를 넘기면 mock 객체가 호출될 때마다 매번 다른 값을 리턴할 수도 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
mock = Mock(side_effect=[1, 2, 3])
mock()
1
mock()
2
mock()
3
mock()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dale/.pyenv/versions/3.7.6/lib/python3.7/unittest/mock.py", line 1011, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/Users/dale/.pyenv/versions/3.7.6/lib/python3.7/unittest/mock.py", line 1073, in _mock_call
    result = next(effect)
StopIteration

mock은 객체처럼 속성을 가질 수 있는데 각 속성은 새로운 mock이 된다. 따라서 다음과 같이 특정 속성에 값을 할당할 수도 있고, 특정 메서드의 리턴 값을 지정해 줄 수도 있다.

1
2
3
4
5
6
7
mock = Mock()
mock.attribute = 'ATTRIBUTE'
mock.attribute
'ATTRIBUTE'
mock.method.return_value = 'METHOD RETURN VALUE'
mock.method()
'METHOD RETURN VALUE'

MagicMock

파이썬에는 Magic method라는 개념이 있는데, 모든 객체에는 언어 레벨에서 특수한 목적으로 쓰이는 메서드들을 정의할 수 있다. 대표적으로 __str__의 경우, 객체를 읽기 좋은 형태의 문자열로 출력하기 위해 사용되는 매직 메서드이다.

기본적으로 Mock 클래스를 사용하면 이러한 매직 메서드가 자동으로 모킹되지 않는다.

1
2
3
4
5
6
7
from unittest.mock import Mock
mock = Mock()
mock.__str__.return_value

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'method-wrapper' object has no attribute 'return_value'

따라서, 매직 메서드를 모킹하려면 다른 속성이나 메서드와 달리 다음과 같이 새로운 mock 객체를 직접 생성해서 할당을 해줘야 한다.

1
2
3
4
mock.__str__ = Mock(return_value = "I'm a mock.")
str(mock)

"I'm a mock."

하지만 Mock 클래스의 확장 버전인 MagicMock 클래스를 사용하면 이러한 매직 메서드를 미리 알아서 모킹 해놓기 때문에 편리하다.

1
2
3
4
5
6
7
8
from unittest.mock import MagicMock
mock = MagicMock()
mock.__str__.return_value
"<MagicMock id='4556752144'>"

mock.__str__.return_value = "I'm a magic mock."
str(mock)
"I'm a magic mock."