본문 바로가기
잡(job)기술/파이썬 공부

내 코드가 외부 API와 통신한 척하게 만드는 방법 - 파이썬 Mocking 핵심 개념 2가지

by 무니이구나 2026. 1. 2.

 

 

1. API에 의존적인 코드, 어떻게 테스트할까?

외부 API나 데이터베이스에 의존하는 코드는 테스트하기 어렵다. 실제 API를 호출하면 테스트 속도가 느려지고, 외부 서버 상태에 따라 테스트가 실패할 수 있다. API 호출 제한 때문에 테스트 자체가 막히는 경우도 있다. 이 문제를 해결하는 방법이 모킹(Mock)이다. 모킹은 실제 네트워크 요청 없이도 API와 통신한 것처럼 코드를 테스트하는 기술이다.

이 글에서는 파이썬 테스트에서 외부 의존성을 분리하는 데 꼭 필요한 MagicMockpatch 두 가지 핵심 개념을 설명한다.


2. 핵심 개념 1: 무엇이든 흉내 내는 객체, MagicMock

MagicMock은 어떤 객체든 대신할 수 있는 가짜 객체다. 존재하지 않는 속성이나 메서드에 접근해도 에러가 나지 않고, 또 다른 Mock 객체를 반환한다.

이 기능의 목적은 분명하다.

외부 라이브러리의 복잡한 객체 구조를 직접 만들지 않고,
테스트에 필요한 동작만 최소한으로 정의하기 위함이다.

 

예를 들어 requests.post()는 응답 객체를 반환한다. 이 응답 객체에는 status_code 속성과 json() 메서드가 있다. 실제 네트워크 요청 없이 이 동작만 흉내 내고 싶을 때 MagicMock을 사용한다.

예시: API 응답 객체 흉내 내기

from unittest.mock import MagicMock

mock_response = MagicMock()

# status_code 속성 흉내
mock_response.status_code = 200

# json() 메서드가 호출되면 가짜 데이터 반환
mock_response.json.return_value = {
    "key": "fake_data"
}

 

이 객체는 실제 requests.Response처럼 동작하지만,
네트워크 요청은 전혀 발생하지 않는다.


3. 핵심 개념 2: 실제 함수를 바꿔치기하는 기술, patch

patch는 테스트가 실행되는 동안만 실제 객체를 가짜 객체로 교체하는 기능이다. 앞에서 만든 MagicMock을 실제 코드 흐름에 투입하는 역할을 한다.

patch가 필요한 이유

  • 테스트 속도를 빠르게 만들기 위해
  • API 호출 제한을 피하기 위해
  • 외부 서버 상태로 인한 테스트 실패를 막기 위해

결론적으로 patch는 외부 의존성을 제거해 빠르고 안정적인 단위 테스트를 가능하게 한다.


4. 가장 많이 헷갈리는 부분: patch 경로는 어디를 지정해야 할까?

상황 설정: 실제 코드 구조

📁 프로젝트 구조

project/
 ├─ kiwoom_data.py
 └─ test_kiwoom_data.py

📄 kiwoom_data.py (테스트 대상 코드)

import requests

def fetch_daily_chart():
    response = requests.post("https://api.example.com")
    return response.json()

 

중요한 사실 하나만 기억하면 된다.

fetch_daily_chart 안에서 사용하는 것은
requests.post가 아니라 kiwoom_data.requests.post 이다.


❌ 잘못된 예: 왜 이 patch는 실패할까?

test_kiwoom_data.py

from unittest.mock import patch
from kiwoom_data import fetch_daily_chart

@patch("requests.post")   # ❌ 잘못된 patch
def test_fetch_daily_chart(mock_post):
    mock_post.return_value.json.return_value = {"ok": True}

    result = fetch_daily_chart()
    assert result == {"ok": True}

무슨 일이 벌어질까?

  • requests.post는 patch됨
  • 하지만 fetch_daily_chart는 이미 import requests를 통해 자기만의 requests 참조를 가지고 있음
  • 그래서 이 함수 안에서는 patch되지 않은 진짜 requests.post가 호출됨

결과

  • 실제 API 호출이 발생할 수 있음
  • “Mock을 썼는데 네트워크가 나간다”는 최악의 상황 발생

✅ 올바른 예: 실제로 동작하는 patch

test_kiwoom_data.py

from unittest.mock import patch
from kiwoom_data import fetch_daily_chart

@patch("kiwoom_data.requests.post")  # ✅ 올바른 patch
def test_fetch_daily_chart(mock_post):
    mock_post.return_value.json.return_value = {"ok": True}

    result = fetch_daily_chart()
    assert result == {"ok": True}

이번에는 왜 성공할까?

  • fetch_daily_chart가 실제로 참조하는 위치
  • 즉, kiwoom_data 모듈 안의 requests.post를 정확히 교체함
  • 함수 내부 호출이 100% Mock으로 연결됨

결과

  • 네트워크 요청 없음
  • 항상 동일한 테스트 결과
  • 빠르고 안정적인 단위 테스트

한 줄로 다시 정리하면

patch는 “어디서 import 되었는가”가 아니라
“어디에서 사용되고 있는가”를 기준으로 적용한다.


5. 실전 흐름으로 보는 Mocking 동작 과정

MagicMock과 patch는 보통 함께 사용된다. 전체 테스트 흐름을 단계별로 정리하면 다음과 같다.

1단계: Mock 설정

  • patch()로 requests.post를 가짜 함수로 교체한다.
  • 이 가짜 함수는 MagicMock 응답 객체를 반환한다.
  • 인증 토큰을 가져오는 함수도 더미 토큰을 반환하도록 패치한다.

2단계: 실제 로직 실행

  • 테스트 대상 함수 fetch_daily_chart()를 호출한다.
  • 함수 내부의 requests.post()는 실제 요청이 아닌 가짜 응답을 받는다.

3단계: 데이터 처리

  • .json() 호출 시 MagicMock에 설정된 가짜 데이터가 반환된다.
  • 코드는 이 데이터를 DataFrame으로 변환하고 DB 저장 로직을 수행한다.

4단계: 결과 검증

  • 반환된 DataFrame 값이 예상과 같은지 확인한다.
  • DB에 저장된 데이터 개수가 올바른지 assert로 검증한다.
assert df.iloc[0]["date"] == "20240105"

6. Mocking 구조를 한눈에 보는 도표

이 구조 덕분에 실제 네트워크 요청은 전혀 발생하지 않는다.


7. Mock과 Stub, 간단히 비교

구분 의미
Stub 정해진 값만 반환하는 단순한 대역
Mock 호출 여부·호출 횟수·행동까지 검증 가능한 대역

 

이 글에서 사용하는 방식은 Mock에 해당한다.


마무리: 격리된 테스트가 주는 힘

모킹을 사용하면 네트워크나 외부 서버 상태와 무관하게 테스트할 수 있다. 테스트는 빨라지고, 결과는 항상 일정해진다. 테스트에서 가장 중요한 것은 외부 환경이 아니라 내 코드의 로직이 올바른지다. 외부 API, 데이터베이스, 파일 시스템처럼 테스트하기 까다로운 부분이 있다면 모킹으로 분리하는 것부터 시작해보자.