Lær å teste FastAPI-applikasjoner effektivt med TestClient. Dekker beste praksiser, avanserte teknikker og eksempler for robuste APIer.
Mestre FastAPI Testing: En Omfattende Veiledning til TestClient
FastAPI har etablert seg som et ledende rammeverk for å bygge høyytelses APIer med Python. Dens hastighet, brukervennlighet og automatisk datavalidering gjør den til en favoritt blant utviklere verden over. En godt bygget API er imidlertid bare så god som testene sine. Grundig testing sikrer at API-en din fungerer som forventet, forblir stabil under press, og trygt kan distribueres til produksjon. Denne omfattende veiledningen fokuserer på å bruke FastAPIs TestClient for effektivt å teste API-endepunktene dine.
Hvorfor er Testing Viktig for FastAPI-applikasjoner?
Testing er et avgjørende steg i livssyklusen for programvareutvikling. Det hjelper deg med å:
- Identifisere feil tidlig: Fang opp feil før de når produksjon, noe som sparer tid og ressurser.
- Sikre kodens kvalitet: Fremme velstrukturert og vedlikeholdsvennlig kode.
- Forhindre regresjoner: Garantere at nye endringer ikke bryter eksisterende funksjonalitet.
- Forbedre API-pålitelighet: Bygg tillit til API-ens stabilitet og ytelse.
- Tilrettelegge for samarbeid: Gi klar dokumentasjon av forventet oppførsel for andre utviklere.
Introduksjon til FastAPIs TestClient
FastAPI tilbyr en innebygd TestClient som forenkler prosessen med å teste API-endepunktene dine. TestClient fungerer som en lettvektsklient som kan sende forespørsler til API-en din uten å starte en fullverdig server. Dette gjør testing betydelig raskere og mer praktisk.
Viktige Funksjoner i TestClient:
- Simulerer HTTP-forespørsler: Lar deg sende GET, POST, PUT, DELETE og andre HTTP-forespørsler til API-en din.
- Håndterer data serialisering: Serialiserer automatisk forespørselsdata (f.eks. JSON-innhold) og deserialiserer responser.
- Tilbyr påstandsmetoder: Gir praktiske metoder for å verifisere statuskoden, headere og innhold i svarene.
- Støtter asynkron testing: Fungerer sømløst med FastAPIs asynkrone natur.
- Integreres med testrammeverk: Integreres enkelt med populære Python-testrammeverk som pytest og unittest.
Sette opp Testmiljøet Ditt
Før du begynner å teste, må du sette opp testmiljøet ditt. Dette innebærer vanligvis å installere nødvendige avhengigheter og konfigurere testrammeverket ditt.
Installasjon
Først, sørg for at du har FastAPI og pytest installert. Du kan installere dem ved hjelp av pip:
pip install fastapi pytest httpx
httpx er en HTTP-klient som FastAPI bruker i bakgrunnen. Selv om TestClient er en del av FastAPI, sikrer installasjon av httpx også jevn testing. Noen veiledninger nevner også requests, men httpx er mer i tråd med FastAPIs asynkrone natur.
Eksempel på FastAPI-applikasjon
La oss lage en enkel FastAPI-applikasjon som vi kan bruke til testing:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.get("/")
async def read_root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
@app.post("/items/")
async def create_item(item: Item):
return item
Lagre denne koden som main.py. Denne applikasjonen definerer tre endepunkter:
/: Et enkelt GET-endepunkt som returnerer en "Hello World"-melding./items/{item_id}: Et GET-endepunkt som returnerer et element basert på ID-en./items/: Et POST-endepunkt som oppretter et nytt element.
Skrive Din Første Test
Nå som du har en FastAPI-applikasjon, kan du begynne å skrive tester ved hjelp av TestClient. Opprett en ny fil kalt test_main.py i samme katalog som main.py.
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
I denne testen:
- Vi importerer
TestClientog FastAPIapp-instansen. - Vi oppretter en instans av
TestClientog sender innapp. - Vi definerer en testfunksjon
test_read_root. - Inne i testfunksjonen bruker vi
client.get("/")for å sende en GET-forespørsel til rotendepunktet. - Vi bekrefter at svarkoden er 200 (OK).
- Vi bekrefter at svarkodens JSON er lik
{"message": "Hello World"}.
Kjøre Testene Dine med pytest
For å kjøre testene dine, åpne bare en terminal i mappen som inneholder test_main.py-filen og kjør følgende kommando:
pytest
pytest vil automatisk oppdage og kjøre alle testene i prosjektet ditt. Du bør se utdata som ligner på dette:
============================= test session starts ===============================
platform darwin -- Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /path/to/your/project
collected 1 item
test_main.py .
============================== 1 passed in 0.01s ===============================
Testing av Ulike HTTP-metoder
TestClient støtter alle standard HTTP-metoder, inkludert GET, POST, PUT, DELETE og PATCH. La oss se hvordan vi tester hver av disse metodene.
Testing av GET-forespørsler
Vi så allerede et eksempel på testing av en GET-forespørsel i forrige avsnitt. Her er et annet eksempel, som tester /items/{item_id}-endepunktet:
def test_read_item():
response = client.get("/items/1?q=test")
assert response.status_code == 200
assert response.json() == {"item_id": 1, "q": "test"}
Denne testen sender en GET-forespørsel til /items/1 med en spørringsparameter q=test. Den bekrefter deretter at svarkoden er 200 og at svarkodens JSON inneholder forventede data.
Testing av POST-forespørsler
For å teste en POST-forespørsel, må du sende data i forespørselskroppen. TestClient serialiserer automatisk dataene til JSON.
def test_create_item():
item_data = {"name": "Example Item", "description": "A test item", "price": 9.99, "tax": 1.00}
response = client.post("/items/", json=item_data)
assert response.status_code == 200
assert response.json() == item_data
I denne testen:
- Vi oppretter en ordbok
item_datasom inneholder dataene for det nye elementet. - Vi bruker
client.post("/items/", json=item_data)for å sende en POST-forespørsel til/items/-endepunktet, og senderitem_datasom JSON-innhold. - Vi bekrefter at svarkoden er 200 og at svarkodens JSON samsvarer med
item_data.
Testing av PUT, DELETE og PATCH-forespørsler
Testing av PUT, DELETE og PATCH-forespørsler ligner på testing av POST-forespørsler. Du bruker rett og slett de tilsvarende metodene på TestClient:
def test_update_item():
item_data = {"name": "Updated Item", "description": "An updated test item", "price": 19.99, "tax": 2.00}
response = client.put("/items/1", json=item_data)
assert response.status_code == 200
# Legg til påstander for forventet respons
def test_delete_item():
response = client.delete("/items/1")
assert response.status_code == 200
# Legg til påstander for forventet respons
def test_patch_item():
item_data = {"price": 29.99}
response = client.patch("/items/1", json=item_data)
assert response.status_code == 200
# Legg til påstander for forventet respons
Husk å legge til påstander for å verifisere at svarene er som forventet.
Avanserte Testteknikker
TestClient tilbyr flere avanserte funksjoner som kan hjelpe deg med å skrive mer omfattende og effektive tester.
Testing med Avhengigheter
FastAPIs dependency injection-system lar deg enkelt injisere avhengigheter i API-endepunktene dine. Ved testing kan du ønske å overstyre disse avhengighetene for å tilby mock- eller testspesifikke implementasjoner.
For eksempel, anta at applikasjonen din avhenger av en databaseforbindelse. Du kan overstyre databaseavhengigheten i testene dine for å bruke en in-memory database:
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base, Session
# Databasekonfigurasjon
DATABASE_URL = "sqlite:///./test.db" # In-memory database for testing
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Definer Brukermodell
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
password = Column(String)
Base.metadata.create_all(bind=engine)
# FastAPI App
app = FastAPI()
# Avhengighet for å få databasedatabasen
def get_db():
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
# Endepunkt for å opprette en bruker
@app.post("/users/")
async def create_user(username: str, password: str, db: Session = Depends(get_db)):
db_user = User(username=username, password=password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
from fastapi.testclient import TestClient
from .main import app, get_db, Base, engine, TestingSessionLocal
client = TestClient(app)
# Overstyr databasedependencen for testing
def override_get_db():
try:
db = TestingSessionLocal()
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
def test_create_user():
# Først, sørg for at tabellene er opprettet, noe som kanskje ikke skjer som standard
Base.metadata.create_all(bind=engine) # viktig: opprett tabellene i testdb-en
response = client.post("/users/", params={"username": "testuser", "password": "password123"})
assert response.status_code == 200
assert response.json()["username"] == "testuser"
# Rydd opp overstyringen etter testen om nødvendig
app.dependency_overrides = {}
Dette eksemplet overstyrer get_db-avhengigheten med en testspesifikk funksjon som returnerer en sesjon til en in-memory SQLite-database. Viktig: Opprettelsen av metadata må kalles eksplisitt for at test-db-en skal fungere korrekt. Å unnlate å opprette tabellen vil føre til feil relatert til manglende tabeller.
Testing av Asynkron Kode
FastAPI er bygget for å være asynkron, så du trenger ofte å teste asynkron kode. TestClient støtter asynkron testing sømløst.
For å teste et asynkront endepunkt, definer testfunksjonen din som async:
import asyncio
from fastapi import FastAPI
app = FastAPI()
@app.get("/async")
async def async_endpoint():
await asyncio.sleep(0.1) # Simuler en asynkron operasjon
return {"message": "Async Hello"}
import pytest
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
@pytest.mark.asyncio # Nødvendig for å være kompatibel med pytest-asyncio
async def test_async_endpoint():
response = client.get("/async")
assert response.status_code == 200
assert response.json() == {"message": "Async Hello"}
Merk: Du må installere pytest-asyncio for å bruke @pytest.mark.asyncio: pip install pytest-asyncio. Du må også sørge for at asyncio.get_event_loop() er konfigurert hvis du bruker eldre pytest-versjoner. Hvis du bruker pytest versjon 8 eller nyere, kan dette være unødvendig.
Testing av Filopplastinger
FastAPI gjør det enkelt å håndtere filopplastinger. For å teste filopplastinger kan du bruke files-parameteret i TestClients forespørselsmetoder.
from fastapi import FastAPI, File, UploadFile
from typing import List
app = FastAPI()
@app.post("/files/")
async def create_files(files: List[bytes] = File()):
return {"file_sizes": [len(file) for file in files]}
@app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile]):
return {"filenames": [file.filename for file in files]}
from fastapi.testclient import TestClient
from .main import app
import io
client = TestClient(app)
def test_create_files():
file_content = b"Test file content"
files = [('files', ('test.txt', io.BytesIO(file_content), 'text/plain'))]
response = client.post("/files/", files=files)
assert response.status_code == 200
assert response.json() == {"file_sizes": [len(file_content)]}
def test_create_upload_files():
file_content = b"Test upload file content"
files = [('files', ('test_upload.txt', io.BytesIO(file_content), 'text/plain'))]
response = client.post("/uploadfiles/", files=files)
assert response.status_code == 200
assert response.json() == {"filenames": ["test_upload.txt"]}
I denne testen oppretter vi en dummy-fil ved hjelp av io.BytesIO og sender den til files-parameteret. files-parameteret aksepterer en liste over tupler, der hvert tuple inneholder feltnavnet, filnavnet og filinnholdet. Innholdstypen er viktig for nøyaktig håndtering av serveren.
Testing av Feilhåndtering
Det er viktig å teste hvordan API-en din håndterer feil. Du kan bruke TestClient til å sende ugyldige forespørsler og verifisere at API-en returnerer de korrekte feilmeldingene.
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id > 100:
raise HTTPException(status_code=400, detail="Item ID too large")
return {"item_id": item_id}
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_item_error():
response = client.get("/items/101")
assert response.status_code == 400
assert response.json() == {"detail": "Item ID too large"}
Denne testen sender en GET-forespørsel til /items/101, som utløser en HTTPException med statuskode 400. Testen bekrefter at svarkoden er 400 og at svarkodens JSON inneholder den forventede feilmeldingen.
Testing av Sikkerhetsfunksjoner
Hvis API-en din bruker autentisering eller autorisasjon, må du teste disse sikkerhetsfunksjonene også. TestClient lar deg sette headere og cookies for å simulere autentiserte forespørsler.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI()
# Sikkerhet
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# Simuler autentisering
if form_data.username != "testuser" or form_data.password != "password123":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password")
return {"access_token": "fake_token", "token_type": "bearer"}
@app.get("/protected")
async def protected_route(token: str = Depends(oauth2_scheme)):
return {"message": "Protected data"}
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_login():
response = client.post("/token", data={"username": "testuser", "password": "password123"})
assert response.status_code == 200
assert "access_token" in response.json()
def test_protected_route():
# Først, hent en token
token_response = client.post("/token", data={"username": "testuser", "password": "password123"})
token = token_response.json()["access_token"]
# Deretter, bruk tokenen for å få tilgang til den beskyttede ruten
response = client.get("/protected", headers={"Authorization": f"Bearer {token}"}). # korrigert format.
assert response.status_code == 200
assert response.json() == {"message": "Protected data"}
I dette eksemplet tester vi innloggingsendepunktet og bruker deretter den mottatte tokenen for å få tilgang til en beskyttet rute. headers-parameteret til TestClients forespørselsmetoder lar deg sette egendefinerte headere, inkludert Authorization-headeren for bearer-tokens.
Beste Praksiser for FastAPI Testing
Her er noen beste praksiser du bør følge når du tester FastAPI-applikasjonene dine:
- Skriv omfattende tester: Sikt på høy testdekning for å sikre at alle deler av API-en din blir grundig testet.
- Bruk beskrivende testnavn: Sørg for at testnavnene dine tydelig indikerer hva testen verifiserer.
- Følg Arrange-Act-Assert-mønsteret: Organiser testene dine i tre distinkte faser: Arrange (sett opp testdataene), Act (utfør handlingen som testes), og Assert (verifiser resultatene).
- Bruk mock-objekter: Mock eksterne avhengigheter for å isolere testene dine og unngå å stole på eksterne systemer.
- Test kanttilfeller: Test API-en din med ugyldig eller uventet input for å sikre at den håndterer feil på en god måte.
- Kjør tester ofte: Integrer testing i utviklingsarbeidsflyten din for å fange feil tidlig og ofte.
- Integrer med CI/CD: Automatiser testene dine i CI/CD-pipelinen din for å sikre at alle kodeendringer blir grundig testet før de distribueres til produksjon. Verktøy som Jenkins, GitLab CI, GitHub Actions eller CircleCI kan brukes til å oppnå dette.
Eksempel: Testing av Internasjonalisering (i18n)
Når du utvikler APIer for et globalt publikum, er internasjonalisering (i18n) avgjørende. Testing av i18n innebærer å verifisere at API-en din støtter flere språk og regioner korrekt. Her er et eksempel på hvordan du kan teste i18n i en FastAPI-applikasjon:
from fastapi import FastAPI, Header
from typing import Optional
app = FastAPI()
messages = {
"en": {"greeting": "Hello, world!"},
"fr": {"greeting": "Bonjour le monde !"},
"es": {"greeting": "¡Hola Mundo!"},
}
@app.get("/")
async def read_root(accept_language: Optional[str] = Header(None)):
lang = accept_language[:2] if accept_language else "en"
if lang not in messages:
lang = "en"
return {"message": messages[lang]["greeting"]}
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_root_en():
response = client.get("/", headers={"Accept-Language": "en-US"})
assert response.status_code == 200
assert response.json() == {"message": "Hello, world!"}
def test_read_root_fr():
response = client.get("/", headers={"Accept-Language": "fr-FR"})
assert response.status_code == 200
assert response.json() == {"message": "Bonjour le monde !"}
def test_read_root_es():
response = client.get("/", headers={"Accept-Language": "es-ES"})
assert response.status_code == 200
assert response.json() == {"message": "¡Hola Mundo!"}
def test_read_root_default():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello, world!"}
Dette eksemplet setter Accept-Language-headeren for å spesifisere ønsket språk. API-en returnerer hilsenen på det spesifiserte språket. Testing sikrer at API-en korrekt håndterer ulike språkpreferanser. Hvis Accept-Language-headeren mangler, brukes standard "en"-språket.
Konklusjon
Testing er en essensiell del av å bygge robuste og pålitelige FastAPI-applikasjoner. TestClient tilbyr en enkel og praktisk måte å teste API-endepunktene dine på. Ved å følge beste praksisene skissert i denne veiledningen, kan du skrive omfattende tester som sikrer kvaliteten og stabiliteten til API-ene dine. Fra grunnleggende forespørsler til avanserte teknikker som dependency injection og asynkron testing, gir TestClient deg muligheten til å lage veltetede og vedlikeholdsvennlige koder. Omfavn testing som en kjernekomponent i utviklingsarbeidsflyten din, og du vil bygge APIer som er både kraftige og pålitelige for brukere over hele verden. Husk viktigheten av CI/CD-integrasjon for å automatisere tester og sikre kontinuerlig kvalitetssikring.