HTTP 통신 서버의 테스트 환경을 구축해보았다.
테스트 환경에는 pytest, pytest-asynctio, faker 를 사용했다.
설치모듈
pytest
The pytest framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries.
공식문서 홈페이지에서 부터 말하듯이 파이썬 기반의 간단하면서도 확장성있는 테스트 프레임워크다.
회사에서도 써본 적이 있어서 Learing curve가 낮았기 때문에 이 테스트 프레임워크를 선택했다.
pytest-asyncio
FastAPI는 비동기 기반의 프레임워크이기 때문에, 비동기 처리를 지원하는 pytest-asyncio를 함께 선택했다.
FastAPI 프레임워크를 사용할 때, pytest만 설치되어 있으면 테스트 실행 시 아래와 같은 오류가 발생한다.
PytestUnhandledCoroutineWarning: async def functions are not natively supported and have been skipped.
You need to install a suitable plugin for your async framework, for example:
- anyio
- pytest-asyncio
- pytest-tornasync
- pytest-trio
- pytest-twisted
warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid)))
Faker
Faker는 더미 데이터를 생성해주는 모듈이다.
다양한 성공과 실패 시나리오를 검증하기 위해 데이터 값을 다르게 하여 유효성 검사에 걸리는 등 여러 가지 형태의 데이터를 준비해야 하는데, 이걸 테스트코드 작성할 때마다 수기로 변경해주는 게 꽤나 귀찮은 작업이다.
이번 사이드 프로젝트에서는 최대한 귀찮은 작업들을 없애기 위해 Faker 모듈을 사용해보기로 마음 먹었다.
테스트 환경 설정
root 폴더에 pytest.ini 파일을 생성해준다.
[pytest]
minversion = 6.0
addopts = -ra
testpaths = ["app/tests"]
- minversion = 6.0
- 최소 6.0 이상의 pytest 버전을 지원한다.
- addopts = -ra
- addopts는 pytest 명령어 실행 시 추가적인 옵션을 지정할 때 사용된다.
- -ra: pytest 실행 후 테스트 요약사항을 출려해준다.( skip된 테스트, 실패한 테스트, 성공한 테스트 )
- testpaths = ["app/tests"]
- pytest가 테스트를 찾을 디렉토리를 지정하는 부분이다.
- 해당 디렉토리 내에서 pytest가 자동으로 test_로 시작하는 파일이나 *_test.py와 같은 파일을 찾아 테스트를 실행해준다. ( 나는 test_*.py로 픽했다. )
프로필 생성 API 통합 테스트코드 작성
성공적인 요청과 실패하는 요청을 처리하는 테스트 코드 작성을 해보았다.
Assignment
프로필 데이터는 닉네임(nickname)과 프로필 이미지(profile_image)를 받는다.
이때, 닉네임이 40자를 초과하면 데이터베이스의 VARCHAR(40) 제약으로 인해 오류가 발생하고, 프로필 이미지는 255글자를 초과하면 VARCHAR(255) 제약으로 인해 오류가 발생한다.
이러한 제약 사항을 바탕으로, API 통합 테스트에서 성공할 수 있는 더미 데이터와 실패할 수 있는 더미 데이터를 각각 준비했다.
def generate_profile_data(is_valid=True):
"""프로필 데이터를 만들어조눈 메소드."""
if is_valid:
return CreateProfileDTO(
user_id=1,
nickname=faker.name(),
profile_image=faker.image_url(),
)
else:
return CreateProfileDTO(
user_id=1,
nickname=faker.name(),
profile_image=faker.text(max_nb_chars=300),
)
@pytest.fixture(scope="function")
def valid_profile_data():
"""유효한 프로필 데이터"""
return generate_profile_data(is_valid=True)
@pytest.fixture(scope="function")
def invalid_profile_data():
"""유효하지 않은 프로필 데이터"""
return generate_profile_data(is_valid=False)
성공과 실패를 위한 더미 데이터 생성 로직이 동일하기 때문에, generate_profile_data()라는 메서드를 분리하여 구현했다. 이 메서드는 is_valid 파라미터로 True 또는 False 값을 받아, 성공하는 데이터를 생성할지 아니면 실패하는 데이터를 생성할지 분기처리할 수 있도록 했다.
Act
async def run_post_request(path: str, data):
async with httpx.AsyncClient() as client:
return await client.post(url=f"{base_url}{path}", json=data.model_dump())
데이터를 받아서 POST 메서드를 통해 API를 실행하는 메소드도 구현했다.
원래는 request 모듈을 사용해 API 실행 메서드를 구현하려 했으나, 동기적으로만 처리 가능한 request 모듈로는 FastAPI 환경에서 통합 API 테스트에 어려움이 있어, 대신 httpx 모듈을 사용했다.
Assert
# 성공 테스트
@pytest.mark.asyncio
async def test_create_profile_success(valid_profile_data):
res = await run_post_request(path="/profile", data=valid_profile_data)
assert res.status_code == 200
서버로그 👇🏻 👇🏻
pytest 로그 👇🏻 👇🏻
# 실패 테스트
# profile_image를 300자의 텍스트 더미데이터로 설정
@pytest.mark.asyncio
async def test_create_profile_failure(invalid_profile_data):
res = await run_post_request(path="/profile", data=invalid_profile_data)
assert res.status_code == 400
서버로그 👇🏻 👇🏻 ( 내가 커스텀한 에러로 raise한 것을 볼 수 있다. )
pytest 로그 👇🏻 👇🏻
'사이드프로젝트' 카테고리의 다른 글
[EggChatter] CodeRabbit을 활용한 PR 코드 리뷰 자동화 (0) | 2025.03.10 |
---|---|
[EggChatter] 테스트코드 실행문 스크립트 파일로 간소화하기 (0) | 2025.03.07 |
[EggChatter] DB 설계와 SQLAlchemy로 모델 정의 (0) | 2025.03.04 |
[ 기록의 정원 ] Error:Field validation for *** failed on the 'required' (0) | 2024.06.12 |
[ 포텐데이 X Ncloud ] 기록의 정원과 NCP 사용 후기 (2) | 2024.06.05 |