pytest 란?:
- 파이썬에서 가장 널리 사용되는 테스트 프레임워크 중 하나
- 특징:
- 간단한 문법으로 쉽게 시작 가능
- 테스트 자동 검색(Discovery) 기능: 특별한 설정 없이도 test_*.py 혹은 *_test.py 형식의 파일을 자동으로 찾음
- 고급 기능: Fixtures, parameterized tests 등
- 파이썬 표준 라이브러리에서 제공하는 unittest 모듈보다 더 직관적이고 유연하다는 평가를 받습니다.
설치 및 기본 실행:
# 설치
pip install pytest
pytest 디렉토리 구조 예시:
- 테스트 코드는 tests/ 디렉터리에 모아두는 것이 일반적
- 파일 이름은 test_*.py 형태로 하면 pytest가 자동으로 찾아줌
my_project/
├── app/
│ ├── __init__.py
│ └── main.py
├── tests/
│ ├── __init__.py
│ └── test_main.py
└── requirements.txt
테스트 실행:
- 별도의 인수가 없으면 현재 디렉터리와 하위 디렉터리를 탐색하여 test_*.py 파일을 찾아 실행합니다.
# 프로젝트 루트 디렉터리에서
pytest
가장 기본적인 테스트 작성 예시:
- 테스트 함수 명 규칙: test_로 시작해야 함
- Assertion: assert 구문 사용. assert <조건>, <에러메시지(optional)>
- 파이썬에서는 unittest처럼 self.assertEqual(…) 등을 안 써도 되고, 기본 제공되는 assert문을 써도 충분히 자세한 에러 메시지를 볼 수 있습니
다.# tests/test_main.py def test_sum(): assert 1 + 2 == 3 def test_uppercase(): assert "hello".upper() == "HELLO"
테스트 구조화: 클래스나 모듈별로 나누기:
- 규모가 커지면 테스트를 클래스 단위로 구분하기도 합니다.
- 클래스 이름은 Test로 시작하도록 규칙을 정해 두면 가독성이 높습니다.
- 클래스 내부에서도 함수 이름을 test_로 시작해야 pytest가 테스트 함수로 인식합니다.
# tests/test_user.py
class TestUserCreation:
def test_create_user_success(self):
user = create_user("Alice")
assert user.name == "Alice"
def test_create_user_failure(self):
# 예: 중복된 이름일 때
with pytest.raises(ValueError):
create_user("Alice") # 이미 있는 유저라 실패
예외 상황 테스트:
- 테스트하다 보면 특정 함수가 예외를 던지는지 여부를 검증해야 할 때가 있습니다. pytest는 pytest.raises를 통해 예외 상황을 테스트할 수 있습니다.
def test_raise_exception():
with pytest.raises(ValueError) as exc_info:
raise ValueError("Custom error")
assert "Custom error" in str(exc_info.value)
@patch('requests.get')
def test_resource_cleanup(self, mock_get):
mock_get.side_effect = requests.RequestException
with pytest.raises(requests.RequestException):
crawler.extract(test_url)
# 리소스가 적절히 정리되었는지 확인
assert crawler._is_cleaned_up()
Mocking (단위 테스트를 위한 의존성 제어):
- 다른 언어에서도 테스트 시 의존성을 제거하거나, 외부 API를 가짜로 대체하기 위해 mocking을 자주 사용합니다. 파이썬에서도 unittest.mock 라이브러리를 자주 쓰며, pytest는 이를 매끄럽게 연동합니다.
from unittest.mock import patch
def test_third_party_call():
with patch("app.main.some_third_party_function") as mock_func:
mock_func.return_value = "mocked!"
result = do_something() # 내부적으로 some_third_party_function이 호출됨
assert result == "mocked!"
mock_func.assert_called_once()
Coverage(테스트 커버리지) 확인하기:
- 프로젝트에서 테스트 커버리지를 확인하고 싶다면 coverage 패키지와 연동해서 사용할 수 있습니다.
# 설치
pip install coverage
# 실행
coverage run -m pytest
coverage report -m
# 혹은
coverage html # HTML 리포트 생성
자주 쓰는 플러그인
- pytest-django: Django 프로젝트 테스트 지원
- pytest-flask: Flask, FastAPI 등에서도 유사하게 사용할 수 있는 플러그인 존재
- pytest-cov: coverage와의 통합, --cov 옵션을 제공
- pytest-xdist: 테스트를 멀티프로세싱으로 병렬 실행
@pytest.fixture
테스트할 때 초기화 작업(예: DB 연결, 임시 파일 생성 등)이 필요하거나, 테스트가 끝난 뒤 정리 작업이 필요할 수 있습니다. pytest에서는 이를 손쉽게 처리하기 위해 Fixture라는 기능을 제공합니다.
기본 사용법:
class TestFmKoreaCrawler:
@pytest.fixture
def crawler(self):
return FmKoreaCrawler()
def test_extract_title(self, crawler): # fixture를 매개변수로 자동 주입
result = crawler.extract_title()
assert result == "expected title"
설정과 정리:
@pytest.fixture
def database_connection():
# 설정 (Setup)
db = connect_to_db()
yield db # 테스트에서 사용할 객체 반환
# 정리 (Teardown)
db.close()
설정과 정리 - 데이터베이스 트랜잭션:
@pytest.fixture
def transaction():
# Setup
connection = create_connection()
transaction = connection.begin()
yield connection # 테스트에 연결 전달
# Teardown (항상 롤백)
transaction.rollback()
connection.close()
fixture 범위 지정:
@pytest.fixture(scope="function") # 각 테스트 함수마다 새로 생성
def new_crawler():
return FmKoreaCrawler()
@pytest.fixture(scope="class") # 테스트 클래스당 한 번만 생성
def shared_crawler():
return FmKoreaCrawler()
@pytest.fixture(scope="session") # 전체 테스트 세션에서 한 번만 생성
def config():
return load_config()
fixture 조합:
@pytest.fixture
def mock_response():
return """<html><body>Test</body></html>"""
@pytest.fixture
def crawler_with_mock(crawler, mock_response): # 다른 fixture 사용
crawler.response = mock_response
return crawler
매개변수화된 fixture:
@pytest.fixture(params=['success', 'error'])
def test_scenario(request):
return request.param
def test_crawler(test_scenario):
if test_scenario == 'success':
# 성공 케이스 테스트
pass
else:
# 에러 케이스 테스트
pass
Parametrizing: 한 번의 함수로 다양한 케이스 테스트
테스트 케이스가 여러 개인 상황(예: 여러 개의 입력 조합을 모두 테스트)에서, 비슷한 구조의 테스트 함수를 여러 번 복붙하고 싶지 않을 때 유용합니다.
import pytest
@pytest.mark.parametrize("input_val,expected", [
(1, 2),
(2, 4),
(3, 6),
])
def test_double(input_val, expected):
assert input_val * 2 == expected
vs fixture 에서 parmse=[]:
- 둘 다 테스트를 여러 입력 값으로 반복 실행한다는 점에서 유사하지만, 사용 목적과 적용 범위가 다릅니다
- Parametrizing 은 테스트 함수 레벨에서 직접 값을 제공함
- Parametrizing 은 다중 인자를 쉽게 파라미터화 가능
- 테스트 케이스별 의미를 명시할 수 있음.
'Programming Language > Python' 카테고리의 다른 글
제너레이터(generator) (0) | 2025.02.07 |
---|---|
Asyncio 와 Aiohttp 정리 (0) | 2024.08.14 |
Jupyter notebook 에서 argparse 를 사용할 때 발생하는 에러 (0) | 2024.08.07 |
yield 에 대해서 (0) | 2024.08.06 |
언패킹 (Unpacking) (0) | 2024.07.10 |