Unit Test
를 작성하다 보면 데이터베이스 또는 외부 API 에 의존하는 코드를 테스트해야 할 일이 생기기 마련이다.
운영 환경 대비 제약이 많은 테스트 환경에서 실제 데이터베이스 와 연동하거나 실제 외부 API 를 호출하기가 불가능한 경우가 많다.
가능하더라도, 이렇게 외부 서비스에 의존하는 테스트는 해당 서비스(데이터베이스 혹은 외부 API 등)에 문제가 있으면 깨질 수 있으며 실행 속도가 느릴 수밖에 없다.
따라서 Unit Test
를 작성할 때 외부에 의존하는 부분을 임의의 가짜로 대체하는 기법이 자주 사용되는데 이를 모킹 Mocking
이라고 한다.
모킹 Mocking
은 외부 서비스에 의존하지 않고 독립적으로 실행이 가능한 Unit Test
를 작성하기 위해 사용되는 테스팅 기법이다.
unittest.mock
모듈은 파이썬 3.3 부터 언어 자체에 기본 내장된 모킹 라이브러리다.
따라서 별도의 외부 라이브러리 설치 없이 파이썬 인터프리터에서 아래 코드와 같이 임포트해서 사용할 수 있다.
from unittest.mock import Mock, MagicMock, call
이 모듈을 사용하면 Unit Test
를 작성할 때 코드의 특정 부분을 Mock
객체로 대체할 수 있으며, 해당 Mock
객체가 어떻게 사용되었는지 검증할 수 있다.
Mocking
은 Mock
이라고 불리는 가짜 객체를 생성하는 것부터 시작한다.
생성한 가짜 객체 Mock
객체가 어떻게 작동할 지를 지정해 줄 수 있으며 이 Mock
객체는 자신을 상대로 어떤 작업이 일어났는지를 기억한다.
먼저 호출되었을 때 특정 값을 리턴하는 Mock
객체는 return_value
옵션을 이용해서 생성할 수 있다.
from unittest.mock import Mock
mock = Mock(return_value='Hello, Mock!')
mock()
반면에 호출되었을 때 예외가 발생하는 Mock
객체는 side_effect
옵션을 이용해서 생성할 수 있다.
mock = Mock(side_effect=Exception('Oops!'))
mock()
side_effect
옵션에 리스트를 넘기면 Mock
객체가 호출될 때마다 매번 다른 값을 리턴할 수도 있다.
mock = Mock(side_effect=[1, 2, 3])
mock()
mock()
mock()
mock()
side_effect
옵션에 함수를 넘기면 Mock
객체를 호출했을 때 주어진 인자에 따라 다른 값을 리턴할 수 있다.
mock = Mock(side_effect=lambda x: x * 10)
mock(1)
mock(2)
return_value
와 side_effect
옵션은 꼭 Mock()
생성자의 인자로 넘어갈 필요는 없다.
아래와 같이 Mock
객체를 생성한 후, 얼마든지 옵션값은 바꿀 수 있다.
mock = Mock()
mock.return_value = 1
mock()
mock.return_value = 2
mock()
Mock
이 Mock
객체는 함수처럼 바로 호출(mock())을 할 수도 있지만, 객체처럼 속성도 가질 수 있는데 각 속성은 새로운 Mock
이 된다.
따라서 아래와 같이 특정 속성에 값을 할당할 수도 있고, 특정 메서드의 리턴 값을 지정해 줄 수도 있다.
mock = Mock()
mock.attribute = 'ATTRIBUTE'
mock.attribute
mock.method.return_value = 'METHOD RETURN VALUE'
mock.method()
mock.method.return_value 를 하여 mock 객체 안에 method 이름을 가진 속성은 Mock 객체이기 때문에 method.return_value 를 설정하면 mock.method() 처럼 이용할 수 있다는 뜻이다.
이렇게 Mock
객체의 속성이나 메서드도 또 다른 Mock
객체가 된다는 점을 잘 활용하면 유연한 Mocking 이 가능해진다.