테스트는 애플리케이션 개발 주기에서 빼놓을 수 없는 부분임
- 애플리케이션이 정상적으로 실행되도록 보장하고 프로덕션에 배포하기 전에 이상 징후를 감지할 수 있음
- 테스트 자동화 방법 (기존에는 수동으로 테스트함)
8장에서 다룰 핵심 내용은 다음과 같음
- pytest를 사용한 단위 테스트(Unit test)
- 테스트 환경 구축
- REST API 라우트 테스트 작성
- 테스트 커버리지
8.1 pytest를 사용한 단위 테스트
단위 테스트
- 애플리케이션의 개별 컴포넌트를 테스트하는 절차
- 개별 컴포넌트의 기능을 검증하기 위해 수행
파이썬 테스트 라이브러리 pytest 를 활용한 단위 테스트
- 파이썬은 unittest라는 내장 테스트 라이브러리를 제공함
- but ! pytest 가 더 간단한 구문을 사용할 수 있으므로 인기가 많음 !!
pytest 라이브러리 설치
$ pip install pytest
애플리케이션 테스트 파일을 한 곳에서 관리하기 위해 test라는 폴더를 만들고 파일을 생성함
$ mkdir tests && cd tests
$ touch __init__.py
테스트 파일을 만들 때는 파일명 앞에 'test_' 를 붙여야함
- 해당 파일이 테스트 파일이라는 것을 pytest 라이브러리가 인식해서 실행함
예시로, tests 폴더 아래 신규 테스트 파일을 하나 만들어본다
- 이 테스트 파일은 사친연산이 맞는지 확인함
$ touch test_arithmetic_operations.py
테스트 대상 함수를 만듬
"""
arithmetic_operations.py
"""
def add(a: int, b: int) -> int:
return a + b
def subtract(a: int, b: int) -> int:
return b - a
def multiply(a: int, b: int) -> int:
return a * b
def divide(a: int, b: int) -> int:
return b // a
테스트 함수를 만들어야함
- 테스트 함수는 계산 결과가 맞는지 검증하는 역할을 함
- assert 키워드는 식의 왼쪽에 있는 값이 오른쪽에 있는 처리 결과와 일치하는지 검증할 때 사용됨
"""
test_arithmetic_operations.py
"""
from arithmetic_operations import add,subtract,multiply,divide
def test_add() -> None:
assert add(1, 1) == 2
def test_substract() -> None:
assert subtract(2, 5) == 3
def test_multiply() -> None:
assert multiply(10, 10) == 100
def test_divide() -> None:
assert divide(25, 100) == 4
- 일반적으로 테스트 파일이 아닌 별도의 파일에 테스트 대상 함수를 정의함
- 이 파일(테스트 대상 함수)을 임포트하여 테스트를 수행함
$ pytest tests/test_arithmetic_operations.py
결과 화면
테스트 실패 예시
def test_add() -> None:
assert add(1, 1) == 11
테스트 실패 결과
결과값이 2가 아니라 11로 되어있기 때문에 assert 문에서 테스트가 실패함
- 실패 내용은 AssertionError에 요약되어 표시됨
- 2 == 11 이 아니기 때문에 실패했다고 알려줌
pytest가 어떻게 실행되는지 간단히 살펴봄
다음으로 pytest 의 픽서처(fixture)를 알아보도록 하자
8.1 -(1) 픽스처를 사용한 반복 제거
픽스처 (fixture)
- 재사용할 수 있는 함수
- 테스트 함수에 필요한 데이터를 반환하기 위해 정의됨
- pytest.fixture 데코레이터를 사용해 픽서처를 정의함
- 용도 : API 라우트 테스트 시 애플리케이션 인스턴스를 반환하는 경우 등에 사용됨
테스트 함수가 사용하는 애플리케이션 클라이언트를 픽스처로 정의할 수 있기 때문에 테스트할 때마다 애플리케이션 인스턴스를
다시 정의하지 않아도 된다 ... ? ?
- 이 부분은 <8.3 REST API 라우트 테스트 작성> 에서 다룬다
픽스처 정의 방법
- 픽스처를 어떻게 정의할까 ?
import pytest
from models.events import EventUpdate
# 픽스처 정의
@pytest.fixture
def event() -> EventUpdate:
return EventUpdate(
title="FastAPI Book Launch",
image="https://packt.com/fastapi.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", "launch"],
location="Google Meet"
)
def test_event_name(event: EventUpdate) -> None:
assert event.title == "FastAPI Book Launch"
< 코드 설명 >
- EventUpdate pydantic 모델의 인스턴스를 반환하는 픽서처를 정의함
- 이 픽시처는 test_event_name() 함수의 인수로 사용되며 이벤트 속성에 접근할 수 있음
- 픽스처 데코레이터는 인수를 선택적으로 받을 수 있음
- (예) scope 인수는 픽스처 함수의 유효 범위를 지정할 때 사용됨
- 여기서는 두가지 scope 를 사용함
- session : 테스트 전체 세션 동안 해당 함수가 유효하다.
- module : 테스트 파일이 실행된 후 특정 함수에서만 유효하다.
# 픽스처 정의
@pytest.fixture
def event(Scope parameter : (1) session or (2) module) -> EventUpdate:
return EventUpdate(
title="FastAPI Book Launch",
image="https://packt.com/fastapi.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", "launch"],
location="Google Meet"
)
8.2 테스트 환경 구축
CRUD 처리용 라우트와 사용자 인증을 테스트해보고자함
- 비동기 API를 테스트하려면 httpx와 pytest-asyncio 라이브러리를 설치해야함
$ pip install httpx pytest-asyncio
설치가 완료됐으면,
- pytest.ini 라는 설정 파일을 만들어야함
- 파일을 루트 폴더 (main.py가 있는 폴더)에 생성한 후 아래와 같이 코드를 추가함
"""
pytest.ini
"""
[pytest]
asyncio_mode = auto
pytest가 실행 될 때 이 파일의 내용을 불러옴
- 위와 같이 설정을 하는 이유 : pytest가 모든 테스트를 비동기식으로 실행한다는 의미임
설정 파일 준비가 끝났으니,
- tests 폴더 아래에 테스트 시작점이 될 conftest.py 파일을 만듬
- conftest.py 파일을 왜 만듬?
- 테스트 파일이 필요로 하는 애플리케이션의 인스턴스를 만듬
conftest.py 파일을 생성하고,
$ touch tests/conftest.py
conftest.py 파일에 의존 라이브러리를 임포트함
import asyncio
import httpx
import pytest
from main import app
from database.connection import Settings
from models.events import Event
from models.users import User
- asyncio, httpx, pytest 를 임포트함
- asyncio 모듈
- 활성 루프 세션을 만들어서 테스트가 단일 스레드로 실행되도록함
- httpx 테스트
- HTTP CRUD 처리를 실행하기 위한 비동기 클라이언트 역할을 함
- pytest 라이브러리
- 픽스처 정의를 위해 사용됨
- 애플리케이션 인스턴스(app) , Settings 클래스, 모델도 임포트
- asyncio 모듈
루프 세션 픽스처를 정의해보기
@pytest.fixture(scope="session")
def event_loop():
loop = asyncio.get_event_loop()
yield loop
loop.close()
Settings 클래스에서 새로운 데이터베이스 인스턴스를 만듬
"""
database > connection.py > Settings 클래스
"""
async def init_db():
test_settings = Settings()
test_settings.DATABASE_URL = "mongodb://localhost:27017/testdb"
await test_settings.initialize_database()
< 코드 설명 >
- DATABASE_URL과 <6장. 데이터베이스 연결> 에서 정의한 초기화 함수를 호출함
- testdb 라는 새로운 데이터베이스를 사용함
마지막으로,
기본 클라이언트 픽스처를 정의함
- 이 픽스처는 httpx를 통해 비동기로 실행되는 애플리케이션 인스턴스를 반환함
@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()
< 코드 설명 >
- 데이터베이스를 초기화한 후에 애플리케이션을 AsyncClient로 호출함
- AsyncClient 는 테스트 세션이 끝날 때까지 유지됨
- 테스트 세션이 끝나면 이벤트(Event)와 사용자(User) 컬렉션의 데이터를 모두 삭제하여
테스트를 실행할 때마다 데이터베이스가 비어있도록 함
-------- 지금까지 테스트 환경 구축 방법을 살펴봄 --------
8.3 REST API 라우트 테스트 작성
test_login.py 파일을 작성해서 인증 로직을 테스트 해보도록함
$ touch tests/test_login.py
1. 필요한 의존 라이브러리를 임포트함
import httpx
import pytest
8.3 - (1) 사용자 등록 라우트 테스트
첫번째 테스트 대상은 사용자 등록 라우트임
- pytest.mark.asyncio 데코레이터를 추가해서 비동기 테스트라는 것을 명시함
- 아래와 같이 테스트 함수와 요청 페이로드를 정의함
"""
tests/test_login.py
Method : test_sign_new_user
Param : default_client: httpx.AsyncClient
"""
import httpx
import pytest
@pytest.mark.asyncio
async def test_sign_new_user(default_client: httpx.AsyncClient) -> None:
payload = {
"email": "testuser@packt.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
몽고DB 서버가 실행되고 있는 상태에서 별도의 터미널 창을 열어 테스트를 실행함
$ pytest tests/test_login.py
- 파이썬 버전에 따라 import 문이 인식되지 않아서 실행되지 않을 수 있음 !
- 이 경우에는 명령어 앞에 python -m 을 붙여서 실행하면 해결됨 (이후 테스트 실행시에도 활용)
$ python -m pytest tests/test_login.py
사용자 등록 , 사용자 로그인 라우트 테스트가 성공한 화면 ( 그런데 1 error 인 loop 에러 발생 !! 이유는 알아보는중 .. )
- 테스트 응답을 변경해서 테스트가 실패했는지 확인해보는 것도 좋음 !
- 메일이 중복되면, 409 에러가 나오는 것을 확인함
'백엔드(FastAPI)' 카테고리의 다른 글
[백엔드 > FastAPI] 7장. 보안 (0) | 2024.01.20 |
---|---|
[백엔드 > FastAPI] 5장. 구조화 (0) | 2024.01.06 |
[백엔드 > FastAPI] 2장. 라우팅 (0) | 2023.12.23 |