[Python] FastAPI와 Pytest를 사용한 API 개발 및 테스트 방법

Intro

안녕하세요 Noah입니다.
이 블로그 포스트에서는 Python으로 빠르고 효율적인 웹 API를 개발하기 위해 FastAPI를 사용하고, 단단한 코드를 작성하기 위해 테스트하는 방법에 대해 다룹니다.
FastAPI 프레임워크를 사용하여 간단한 API를 만들고, 이를 Pytest를 사용하여 테스트할 것입니다.



본문

1. Pytest 소개

Pytest는 Python에서 가장 인기 있는 테스트 프레임워크 중 하나입니다. 간단하고 사용하기 쉬우며 다양한 기능을 제공하여 테스트 코드를 작성하고 실행하는 데 도움을 줍니다.

Pytest의 주요 기능

  • 간단하고 직관적인 테스트 코드 작성
  • 다양한 테스트 유형 지원 (단위 테스트, 통합 테스트, 기능 테스트 등)
  • 명확하고 간결한 테스트 결과 출력
  • 테스트 코드 실행 자동화
  • 다양한 테스트 도구 및 라이브러리 지원



2. Pytest 설치 및 기본 설정

Pytest 설치:

pip install pytest



3. FastAPI를 사용한 API 개발

먼저 FastAPI를 사용하여 간단한 API를 개발해 보겠습니다.

Copy code
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello, World"}

위 코드는 / 엔드포인트에 대한 GET 요청에 대해 “Hello, World”를 반환하는 간단한 API를 정의합니다.

3. 테스트 코드 작성

이제 Pytest를 사용하여 위에서 작성한 API를 테스트하는 방법을 알아보겠습니다.

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_read_root():
    response = client.get("/")

위 코드에서 TestClient를 활용하면 FastAPI 서버를 실행하지 않았어도 ‘/’ 엔드포인트가 예상대로 동작하는지 테스트할 수 있습니다.
이를 활용해 제작한 모든 API에 대해 요청이 정상적으로 작동하는지 테스트할 수 있습니다.
test_read_root() 함수는 해당 엔드포인트에 GET 요청을 보내고, 반환된 상태 코드와 내용을 확인하여 테스트를 수행해야합니다.

4. 테스트 실행 및 결과 확인 방법

테스트 결과를 확인하는 로직에 대해 알아보겠습니다. 간단하게 코드로 작성을 해보면 크게 2가지로 확인 가능합니다. code를 통해 정상 통신한 내용인지 아니면 원하는 결과값이 나온 것인지를 통해 결과를 확인합니다.

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_read_root():
    # 클라이언트를 사용하여 '/' 엔드포인트에 GET 요청을 보냅니다.
    response = client.get("/")

    # 1. 반환된 상태 코드가 200인지 확인합니다.
    assert response.status_code == 200
    
    # 2. 반환된 JSON 응답 내용을 확인합니다. 이 때 예상한 JSON 형식과 일치하는지 확인합니다.
    assert response.json() == {"message": "Hello, World"}

위 코드에서 test_read_root() 함수는 다음과 같은 작업을 수행합니다.

  1. TestClient를 사용하여 FastAPI 애플리케이션의 ‘/’ 엔드포인트에 GET 요청을 보냅니다.
  2. 반환된 응답의 상태 코드가 200인지 확인합니다. 이는 요청이 성공적으로 처리되었음을 나타냅니다.
  3. 반환된 JSON 응답을 확인하여 예상한 형식과 일치하는지 확인합니다. 위 예제에서는 {“message”: “Hello, World”}와 일치하는지 확인합니다.
  4. 이러한 테스트 코드를 실행하면 FastAPI 애플리케이션이 예상대로 동작하는지 확인할 수 있습니다. 만약 API의 응답이 예상한 형식과 다르다면 테스트가 실패하게 됩니다. 이를 통해 개발자는 코드를 변경하거나 수정함으로써 원하는 동작을 보장할 수 있습니다.

5. 각 Router별로 테스트 코드 작성하기

아래는 각 라우터에 맞는 API를 테스트할 수 있도록 클래스로 구현하는 예제입니다. 라우터 종류로는 guest, user, product를 사용하겠습니다.

import pytest
from fastapi.testclient import TestClient
from main import app

class TestGuestAPI:
    client = TestClient(app)

    def test_guest_signup(self):
        # Guest 회원가입 API 테스트
        response = self.client.post("/guest/signup", json={"username": "testuser", "password": "testpassword"})
        assert response.status_code == 200
        assert response.json()["message"] == "Signup successful"

    def test_guest_login(self):
        # Guest 로그인 API 테스트
        response = self.client.post("/guest/login", data={"username": "testuser", "password": "testpassword"})
        assert response.status_code == 200
        assert "access_token" in response.json()

class TestUserAPI:
    client = TestClient(app)

    def test_user_profile(self):
        # User 프로필 조회 API 테스트
        response = self.client.get("/user/profile", headers={"Authorization": "Bearer ACCESS_TOKEN_HERE"})
        assert response.status_code == 200
        assert "username" in response.json()

    def test_user_update_profile(self):
        # User 프로필 업데이트 API 테스트
        response = self.client.put("/user/profile", headers={"Authorization": "Bearer ACCESS_TOKEN_HERE"}, json={"username": "updated_user"})
        assert response.status_code == 200
        assert response.json()["message"] == "Profile updated successfully"

class TestProductAPI:
    client = TestClient(app)

    def test_product_list(self):
        # 상품 목록 조회 API 테스트
        response = self.client.get("/product/list")
        assert response.status_code == 200
        assert len(response.json()) > 0

    def test_product_details(self):
        # 상품 상세 정보 조회 API 테스트
        response = self.client.get("/product/details/1")
        assert response.status_code == 200
        assert "name" in response.json()

    def test_product_create(self):
        # 상품 생성 API 테스트
        response = self.client.post("/product/create", json={"name": "New Product", "price": 100})
        assert response.status_code == 200
        assert response.json()["message"] == "Product created successfully"

이런 식으로 간단하게 각 Router별로 Class를 관리하는 방법이 존재합니다. 하지만 특정 로직이 제대로 동작하는지 확인하고 싶다면 아래와 같은 방법을 사용하기도 합니다.

아래는 test_product_create()를 사용하여 상품을 생성하고, 그 후에 생성한 상품의 이름과 같은 값이 조회되는지 확인하는 로직을 추가한 코드입니다.

import pytest
from fastapi.testclient import TestClient
from main import app

class TestProductAPI:
    client = TestClient(app)

    def test_product_create_and_retrieve(self):
        # 상품 생성 API 테스트
        create_response = self.client.post("/product/create", json={"name": "New Product", "price": 100})
        assert create_response.status_code == 200
        assert create_response.json()["message"] == "Product created successfully"

        # 생성한 상품의 이름과 같은 값을 가진 상품이 조회되는지 확인하는 API 테스트
        product_id = create_response.json()["product_id"]
        retrieve_response = self.client.get(f"/product/details/{product_id}")
        assert retrieve_response.status_code == 200
        assert retrieve_response.json()["name"] == "New Product"

위 코드는 test_product_create_and_retrieve() 메서드에서 다음을 수행합니다

  1. 먼저 test_product_create()를 사용하여 새 상품을 생성합니다.
  2. 생성된 상품의 ID를 반환하여 저장합니다.
  3. 해당 ID를 사용하여 상품 상세 정보를 조회하고, 반환된 상품의 이름이 “New Product”인지 확인합니다.

이렇게 하면 상품을 생성하고, 해당 상품이 성공적으로 조회되는지 테스트할 수 있습니다.



글을 마치며

FastAPI와 Pytest를 사용하여 API를 개발하고 테스트하는 것은 매우 간편하고 효율적입니다. FastAPI는 빠르고 간결한 코드를 작성할 수 있게 해주며, Pytest는 간단한 테스트 코드 작성과 함께 API를 효율적으로 테스트할 수 있도록 도와줍니다. 이를 통해 안정적이고 견고한 웹 애플리케이션을 개발할 수 있습니다.

이상으로 FastAPI와 Pytest를 사용한 API 개발 및 테스트에 대한 블로그 포스트를 마치겠습니다. 추가 질문이나 의견이 있으시면 언제든지 댓글을 남겨주세요!



  • 참고
    • FastAPI 공식 문서: https://fastapi.tiangolo.com/
    • Pytest 공식 문서: https://docs.pytest.org/en/7.0.x/
    • TestClient - FastAPI 공식 문서: https://fastapi.tiangolo.com/tutorial/testing/
    • FastAPI와 Pytest를 사용한 테스트 예제 - Real Python: https://realpython.com/fastapi-python-web-apis/#testing-the-api