Home Django 테스트에 mocking 적용하기
Post
Cancel

Django 테스트에 mocking 적용하기

개요

프로젝트의 유닛 테스트를 진행하면서 여러 비효율적인 상황을 볼 수 있다.

여기 어떤 기능을 실행하는데 오래 걸리는 전처리 작업이 있다.
이 기능은 사용자의 요청이 발생하면 A -> B -> C 순서로 작업을 진행하는데, 문제는 A, B 작업이 너무 오래 걸리는 작업이라는 점이다.
테스트에서 A, B, C를 각각 테스트하고 싶지만 C를 실행할 때 테스트 시간이 너무 오래 걸리는 문제가 있다.

여기 또 다른 기능으로 외부 API 호출이 있다.
이 기능은 위 상황처럼 오래 걸리지는 않지만, 하루에 호출할 수 있는 제한량이 있다. 문제는 해당 기능이 수십개의 다른 기능에 전처리 되는 호출이라, 직원들이 개발을 하다 테스트를 돌릴 때마다 해당 API가 호출되는 애로사항이 있다.

이런 상황은 비단 대형 프로젝트에서만 발생하는 것은 아니다.

가장 친숙한 예시는 외부 로그인 기능인 OAuth일 것이다.

일반적으로 OAuth 기능은 프로젝트의 모든 기능에 우선적으로 실행될 것이고, 테스트 코드 수십개를 실행하면 한번에 수십개의 요청이 구글, 페이스북, 카카오로 날아갈 것이다.
그럼 당연히 요청 제한을 먹고 테스트는 실패하는 결과만 남아있다.

이런 상황을 해결해 줄 기능이 바로 mocking이다.

Mocking 이란

Mock은 개발한 제품을 테스트하는 과정에서 실제 모듈을 흉내내는 “가짜”를 작성하여 테스트의 효율성을 높이기 위한 개념이다.
이 가짜 모듈은 외부 API만 해당하는 것이 아니라, 현재 테스트하려는 모듈과 연결된 다른 모듈도 포함한다.

파이썬 unittest의 mock

파이썬 3.3부터 기본으로 제공하는 unittest.mock이라는 모킹 라이브러리가 있다.

이 라이브러리를 통해서 기존 코드에 영향을 주지 않고 특정한 부분을 가짜로 대체할 수 있다.

1
from unittest import mock

테스트 메소드 mocking

test-structure 예시 프로젝트 구조

1
2
3
4
5
6
from unittest import mock
...
@mock.patch("lib.csv_data.requests.get")
def test_function_mocking(self, mock_obj):
    # 모킹 테스트
...

먼저 unittest.mock라이브러리의 patch 함수를 가져왔다.
여기서 patch 함수를 데코레이터로 사용하여, mocking 경로를 제공해주었다.

여기서 중요한 것은, 함수에 제공되는 경로는 mocking 하고자 하는 대상이 있는 경로 라는 점이다.

위 코드에서 모킹 하려는 대상은 requests.get 코드이고, 이는 lib/csv_data.py 파일에 있다.

모킹하려는 메소드의 실제 위치가 아닌, 그 메소드를 실제 호출하는 위치라는 점을 기억하면 된다.

mocking 반환값 할당하기

1
2
3
4
5
6
from unittest import mock
...
@mock.patch("lib.csv_data.requests.get")
def test_function_mocking(self, mock_obj):
    # 모킹 테스트
...

위 코드에서 테스트 메소드의 argementmock_obj가 추가된 것을 볼 수 있다.
이는 mock 객체를 의미하며 변수명은 상관없다. 여기서는 보기 편하게 mock_obj로 설정하였다.

이 객체의 return_value를 이용하여 어떤 결과값을 모킹할지 선언하여야 한다. 이 때 반환되는 객체의 method, property에 따라 코드가 다르다.

Mock 객체의 method인 경우

1
2
3
4
mock_obj.return_value.json.return_value = {
    "PID": [113,241,323,124,105],
    "Cost": [10.5, 6.3, 2.6, 1.1, 9.7]
}

Mock 객체의 property인 경우

1
mock_obj.return_value.status_code = "200"

추가: mocking 반환값에 다른 객체 선언

다른 방법으로는 mock_obj.return_value에 모킹한 기능의 원래 반환했어야 할 객체를 할당하는 방법도 있다.

1
2
3
4
5
6
7
8
...
fake_res = requests.models.Response()
fake_res.status_code = 200
fake_res.headers["Content-Type"] = "application/json"
fake_res.headers["Content-Length"] = "100"
...

mock_obj.return_value = fake_res

그 객체가 생성자를 가지고 있었다면 손쉽게 나타낼 수도 있지만, requests는 해당되지 않는 것 같다.

기본적인 mock 객체 할당법이 메모리도 더 적게 소모하고 편하므로 그쪽을 쓰도록 하자.

This post is licensed under CC BY 4.0 by the author.

DRF에서 테스트 코드 작성하기(3) - factory boy

DRF에서 커스텀 로그인 & 사용자 모델 작성하기

Trending Tags