진취적 삶
8 테스트 본문
테스트는 애플리케이션이 정상적으로 실행되도록 보장하고 프로덕션에 배포하기 전에 이상 징후를 감지할수 있게 해준다.
8.1 pytest를 사용한 단위 테스트
단위 테스트는 애플리케이션의 개별 컴포넌트를 테스트 하는 절차로 .개별 컴포넌트의 기능을 검증하기 위해 수행된다.
pytest를 통해 단위 테스트 수행
테스트 파일을 만들떄는 파일명 앞에 test_ 를 붙여서 해당 파일이 테스트 파일인지 확인시켜준다.
픽스처를 사용한 반복 제거
픽스처는 재사용할 수 있는 함수로, 테스트 함수에 필요한 데이터를 반환하기 위해 정의된다.
pytest.fixture 데코레이션을 사용해 픽스처를 정의할수 있다.
fixture 의 인수의 경우
module 과 session이 있다.
module : 테스트 파일이 실행된 후 특정함수에서만 유효
session : 테스트 전체 세션 동안 해당 함수가 유효
8.2 테스트 환경 구축
pytest.ini 설정 파일 만들기
pytest 실행될 때 pytest.ini 파일의 내용을 불러온다.
해당 설정은 pytest 가 모든 테스트를 비동기식으로 실행한다는 의미 .
Setting 클래스에서 새로운 데이터베이스 인스턴스를 만든다.
conftest
테스트 파일이 필요로 하는 애플리케이션의 인스턴스를 만든다.
- asyncio : 활성 루프 세션을 만들어서 테스트가 단일 스레드로 실행
- httpx : HTTP CRUD 처리를 실행하기 위한 비동기 클라이언트 역할
- pytest : 픽스처 정의를 위해 사용
import asyncio
import httpx
import pytest
from database.connection import Settings
from main import app
from models.events import Event
from models.users import User
# fixture 정의
@pytest.fixture(scope="session")
def event_loop():
loop = asyncio.get_event_loop()
yield loop
loop.close()
async def init_db():
test_settings = Settings()
test_settings.DATABASE_URL = "mongodb://localhost:27017/testdb"
await test_settings.initialize_database()
@pytest.fixture(scope="session")
async def default_client():
await init_db()
async with httpx.AsyncClient(app=app, base_url="<http://app>") as client:
yield client
# 리소스 정리
await Event.find_all().delete()
await User.find_all().delete()
mongo db 연결
- 데이터를 저장할 경로 설정설정
mongod --dbpath C:\\data\\db
- 클라이언트 실행
mongo //
8.3 REST API 라우트 테스트 작성
사용자 등록 라우트 테스트
import httpx
import pytest
# 비동기 테스트임을 명시
@pytest.mark.asyncio
async def test_sign_new_user ( default_client : httpx.AsyncClient)->None :
payload = {
"email": "testuser@packet.com",
"password": "testpassword",
}
# 요청 헤더와 응답 정의
headers = {
"accept": "application/json",
"Content-Type" : "application/json"
}
test_response = {
"message": "User created successfully"
}
# 요청에 대한 예상 응답 정의
response = await default_client.post("/user/signup", json=payload,headers=headers)
# 응답 비교
assert response.status_code ==200
assert response.json() ==test_response
@pytest.mark.asyncio
async def test_sign_user_in ( default_client : httpx.AsyncClient) ->None :
payload = {
"username": "testuser@packet.com",
"password": "testpassword",
}
headers = {
"accept" : "application/json",
"Content-Type" : "application/x-www-form-urlencoded"
}
# 요청에 대한 예상 응답 정의
response = await default_client.post("/user/signin",data=payload, headers=headers)
assert response.status_code==200
assert response.json()["token_type"] == "Bearer"
조회 라우트 테스트
import httpx
import pytest
from auth.jwt_handler import create_access_token
from models.events import Event
@pytest.fixture(scope="module")
async def access_token() -> str:
return create_access_token("testuser@packet.com")
@pytest.fixture(scope="module")
async def mock_event() -> Event:
new_event = Event(
creator="testuser@packet.com",
title = "FastAPI Book Launch",
image = "<https://linktomyimage.com/image.png>",
description="we will be discussing the contents of the FastAPI book in this event. Ensure to come with your own copy \\
to win gifts!",
tags=["python","fastapi","book","lunch"],
location="Google Meet"
)
await Event.insert_one(new_event)
yield new_event
@pytest.mark.asyncio
async def test_get_events (default_client:httpx.AsyncClient,mock_event:Event) ->None:
response = await default_client.get("/event/")
assert response.status_code ==200
assert response.json()[0]["_id"] ==str(mock_event.id)
@pytest.mark.asyncio
async def test_get_event (default_client:httpx.AsyncClient,mock_event:Event) ->None:
url = f"/event/{str(mock_event.id)}"
response = await default_client.get(url)
assert response.status_code ==200
assert response.json()["creator"] == mock_event.creator
assert response.json()["_id"] == str(mock_event.id)
생성 라우트 테스트
픽스처를 사용해 접속 토큰을 추출하고 테스트 함수를 정의한다.
요청 페이로드에는 콘텐츠 유형과 인증 헤더가 포함된다.
@pytest.mark.asyncio
async def test_post_event (default_client: httpx.AsyncClient,access_token:str) -> None:
payload = {
"title" : "FastAPI Book Launch",
"image" : "<https://linktomyimage.com/image.png>",
"description":"we will be discussing the contents of the FastAPI book in this event. Ensure to come with your own copy \\
to win gifts!",
"tags":["python","fastapi","book","lunch"],
"location":"Google Meet"
}
headers = {
"Content-Type" :"application/json",
"Authorization": f"Bearer {access_token}"
}
test_response = {
"message" : "Event created successfully"
}
response = await default_client.post("/event/new",json=payload,headers=headers)
assert response.status_code ==200
assert response.json() == test_response
이벤트 개수 확인
현재까지 2개
@pytest.mark.asyncio
async def test_get_events_count(default_client:httpx.AsyncClient) -> None :
response = await default_client.get("/event/")
events = response.json()
assert response.status_code==200
assert len(events) == 2
8.4 테스트 커버리지
pip install coverage
coverage run -m pytest
coverage report
html 파일 만들기
coverage html