C.W.K.
Stream
Lesson 02 of 05 · published

mock 와 unittest.mock — 세계 가짜로

~18 min · mock, patch, monkeypatch, stub

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

왜 mocking 존재

테스트는 빠르고, deterministic 이고, 격리되어야. 진짜 HTTP API 치는 함수, 또는 파일 읽거나 DB 쿼리하는 함수 — 테스트 시점에 그것 다 X — 느리고, flaky 하고, 부작용 가득. Mocking 이 그 외부 의존성을 말한 거 반환하는 stand-in 으로 교체. 테스트되는 함수가 차이 모름.

unittest.mock — Mock 와 patch

Mock() 이 모든 호출 기록 + 어떤 속성 접근도 받는 객체 생성. patch("module.path") 가 테스트 동안 다른 모듈 네임스페이스의 뭔가 교체 (with-block, decorator, 또는 context manager). 조합이 mocking 필요의 90% 처리.

patch path — 사용된-모듈, 정의된-모듈 X

가장 흔한 mocking 버그 — 테스트 대상 코드가 from requests import get; get(...) 인데 patch("requests.get"). import 후 테스트 대상 네임스페이스의 함수가 별 참조, 원래 모듈 patching 이 영향 X. 해결 — patch("my_module.get") — 정의된 곳 X, lookup 되는 곳 patch.

monkeypatch — pytest 의 가벼운 대안

pytest 의 monkeypatch fixture 가 테스트 끝 자동 정리 + 비슷한 능력 제공. monkeypatch.setattr(module, "attr", value) 가 뭔가 swap. monkeypatch.setenv("VAR", "value") 가 env var 설정. 흔한 패턴 — pytest 테스트엔 더 깔끔한 mock.

War Story: Over-mocking 이 진짜 안티 패턴. 모든 거 mock 하는 테스트가 코드 대신 mock 테스트 끝남. Pippa 의 stack 이 경계에서 mock — 네트워크, LLM API, 시간 함수 — 모듈 간 내부 호출은 X. 선 — 프로세스 *간* mock, 코드 *내* 신뢰.

Code

Mock — 기본·python
from unittest.mock import Mock

# mock 생성 — 어떤 호출도 받음, Mock 반환
m = Mock()
m.do_something("foo", count=5)
m.other.deep.call()

# 일어난 거 검사
print(m.do_something.called)               # True
print(m.do_something.call_args)            # call('foo', count=5)
print(m.do_something.call_count)           # 1

# 반환값 설정
m.compute.return_value = 42
print(m.compute())                          # 42
print(m.compute("any", "args"))            # 42 — 인자 무시

# Side effect — raise 또는 순서 반환
m.flaky.side_effect = [1, 2, ValueError("oops")]
print(m.flaky())                           # 1
print(m.flaky())                           # 2
try:
    m.flaky()
except ValueError:
    print("세 번째에 raise")
patch — 다른 모듈에서 교체·python
# my_module.py
# import requests
# def fetch_user(uid):
#     resp = requests.get(f"https://api.example.com/{uid}")
#     return resp.json()

# test_my_module.py
from unittest.mock import patch, Mock

def test_fetch_user():
    fake_resp = Mock()
    fake_resp.json.return_value = {"name": "alice", "id": 42}

    # 사용된 곳 patch — my_module.requests, 그냥 requests X
    with patch("my_module.requests.get", return_value=fake_resp) as fake_get:
        # from my_module import fetch_user
        # result = fetch_user(42)
        # assert result == {"name": "alice", "id": 42}
        # fake_get.assert_called_once_with("https://api.example.com/42")
        pass
patch 데코레이터로 — 전체 테스트에 더 깔끔·python
from unittest.mock import patch, Mock

@patch("my_module.requests.get")
def test_with_decorator(mock_get):
    mock_get.return_value.json.return_value = {"x": 1}
    # ... my_module.fetch 호출 ...
    # ... mock_get.called assert ...

# 여러 patch 스택 — 파라미터 list 첫 번째가 가장 안쪽
@patch("my_module.B")
@patch("my_module.A")
def test_stacked(mock_A, mock_B):
    # mock_A 가 안의 @patch, 첫 list
    # mock_B 가 바깥 @patch, 두 번째 list
    pass
pytest 의 monkeypatch fixture·python
# pytest 안에서 patch 보다 깔끔한 대안

def test_with_monkeypatch(monkeypatch):
    # 속성 교체
    import my_module
    fake_get = lambda url: {"fake": True}
    monkeypatch.setattr(my_module, "fetch", fake_get)
    # ... 테스트 코드 ...
    # 테스트 끝 자동 정리

    # env var 설정 (자동 복원)
    monkeypatch.setenv("DEBUG", "true")
    # ... 테스트 코드 ...

    # 디렉토리 변경 (자동 복원)
    monkeypatch.chdir("/tmp")
호출 assert·python
from unittest.mock import Mock

m = Mock()
m.send("hello", priority=1)
m.send("world", priority=5)

# 다양한 assertion
m.send.assert_called()                  # 최소 한 번
m.send.assert_called_once()             # 정확히 한 번 — 두 번 호출되면 실패
m.send.assert_called_with("world", priority=5)   # 마지막 호출 매치
m.send.assert_any_call("hello", priority=1)      # 어떤 호출 매치

# 전체 history 검사
print(m.send.call_args_list)
# [call('hello', priority=1), call('world', priority=5)]

External links

Exercise

모듈 weather.pyget_weather(city) 함수 — requests import + requests.get(url).json() 호출. test_weather.pypatch("weather.requests.get") 으로 응답 mock, fake JSON dict 반환. 확인 — (a) 테스트가 네트워크 호출 안 함, (b) requests.get 에 넘긴 URL assert 가능, (c) side_effect 통해 requests.RequestException 시뮬레이트. patch 대신 monkeypatch 사용 두 번째 테스트 추가 + 비교.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.