파이썬 단위 테스트를 더 잘 작성하기 위한 팁

파이썬 개발자로서 코드 단위 테스트는 가장 좋은 습관 중 하나입니다. 초기에 버그를 잡고, 디버깅을 쉽게 하며, 기존 코드를 망가뜨리지 않고 변경할 수 있는 자신감을 줍니다.
하지만 모든 테스트가 동등하게 좋은 것은 아닙니다! 테스트가 지저분하거나, 느리거나, 너무 많으면 별로 도움이 되지 않습니다.
이 글에서는 더 나은 단위 테스트를 작성하기 위한 실용적인 팁들을 간단하고 유용한 예제와 함께 알아보겠습니다.
1. 테스트 코드를 명확하게 구성하기
테스트를 잘 정리하면 유지보수가 훨씬 쉬워집니다. 소스 코드 구조를 그대로 따르되 테스트는 별도의 tests 폴더에 보관하는 것이 좋은 방법입니다.
다음은 사용할 수 있는 디렉토리 구조 예시입니다:
my_project/
│
├── src/
│ ├── utils.py
│ ├── app.py
│
├── tests/
├── test_utils.py
├── test_app.py
이런 구조는 관련 소스 코드 파일에 대한 테스트 파일을 빠르게 찾는 데 도움이 됩니다.
2. 설명적인 테스트 이름 사용하기
테스트 이름을 지을 때는 무엇을 테스트하는지 명확하게 설명해야 합니다. 이는 여러분과 팀원들이 테스트의 목적을 쉽게 파악하는 데 도움이 됩니다.
좋지 않은 예:
python
def test_function():
assert somefunction() == expectedresult
더 나은 버전:
python
def testadditionwithpositivenumbers():
assert addition(2, 3) == 5
테스트 이름이 설명적이면 필요에 따라 테스트를 쉽게 찾고 수정할 수 있습니다.
3. 하나의 테스트에서 한 가지만 검사하기
각 테스트는 단일 동작만 평가해야 합니다. 하나의 함수에서 여러 항목을 테스트하면 실패 원인이 명확하지 않을 수 있습니다. 테스트 항목을 분리하면 명확성이 보장되고 디버깅이 간단해집니다.
문자열에 알파벳 문자만 포함되어 있는지 확인하는 함수를 테스트해 봅시다:
python
src/utils.py
def is_alpha(string):
return string.isalpha()
단위 테스트는 다음과 같습니다:
python
tests/test_utils.py
def testisalphawithall_letters():
assert is_alpha("hello") is True
def testisalphawithnumbers():
assert is_alpha("hello123") is False
def testisalphawithspecial_characters():
assert is_alpha("hello!") is False
이러한 케이스를 하나의 테스트로 결합하는 대신 분리하면 문제를 빠르게 찾을 수 있습니다. 예를 들어 특수 문자에 대한 테스트가 실패한다면, 모든 케이스를 살펴볼 필요 없이 문제를 파악할 수 있습니다.
4. 모킹을 사용하여 의존성 분리하기
코드가 API, 데이터베이스 또는 다른 외부 시스템에 의존하는 경우, 모킹을 사용하여 테스트 중인 코드를 분리하세요.
실제 API 호출 없이 API 클라이언트 함수를 테스트해 봅시다:
python
src/api_client.py
import requests
def fetch_data(url):
response = requests.get(url)
return response.json()
테스트 코드는 다음과 같습니다:
python
tests/testapiclient.py
from unittest.mock import patch
from src.apiclient import fetchdata
@patch("src.api_client.requests.get")
def testfetchdata(mock_get):
# API 응답 시뮬레이션
mockget.returnvalue.json.return_value = {"key": "value"}
result = fetch_data("http://example.com/api")
assert result == {"key": "value"}
mockget.assertcalledoncewith("http://example.com/api")
모킹을 사용하면 실제 네트워크 호출에 의존하지 않고 코드를 테스트할 수 있어 테스트가 더 빠르고 안정적입니다.
5. 엣지 케이스와 오류 처리 포함하기
좋은 테스트는 코드가 제대로 작동하는지 확인할 뿐만 아니라 문제가 발생했을 때 어떻게 동작하는지도 확인합니다. 엣지 케이스를 포함하고 필요할 때 예외가 발생하는지 확인해야 합니다.
숫자를 나누는 함수를 테스트해 봅시다:
python
src/utils.py
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero!")
return a / b
테스트 코드:
python
tests/test_utils.py
import pytest
from src.utils import divide
def testdividenormal_case():
assert divide(10, 2) == 5
def testdividebyzeroraises_error():
with pytest.raises(ValueError, match="Cannot divide by zero!"):
divide(10, 0)
엣지 케이스와 발생 가능한 예외를 테스트하면 잘못된 입력이 주어졌을 때 코드가 예측 가능하게 동작하도록 보장합니다.
6. 파라미터화된 테스트로 반복 줄이기
동일한 함수를 다양한 입력으로 테스트할 때는 파라미터화된 테스트를 사용하여 시간을 절약하고 반복을 줄이세요.
예를 들어:
python
tests/test_utils.py
import pytest
from src.utils import square
@pytest.mark.parametrize("input,expected", [
(2, 4),
(0, 0),
(-3, 9),
(1.5, 2.25),
])
def test_square(input, expected):
assert square(input) == expected
이 접근 방식은 테스트를 깔끔하게 유지하고 모든 케이스가 테스트되도록 보장합니다.
7. 과도하지 않게 높은 코드 커버리지 목표 설정하기
높은 테스트 커버리지를 목표로 하는 것은 좋지만, 100% 커버리지가 코드에 버그가 없다는 의미는 아닙니다. 애플리케이션의 중요한 부분, 특히 엣지 케이스와 복잡한 로직에 집중하세요.
pytest-cov와 같은 도구를 사용하여 커버리지를 측정하세요:
pip install pytest-cov
pytest --cov=src tests/
커버리지 보고서를 확인하여 테스트의 공백을 발견하되, 모든 코드 라인을 커버하는 것에 너무 스트레스받지 마세요.
마무리
좋은 단위 테스트는 단순히 코드를 테스트하는 코드를 작성하는 것이 아니라, 애플리케이션이 어떻게 동작하고 예상대로 작동하는지 신중하게 생각하는 것입니다.
작은 것부터 시작하여 명확하고 간단한 테스트에 집중하고, 프로젝트가 성장함에 따라 강력한 테스트 세트를 점진적으로 구축하세요.
행복한 테스팅 되세요! 🚀