- 모의은 API 클라이언트와 같은 실제 개체를 나타내므로 테스트가 오프라인으로 빠르게 실행됩니다.
- 고정된 답변의 경우
return_value을 사용하고 오류 또는 순서의 경우side_effect를 사용합니다. - 정의된 위치가 아니라 사용된 위치에 이름을 패치하세요. 이것은 가장 일반적인 버그입니다.
- 자동으로 전달하는 대신 오타와 잘못된 인수가 실패하도록
spec또는autospec를 사용하십시오.
함수가 날씨 API를 호출합니다. 테스트는 인터넷, API 키 또는 내일 날씨에 의존해서는 안 됩니다. Python의 내장 unittest.mock을 사용하면 해당 호출을 선택한 것을 정확하게 반환하는 대체 기능으로 바꿀 수 있습니다.
우리는 작은 weather 모듈과 9개의 테스트를 작성했으며 모두 Python 3.13의 단위 테스트로 실행됩니다. 9개 모두 통과했으며 아래의 인쇄된 출력물은 해당 실행에서 나온 것입니다.
왜 조롱합니까?
- 속도: 네트워크 호출이나 데이터베이스 설정이 없습니다.
- 신뢰성: 타사 서비스가 중단되어도 테스트가 실패하지 않습니다.
- 제어: 필요에 따라 시간 초과, 오류 및 이상한 데이터를 시뮬레이션할 수 있습니다.
- 격리: 다른 사람의 서버가 아닌 논리를 테스트합니다.
모의는 주로 단위 테스트에 속합니다. 통합 테스트는 여전히 실제 또는 현실적인 종속성과 대화해야 합니다.
모의 기본 사항
from unittest.mock import Mock
api = Mock()
api.get_price.return_value = 499
print(api.get_price("pen")) # 499
api.get_price.assert_called_once_with("pen")
print(api.get_price.call_args_list) # [call('pen')]
print(api.anything_at_all()) # <Mock name='mock.anything_at_all()' ...>
AMock은 모든 통화를 녹음하여 어떻게 사용되었는지 확인할 수 있습니다. 하지만 마지막 줄을 주목하세요. 일반 Mock은 사용자가 요청하는 모든 속성을 만들어냅니다. 앞으로 살펴보겠지만 그러한 유연성은 편리하면서도 위험하기도 합니다.
MagicMock은 동일한 아이디어이지만 __len__ 및 __iter__과 같은 “마법의” 방법도 지원합니다. patch()는 기본적으로 MagicMock을 사용합니다.
side_ effect: 오류 및 시퀀스
side_effect은 각 호출에서 발생하는 작업을 제어합니다. 실패를 시뮬레이션하려면 예외로 설정하십시오.
api.fetch.side_effect = ConnectionError("down")
api.fetch() # raises ConnectionError
연속 호출에 대해 다른 결과를 반환하려면 목록으로 설정하세요. 이는 재시도 논리를 테스트하는 데 적합합니다.
def fetch_with_retry(api, attempts=3):
for _ in range(attempts):
try:
return api.fetch()
except ConnectionError:
continue
raise ConnectionError("all attempts failed")
api = Mock()
api.fetch.side_effect = [ConnectionError("busy"), {"ok": True}]
print(fetch_with_retry(api)) # {'ok': True}
print(api.fetch.call_count) # 2
첫 번째 호출은 실패했고 두 번째 호출은 성공했으며 테스트를 통해 함수가 정확히 한 번 재시도되었음을 입증했습니다. 실제 서비스에 대해 이를 시뮬레이션하는 것은 거의 불가능합니다.
patch(): 일시적으로 무언가 교체
종속성은 전달되지 않는 경우가 많습니다. 모듈 내부로 가져옵니다. patch() 테스트 기간 동안 교체하고 나중에 복원합니다.
# weather.py
from http_client import get as requests_get
def report(city):
data = requests_get(f"https://api.example.com/weather?city={city}")
return f"{city}: {data['temp_c']}°C"
from unittest.mock import patch
with patch("weather.requests_get") as fake_get:
fake_get.return_value = {"temp_c": 31}
print(weather.report("Pune")) # Pune: 31°C
fake_get.assert_called_once_with(
"https://api.example.com/weather?city=Pune")
patch을 이와 같은 컨텍스트 관리자로 사용하거나 모의 객체를 인수로 전달하는 데코레이터로 사용할 수 있습니다.
정의된 곳이 아닌 사용되는 곳의 패치
이것은 거의 모든 사람이 한 번씩 저지르는 실수입니다. 실제 get 함수는 http_client에 있으므로 거기에 패치하는 것이 자연스럽게 느껴집니다.
with patch("http_client.get") as fake_get:
fake_get.return_value = {"temp_c": 99}
weather.report("Pune")
# ConnectionError: no network in tests: https://api.example.com/weather?city=Pune
실제 기능이 실행되었습니다. weather.py이 from http_client import get as requests_get를 수행했을 때 원래 기능을 가리키는 자체 이름을 만들었습니다. 패치 http_client.get은 다른 이름을 변경했기 때문에 weather는 모의를 본 적이 없습니다. 우리 http_client.get는 고의로 레이즈를 해서 실수가 컸습니다. 실제 프로젝트에서는 조용히 라이브 API를 호출할 수도 있습니다.
규칙: "module_under_test.name"와 같이 테스트 중인 모듈의 이름을 패치합니다. 모듈이 import http_client을 수행하고 http_client.get()를 호출하면 호출 시 조회가 발생하므로 패치 "http_client.get"가 올바른 것입니다.
spec 및 autospec: 모의 객체를 엄격하게 만듭니다.
일반 Mock은 오타를 포함한 모든 것을 허용합니다. 사양을 사용하면 실제 클래스에 있는 속성만 허용됩니다.
api = Mock(spec=weather.WeatherClient)
api.get_temprature("Pune")
# AttributeError: Mock object has no attribute 'get_temprature'
loose = Mock()
loose.get_temprature("Pune") # returns a Mock, no error at all
느슨한 모의 객체는 철자가 틀린 메서드를 기꺼이 받아들였기 때문에 이를 사용한 테스트는 실제 코드가 충돌하더라도 통과할 수 있었습니다. autospec은 더 나아가 메소드 서명을 확인합니다.
with patch("weather.WeatherClient", autospec=True) as Fake:
client = Fake.return_value
client.get_temperature("Pune", "extra", "args")
# TypeError: too many positional arguments
자신의 클래스를 모의할 때 autospec=True 또는 create_autospec()를 선호합니다. 실제 코드가 변경될 때 모의를 정직하게 유지합니다.
조롱 날짜 및 시간
오늘 날짜에 의존하는 코드는 테스트하기 어렵습니다. 시계를 작은 함수로 감싸서 다음과 같이 패치합니다.
def today():
return datetime.date.today()
def is_weekend():
return today().weekday() >= 5
@patch("weather.today")
def test_date(self, fake_today):
fake_today.return_value = datetime.date(2026, 11, 7)
print(weather.is_weekend()) # True (a Saturday)
내장 유형은 속성 설정을 허용하지 않기 때문에 datetime.date.today을 직접 패치하는 것은 쉽지 않습니다. Freezegun 및 Time-Machine과 같은 라이브러리는 이를 보다 완벽하게 처리합니다.
모의에 대한 주장
| 방법 | 수표 |
|---|---|
assert_called() |
한 번 이상 전화함 |
assert_called_once() |
정확히 한 번 호출됨 |
assert_called_with(...) |
마지막 호출에서는 다음 인수를 사용했습니다. |
assert_called_once_with(...) |
다음 인수를 사용하여 정확히 하나의 호출 |
assert_any_call(...) |
모든 호출에서 이러한 인수를 사용했습니다. |
assert_not_called() |
전화한 적 없음 |
어설션이 실패하면 메시지가 명확해집니다. 우리는 실행되지 않은 메서드에 대해 assert_called_once()을 호출했습니다.
AssertionError: Expected 'save' to have been called once. Called 0 times.
이전 Python 버전에서는 assret_called_once과 같은 철자가 틀린 어설션 메서드가 자동으로 전달되었습니다. 최신 버전에서는 assert로 시작하는 이름이나 일반적인 철자 오류에 대해 오류가 발생하지만 사양은 여전히 더 강력한 보호 장치입니다.
pytest와 모의
위의 모든 것은 pytest에서도 작동합니다. 많은 pytest 사용자는 또한 간단한 교체를 위해 내장된 monkeypatch 고정 장치나 패치를 자동으로 실행 취소하는 mocker 고정 장치를 제공하는 pytest-mock 플러그인을 사용합니다. “사용된 패치” 규칙은 모든 패치에 적용됩니다. 고정 장치 및 설정에 대해서는 pytest 튜토리얼을 참조하세요.
조롱하지 말아야 할 때
- 테스트 중인 것을 조롱하지 마십시오. 모의 테스트가 모든 작업을 수행한다면 테스트는 아무것도 증명하지 못합니다.
- 단순한 값 개체를 조롱하지 마세요. 실제 개체를 사용하세요.
- 모든 것을 모의하지 마십시오. 모의로 가득 찬 테스트는 코드가 리팩토링될 때마다, 심지어 동작이 올바른 경우에도 중단되는 경우가 많습니다.
- 실제 통합 테스트를 계속하세요. 모의는 서비스에 대한 가정을 인코딩하며 이러한 가정은 틀릴 수 있습니다.
또한 모의 경로는 실제 동작 없이 실행되기 때문에 코드 적용 범위를 쉽게 확장합니다.
일반적인 조롱 실수
- 잘못된 이름 패치. 위에서 다루었지만 여전히 “내 모의가 무시됩니다”의 가장 큰 원인입니다.
- return_value 설정을 잊어버렸습니다. 테스트 중인 코드는 데이터 대신 Mock 객체를 받습니다. 예를 들어 결과가 진실인지만 확인하는 경우에는 함수를 곧바로 통과하고 우연히 테스트를 통과할 수 있습니다.
- 결과 대신 모의 개체를 주장합니다.
save()가 호출되었는지 확인하는 것은 저장된 내용을 확인하는 것보다 약합니다. 실제 인수와 함께assert_called_once_with(...)을 사용하거나 더 나은 방법으로 함수 결과를 주장하십시오. - 패치 누출.
stop()없이patcher.start()를 호출하면 이후 테스트를 위해 가짜가 그대로 유지됩니다. 컨텍스트 관리자, 데코레이터 또는addCleanup(patcher.stop)를 사용하십시오. - 데코레이터가 잘못된 순서로 쌓였습니다. 여러
@patch개의 데코레이터가 있는 경우 맨 아래에 있는 것이 첫 번째 인수로 전달됩니다. 자동으로 혼합하면 잘못된 모의가 구성됩니다. - 많은 테스트에서 자신이 소유하지 않은 라이브러리를 모의합니다. 이를 자신만의 소규모 클래스로 감싸서 대신 조롱하세요. 그러면 테스트는 라이브러리의 API가 아닌 요구 사항을 설명합니다.
모의, 스텁 및 가짜
이러한 단어는 느슨하게 사용되는 경우가 많지만 테스트를 논의할 때 차이점이 도움이 됩니다. 스텁은 미리 준비된 답변만 반환합니다. 모의는 통화를 녹음하여 어떻게 사용되었는지 확인할 수도 있습니다. 가짜는 데이터베이스 대신 메모리 내 저장소와 같이 작동하고 단순화된 구현입니다. Python의 Mock는 스텁 또는 모의 역할을 할 수 있습니다. 가짜는 사용자가 직접 작성하는 일반적인 클래스이며 정확한 호출보다는 동작을 확인하기 때문에 리팩토링을 더 잘 견디는 테스트로 이어지는 경우가 많습니다.
경험상 좋은 규칙은 시스템의 가장자리(네트워크, 시계, 난수, 파일 시스템 및 유료 타사 서비스)를 모의하는 것입니다. 그 가장자리 안에는 실제 물체를 선호합니다. 이런 방식으로 구축된 테스트는 빠르게 유지되며 실제로 작성한 코드를 계속 실행합니다.
마무리
Mocking은 느리거나 예측할 수 없는 종속성을 대체하고, 반환되는 내용을 제어하고, 코드에서 종속성을 어떻게 사용했는지 확인하는 정밀한 도구입니다. 두 가지 습관이 대부분의 문제를 예방합니다. 테스트 중인 모듈의 이름을 패치하고 autospec을 사용하여 모의 개체가 실제 개체가 거부할 호출을 거부하도록 합니다.
자주 묻는 질문
Python에서 조롱이란 무엇입니까?
모의는 API 클라이언트나 데이터베이스와 같은 실제 객체를 테스트 중에 제어하는 가짜로 대체합니다. Python은 이를 unittest.mock 모듈에 포함합니다.
Mock과 MagicMock의 차이점은 무엇인가요?
MagicMock은 __len__, __iter__ 및 __enter__와 같은 매직 메서드도 지원하는 Mock입니다. patch()는 기본적으로 MagicMock을 생성합니다.
내 패치가 작동하지 않는 이유는 무엇입니까?
일반적으로 객체가 사용되는 위치 대신 객체가 정의된 위치를 패치했기 때문입니다. 테스트 중인 모듈 내부의 이름을 패치합니다.
return_value와 side_ effect의 차이점은 무엇입니까?
return_value는 고정된 결과를 설정합니다. side_ effect는 예외를 발생시키거나, 목록에서 순서대로 값을 반환하거나, 함수를 호출할 수 있습니다.
자동 사양은 무엇을 합니까?
실제 객체에서 모의 객체를 구축하므로 실제 코드에서와 마찬가지로 누락된 속성과 잘못된 인수로 인해 오류가 발생합니다.
계속 학습하세요
테스트, WordPress 또는 SEO에 도움이 필요하십니까?
우리는 팀이 더 나은 소프트웨어를 출시하고 사이트를 성장시킬 수 있도록 돕습니다. 현재 진행 중인 작업을 알려주시면 명확한 다음 단계를 제안해 드리겠습니다.
추가 가이드 테스트, AI 및 최신 개발
이 기사가 마음에 드셨나요? 주제를 선택하고 최신 튜토리얼로 바로 이동하세요.
AI 환각 설명: 언어 모델이 사물을 구성하는 이유와 이를 줄이는 방법
AI 환각은 그것이 무엇인지, 언어 모델이 환각을 생성하는 이유, 일반적인 유형, 접지 및 프롬프트를 통해 환각을 줄이는 방법, 그리고 우리가 실행한 간단한 점검에 대해 설명했습니다.
기사 읽기 →DevOps환경 변수 설명: .env 파일, 비밀 및 구성이 올바르게 완료되었습니다.
우리가 실행한 Python 예제를 통해 설명된 환경 변수: 읽기 및 설정, 모든 값이 문자열인 이유, .env 파일, 프로세스 상속, 비밀 및 유효성 검사.
기사 읽기 →소프트웨어 개발API 속도 제한 설명: 알고리즘, 헤더 및 429 오류 처리
우리가 실행한 시뮬레이션을 통해 설명된 API 속도 제한: 토큰 버킷, 고정 창, 슬라이딩 창, 429 응답, 속도 제한 헤더, 백오프를 사용한 재시도 및 테스트 제한.
기사 읽기 →테스트 자동화Python에서 조롱: unittest.mock, 패치 및 테스트를 거짓말로 만드는 실수
Unittest.mock을 사용하여 Python에서 모의, 테스트됨: 모의 및 return_value, 부작용, 이름이 사용되는 패치, 사양 및 자동 사양, 모의 날짜 및 일반적인 실수.
기사 읽기 →소프트웨어 테스트코드 커버리지 설명: 회선 커버리지와 분기 커버리지 및 100%의 실제 의미
실제 Coverage.py 실행을 통해 설명되는 코드 적용 범위: 라인, 분기 및 조건 적용 범위, 보고서 읽기, 100% 적용 범위가 여전히 버그를 놓칠 수 있는 이유 및 합리적인 대상.
기사 읽기 →데이터베이스 및 SQLSQL의 DELETE vs TRUNCATE vs DROP: 차이점, 롤백 및 사용 시기
테스트된 SQL을 통해 DELETE 대 TRUNCATE 대 DROP에 대해 설명합니다. 각각의 제거, 롤백, 트리거, ID 재설정, 속도 및 PostgreSQL, MySQL, SQL Server 및 SQLite의 차이점.
기사 읽기 →데이터베이스 및 SQLSQL 뷰 설명: 예제를 통해 함정 생성, 사용 및 방지
우리가 실행한 쿼리로 설명된 SQL 뷰: 뷰 생성, 실시간 결과, 열 숨기기, 성능, 업데이트 가능하고 구체화된 뷰, 손상된 종속성 및 모범 사례.
기사 읽기 →소프트웨어 개발TypeScript 인터페이스와 유형: 실제 차이점, 컴파일러로 테스트
TypeScript 인터페이스 대 유형, 실제 컴파일러로 확인: 둘 다 할 수 있는 것, 하나만 할 수 있는 것, 선언 병합, 확장 대 교차점 및 따라야 할 간단한 규칙.
기사 읽기 →소프트웨어 개발Python *args 및 **kwargs 설명: 명확한 예가 포함된 유연한 함수
Python *args 및 **kwargs는 우리가 실행한 코드(패킹 및 언패킹, 매개변수 순서, 키워드 전용 및 위치 전용 인수, 데코레이터 및 일반적인 오류)를 설명했습니다.
기사 읽기 →소프트웨어 개발Python Lambda 함수: 구문, 실제 사용 및 사용하지 말아야 할 경우
Python 람다 함수는 구문, 키를 사용한 정렬, 맵 및 필터, 클로저, 후기 바인딩 트랩, 일반 def가 더 나은 선택인 경우 등 우리가 실행한 코드로 설명되었습니다.
기사 읽기 →인공지능AI 에이전트란 무엇입니까? 작동 방식, 간단한 에이전트 루프를 사용하여 실행할 수 있음
AI 에이전트란 무엇입니까? 우리가 실행한 작은 Python 루프, 실제 위험 및 간단한 챗봇이 더 나은 선택일 때 에이전트 루프, 도구, 메모리 및 가드레일에 대한 일반 가이드입니다.
기사 읽기 →DevOps모니터링과 관찰 가능성: 로그, 지표, 추적을 통한 실제 차이점
모니터링과 관찰 가능성 설명: 알려진 오류와 알려지지 않은 오류, 로그, 지표 및 추적, 결제 사건 안내, 골든 신호, OpenTelemetry 및 유용한 경고.
기사 읽기 →DevOpsDocker 볼륨과 바인딩 마운트: 컨테이너 데이터를 올바른 방식으로 유지
Docker 볼륨과 바인드 마운트 설명: 컨테이너 데이터가 사라지는 이유, 명명된 볼륨, 바인드 마운트, tmpfs, Compose 예제, 백업, 권한 및 각 사용 시기.
기사 읽기 →테스트 자동화TDD와 BDD: 주요 차이점, 두 가지 방식으로 구축된 하나의 기능 및 각 사용 시기
TDD와 BDD는 두 가지 방식으로 구축된 하나의 할인 기능, 즉 우리가 실행한 코드를 사용한 적록 리팩토링, 주어진 시점 시나리오, 도구, 장단점 및 각 사용 시기를 설명합니다.
기사 읽기 →소프트웨어 개발Git merge vs rebase: 차이점, 예제, 언제 무엇을 쓸까
Git merge vs rebase를 실제 로그 출력, 비교표, rebase의 황금률, 안전한 팀 워크플로로 설명합니다.
기사 읽기 →소프트웨어 개발파이썬 리스트 컴프리헨션: 문법, 예제, 피해야 할 때
파이썬 리스트 컴프리헨션 설명: 문법, 필터링, if-else, 중첩 반복문, 실제 측정 시간, 그리고 일반 for 반복문이 더 나은 경우.
기사 읽기 →데이터베이스 및 SQL예제로 쉽게 배우는 SQL JOIN (Inner, Left, Right, Full)
샘플 테이블과 쿼리로 SQL JOIN을 단계별로 이해하세요. INNER, LEFT, RIGHT, FULL, CROSS, SELF와 흔한 실수, 성능 팁까지 정리했습니다.
기사 읽기 →