mock을 이용한 requests의 ConnectionError 테스트하기

Ndic이라는 파이썬 패키지를 만들면서 테스트 코드의 coverage를 측정하기 위해 coveralls를 사용하고 있다. 패키지의 규모가 작았고, 모든 기능을 테스트하였다고 생각하였지만 coverage는 93%가 나오였다. 93%는 나쁘지 않은 수치이긴 하였지만, 나머지 7%에 대한 찝찝함과 100%를 달성해보고 싶은 욕심이 생겼다. Github에 간지나게 coverage 100% 뱃지도 달고 싶었다.

coveralls를 통해 확인해보니, 위의 두 줄의 코드가 coverage되지 않고 있었다.

이 코드는 만약 인터넷 연결이 되지 않았을 경우, requests에서 발생하는 예외를 처리하는 코드이다. 하지만 이를 테스트하기 위해 Python 코드로 인터넷 연결을 강제로 끊을 수도 없는 노릇이었다.

이렇게 coverage 100%는 불가능한건가하고 좌절에 빠져있을 때, 그 때 Python 테스트 시작하기라는 이호성님의 PyCon 발표영상을 보게되었고, mock이라는 훌륭한 테스트 라이브러리를 알게 되었다. 딱 지금 내가 처한 상황을 해결해 줄 구세주같은 라이브러리였다.

영상을 참고하여 아래와 같은 형태의 테스트 코드를 작성하였다.

@mock.patch.object(requests, 'get', side_effect=requests.ConnectionError)
def test_something_without_internet_network(self, mock_requests):  
    with self.assertRaises(ConnectionError) as cm:
        # a function using request.get()
        test_func(test_param)

side_effect라는 옵션을 이용하면 requests.get() 함수가 실행될 때 강제로 requests.ConnectionError 예외를 발생시킬 수 있다. 물론 이는 Decorator 아래의 함수에서만 적용되고, 다른 테스트 함수에서는 적용되지 않는다.

이렇게 mock을 이용하면 시스템이나 네트워크를 건드려야 되는 까다로운 테스트 작업들을 편리하게 구현할 수 있다.

들뜬 마음으로 tox를 실행해보았지만,

Python 2.6 버젼에서 실패하였다...

assertRaises()에 대한 문서를 보니 위와 같은 코드는 2.7 버젼 이상만 지원됨을 알 수 있었다.

Changed in version 2.7: Added the ability to use assertRaises() as a context manager.

그래서 어쩔 수 없이 조금 구리긴 하지만 아래와 같이 코드를 바꾸었다.

@mock.patch.object(requests, 'get', side_effect=requests.ConnectionError)
def test_something_without_internet_network(self, mock_requests):  
    self.assertRaises(
        ConnectionError,
        test_func,
        test_param,
    )

그 결과, 드디어 coverage 100% 뱃지를 얻을 수 있었다.