From ecc
pytest, TDD 방법론, fixtures, 모킹, 매개변수화 및 커버리지 요구 사항을 사용하는 Python 테스트 전략입니다.
npx claudepluginhub sam42-lab/everything-claude-code-krThis skill uses the workspace's default tool permissions.
pytest, TDD 방법론 및 모범 사례를 사용한 Python 애플리케이션을 위한 포괄적인 테스트 전략입니다.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
pytest, TDD 방법론 및 모범 사례를 사용한 Python 애플리케이션을 위한 포괄적인 테스트 전략입니다.
항상 TDD 사이클을 따르십시오:
# 1단계: 실패하는 테스트 작성 (RED)
def test_add_numbers():
result = add(2, 3)
assert result == 5
# 2단계: 최소한의 구현 작성 (GREEN)
def add(a, b):
return a + b
# 3단계: 필요시 리팩터링 (REFACTOR)
pytest --cov를 사용하십시오.pytest --cov=mypackage --cov-report=term-missing --cov-report=html
import pytest
def test_addition():
"""기본적인 덧셈 테스트."""
assert 2 + 2 == 4
def test_string_uppercase():
"""문자열 대문자 변환 테스트."""
text = "hello"
assert text.upper() == "HELLO"
def test_list_append():
"""리스트 추가 테스트."""
items = [1, 2, 3]
items.append(4)
assert 4 in items
assert len(items) == 4
# 일치 여부
assert result == expected
# 불일치 여부
assert result != unexpected
# 진리값 (Truthiness)
assert result # Truthy
assert not result # Falsy
assert result is True # 정확히 True
assert result is False # 정확히 False
assert result is None # 정확히 None
# 포함 여부
assert item in collection
assert item not in collection
# 비교
assert result > 0
assert 0 <= result <= 100
# 타입 확인
assert isinstance(result, str)
# 예외 테스트 (권장되는 방식)
with pytest.raises(ValueError):
raise ValueError("error message")
# 예외 메시지 확인
with pytest.raises(ValueError, match="invalid input"):
raise ValueError("invalid input provided")
# 예외 속성 확인
with pytest.raises(ValueError) as exc_info:
raise ValueError("error message")
assert str(exc_info.value) == "error message"
import pytest
@pytest.fixture
def sample_data():
"""샘플 데이터를 제공하는 fixture."""
return {"name": "Alice", "age": 30}
def test_sample_data(sample_data):
"""fixture를 사용하는 테스트."""
assert sample_data["name"] == "Alice"
assert sample_data["age"] == 30
@pytest.fixture
def database():
"""설정 및 정리 기능이 있는 fixture."""
# Setup (설정)
db = Database(":memory:")
db.create_tables()
db.insert_test_data()
yield db # 테스트에 제공
# Teardown (정리)
db.close()
def test_database_query(database):
"""데이터베이스 작업 테스트."""
result = database.query("SELECT * FROM users")
assert len(result) > 0
# 함수 스코프 (기본값) - 각 테스트마다 실행
@pytest.fixture
def temp_file():
with open("temp.txt", "w") as f:
yield f
os.remove("temp.txt")
# 모듈 스코프 - 모듈당 한 번 실행
@pytest.fixture(scope="module")
def module_db():
db = Database(":memory:")
db.create_tables()
yield db
db.close()
# 세션 스코프 - 전체 테스트 세션당 한 번 실행
@pytest.fixture(scope="session")
def shared_resource():
resource = ExpensiveResource()
yield resource
resource.cleanup()
@pytest.fixture(params=[1, 2, 3])
def number(request):
"""매개변수가 있는 fixture."""
return request.param
def test_numbers(number):
"""각 매개변수마다 한 번씩, 총 3번 실행됩니다."""
assert number > 0
@pytest.fixture
def user():
return User(id=1, name="Alice")
@pytest.fixture
def admin():
return User(id=2, name="Admin", role="admin")
def test_user_admin_interaction(user, admin):
"""여러 fixture를 사용하는 테스트."""
assert admin.can_manage(user)
@pytest.fixture(autouse=True)
def reset_config():
"""모든 테스트 전에 자동으로 실행됩니다."""
Config.reset()
yield
Config.cleanup()
def test_without_fixture_call():
# reset_config가 자동으로 실행됨
assert Config.get_setting("debug") is False
# tests/conftest.py
import pytest
@pytest.fixture
def client():
"""모든 테스트에서 공유되는 fixture."""
app = create_app(testing=True)
with app.test_client() as client:
yield client
@pytest.fixture
def auth_headers(client):
"""API 테스트를 위한 인증 헤더 생성."""
response = client.post("/api/login", json={
"username": "test",
"password": "test"
})
token = response.json["token"]
return {"Authorization": f"Bearer {token}"}
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
("PyThOn", "PYTHON"),
])
def test_uppercase(input, expected):
"""서로 다른 입력으로 3번 실행됩니다."""
assert input.upper() == expected
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300),
])
def test_add(a, b, expected):
"""여러 입력 조합으로 덧셈을 테스트합니다."""
assert add(a, b) == expected
@pytest.mark.parametrize("input,expected", [
("valid@email.com", True),
("invalid", False),
("@no-domain.com", False),
], ids=["valid-email", "missing-at", "missing-domain"])
def test_email_validation(input, expected):
"""읽기 좋은 테스트 ID와 함께 이메일 유효성을 검증합니다."""
assert is_valid_email(input) is expected
# 느린 테스트 표시
@pytest.mark.slow
def test_slow_operation():
time.sleep(5)
# 통합 테스트 표시
@pytest.mark.integration
def test_api_integration():
response = requests.get("https://api.example.com")
assert response.status_code == 200
# 단위 테스트 표시
@pytest.mark.unit
def test_unit_logic():
assert calculate(2, 3) == 5
# 느리지 않은 테스트만 실행
pytest -m "not slow"
# 통합 테스트만 실행
pytest -m integration
# 통합 또는 느린 테스트 실행
pytest -m "integration or slow"
# unit으로 표시되었지만 slow하지 않은 테스트 실행
pytest -m "unit and not slow"
from unittest.mock import patch, Mock
@patch("mypackage.external_api_call")
def test_with_mock(api_call_mock):
"""외부 API를 모킹하여 테스트합니다."""
api_call_mock.return_value = {"status": "success"}
result = my_function()
api_call_mock.assert_called_once()
assert result["status"] == "success"
@patch("mypackage.Database.connect")
def test_database_connection(connect_mock):
"""데이터베이스 연결을 모킹하여 테스트합니다."""
connect_mock.return_value = MockConnection()
db = Database()
db.connect()
connect_mock.assert_called_once_with("localhost")
@patch("mypackage.api_call")
def test_api_error_handling(api_call_mock):
"""모킹된 예외를 사용하여 오류 처리를 테스트합니다."""
api_call_mock.side_effect = ConnectionError("Network error")
with pytest.raises(ConnectionError):
api_call()
api_call_mock.assert_called_once()
@patch("builtins.open", new_callable=mock_open)
def test_file_reading(mock_file):
"""open을 모킹하여 파일 읽기를 테스트합니다."""
mock_file.return_value.read.return_value = "file content"
result = read_file("test.txt")
mock_file.assert_called_once_with("test.txt", "r")
assert result == "file content"
import pytest
@pytest.mark.asyncio
async def test_async_function():
"""비동기 함수를 테스트합니다."""
result = await async_add(2, 3)
assert result == 5
@pytest.mark.asyncio
async def test_async_with_fixture(async_client):
"""비동기 fixture와 함께 비동기 테스트를 수행합니다."""
response = await async_client.get("/api/users")
assert response.status_code == 200
import tempfile
import os
def test_file_processing():
"""임시 파일을 사용하여 파일 처리를 테스트합니다."""
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
f.write("test content")
temp_path = f.name
try:
result = process_file(temp_path)
assert result == "processed: test content"
finally:
os.unlink(temp_path)
def test_with_tmp_path(tmp_path):
"""pytest 내장 임시 경로 fixture를 사용하여 테스트합니다."""
test_file = tmp_path / "test.txt"
test_file.write_text("hello world")
result = process_file(str(test_file))
assert result == "hello world"
# tmp_path는 자동으로 정리됩니다.
tests/
├── conftest.py # 공유 fixtures
├── __init__.py
├── unit/ # 단위 테스트
│ ├── __init__.py
│ ├── test_models.py
│ ├── test_utils.py
│ └── test_services.py
├── integration/ # 통합 테스트
│ ├── __init__.py
│ ├── test_api.py
│ └── test_database.py
└── e2e/ # End-to-end 테스트
├── __init__.py
└── test_user_flow.py
test_user_login_with_invalid_credentials_failspytest.raises를 사용하십시오.@pytest.fixture
def client():
app = create_app(testing=True)
return app.test_client()
def test_get_user(client):
response = client.get("/api/users/1")
assert response.status_code == 200
assert response.json["id"] == 1
@pytest.fixture
def db_session():
"""테스트용 데이터베이스 세션을 생성합니다."""
session = Session(bind=engine)
session.begin_nested()
yield session
session.rollback()
session.close()
def test_create_user(db_session):
user = User(name="Alice", email="alice@example.com")
db_session.add(user)
db_session.commit()
retrieved = db_session.query(User).filter_by(name="Alice").first()
assert retrieved.email == "alice@example.com"
... (중략) ...
# 모든 테스트 실행
pytest
# 특정 파일 실행
pytest tests/test_utils.py
# 특정 테스트 실행
pytest tests/test_utils.py::test_function
# 상세 출력과 함께 실행
pytest -v
# 커버리지 보고서와 함께 실행
pytest --cov=mypackage --cov-report=html
# 빠른 테스트만 실행
pytest -m "not slow"
# 첫 번째 실패에서 중단
pytest -x
# 마지막으로 실패한 테스트부터 실행
pytest --lf
# 디버거와 함께 실행 (실패 시)
pytest --pdb
기억하십시오: 테스트도 코드입니다. 깔끔하고 읽기 쉬우며 유지보수가 가능하게 관리하십시오. 좋은 테스트는 버그를 찾아내고, 훌륭한 테스트는 버그를 예방합니다.