unit test - mock

보람찬하루·2024년 10월 2일
1

1. Mock

  • Mock은 실제 객체 대신 가짜 객체를 사용해 테스트할 수 있는 도구.
  • 네트워크 요청, DB 연결 등 외부 종속성을 빠르게 처리할 수 있음.

2. Mock를 사용하는 이유

2.1 오래 걸리는 작업을 빠르게 처리

  • 문제: sleep, HTTP 요청 같은 작업이 실제 실행 시 시간이 오래 걸림.
  • 해결: Mock 처리로 빠르게 테스트 완료, 정확한 결과 확인 가능.
from unittest.mock import patch
@patch('time.sleep', return_value=None)  # 즉시 반환
def test_function(mock_sleep):
    time.sleep(10)  # 실제로 sleep하지 않음

2.2 외부 종속성으로부터 독립적인 테스트

  • 문제: 함수 A에 의존하는 함수 B를 테스트하려면 A의 동작에 영향받음.
  • 해결: A를 Mock으로 대체하여 B만 독립적으로 테스트 가능.
@patch('__main__.a', return_value="Mock A")
def test_b(mock_a):
    result = b()
    assert result == "Mock A and I'm B"

3. Mock의 사용

3.1 주요 활용 예시

  • 네트워크 요청: HTTP 요청을 Mock으로 대체해 가짜 응답 반환.
  • 시간 지연 작업: time.sleep()을 Mock 처리해 지연 없이 테스트.
  • DB 연동: 실제 DB에 연결하지 않고 가짜 응답을 사용.

3.2 Mock 객체 사용 단계

  1. 종속성 식별: 네트워크, 외부 함수, 시간 지연 등.
  2. Mock 처리: patch() 또는 MagicMock()으로 종속성 Mock 처리.
  3. 검증: Mock 호출 및 반환 값 검증.

3.3 Mock을 사용할 때 주의할 점

  • 과도한 Mock 사용: 모든 것을 Mock으로 처리하면 테스트가 실제 시스템 동작을 제대로 반영하지 못할 수 있습니다.
  • Mock이 테스트하는 코드와 너무 결합: Mock을 설정할 때 특정 내부 구현에 의존하게 되면, 실제 코드 변경 시 Mock 설정도 변경해야 할 수 있습니다
  • 실제 의존성을 테스트하지 않음 : Mock은 외부 종속성을 제거하는 데 유용하지만, 가짜 객체만을 테스트하면 실제 환경에서의 문제가 발견되지 않을 수 있습니다. 따라서 Mock을 사용하지 않은 통합 테스트도 함께 고려해야 합니다.
  • 속도 개선: Mock을 사용하면 오래 걸리는 작업을 빠르게 대체할 수 있지만, 실제 의존성을 완전히 제거해선 안 됩니다.

3.4 vs MagicMock

MagicMockMock의 확장 버전으로, 특별한 매직 메서드(__getitem__, __setitem__ 등)를 자동으로 처리하는 기능을 포함합니다.

from unittest.mock import MagicMock

mock = MagicMock()
mock[0] = "value"
assert mock[0] == "value"

4. Mock 객체의 주요 기능

4.1 메서드 및 속성 대체

Mock 객체는 메서드나 속성 호출을 대체할 수 있습니다. 이는 테스트 시 실제 객체를 호출하지 않도록 하고, 원하는 동작을 정의할 수 있습니다.

mock = Mock()

# 메서드 호출 시 원하는 값을 반환하도록 설정
mock.method.return_value = "mocked result"
assert mock.method() == "mocked result"

4.2 특정 예외 발생시키기

테스트 중 특정 상황에서 예외를 발생시키고 싶은 경우 side_effect 속성을 사용할 수 있습니다.

mock = Mock()
mock.method.side_effect = ValueError("Invalid value")
try:
    mock.method()
except ValueError as e:
    assert str(e) == "Invalid value"

4.3 호출 기록 추적

Mock 객체는 호출 기록을 자동으로 저장하므로, 테스트 중 어떤 메서드가 호출되었는지, 몇 번 호출되었는지 검증할 수 있습니다.

mock = Mock()
mock.method(1, 2, 3)

# 메서드가 호출된 적이 있는지 확인
mock.method.assert_called()

# 호출 시 특정 인자로 호출되었는지 확인
mock.method.assert_called_with(1, 2, 3)

# 호출 횟수 확인
assert mock.method.call_count == 1

5. Mock 객체의 속성

  • return_value: Mock된 함수나 메서드가 호출되었을 때 반환되는 값을 설정합니다.
  • side_effect: 함수 호출 시 예외를 발생시키거나 호출된 값에 따라 다른 동작을 정의할 수 있습니다.
  • call_count: Mock된 함수가 호출된 횟수를 반환합니다.
  • assert_called_with(): 특정 인자로 호출되었는지 확인할 수 있습니다.
  • assert_not_called(): 호출되지 않았는지 확인할 수 있습니다.

6. patch의 추가 활용

6.1 patch와 context manager

patch()는 컨텍스트 관리자로 사용할 수 있어, 특정 블록 내에서만 Mock 처리를 적용할 수 있습니다.

from unittest.mock import patch

def some_function():
    return time.time()

# 특정 함수에서만 time.time()을 Mock 처리
with patch('time.time', return_value=12345):
    assert some_function() == 12345

# 블록 밖에서는 다시 원래 함수가 동작
assert some_function() != 12345

6.2 클래스와 메서드 Mock

클래스 내부의 특정 메서드를 Mock 처리하는 것도 가능합니다.

class MyClass:
    def method(self):
        return "original result"

with patch.object(MyClass, 'method', return_value="mocked result"):
    obj = MyClass()
    assert obj.method() == "mocked result"
profile
를 만들어 가자

0개의 댓글