Opi testaamaan FastAPI-sovelluksiasi tehokkaasti TestClientin avulla. Tutustu parhaisiin käytäntöihin, edistyneisiin tekniikoihin ja tosielämän esimerkkeihin vankkojen ja luotettavien APIen luomiseksi.
FastAPI-testauksen hallinta: Kattava opas TestClient-ohjelmaan
FastAPI on noussut johtavaksi kehykseksi suorituskykyisten APIen rakentamiseen Pythonilla. Sen nopeus, helppokäyttöisyys ja automaattinen tiedon validointi tekevät siitä suosikin kehittäjien keskuudessa ympäri maailmaa. Hyvin rakennettu API on kuitenkin vain niin hyvä kuin sen testit. Perusteellinen testaus varmistaa, että API toimii odotetusti, pysyy vakaana paineen alla ja voidaan luottavaisin mielin ottaa käyttöön tuotannossa. Tämä kattava opas keskittyy FastAPI:n TestClient-ohjelman käyttöön API-päätepisteiden tehokkaaseen testaamiseen.
Miksi testaus on tärkeää FastAPI-sovelluksille?
Testaus on kriittinen vaihe ohjelmistokehityksen elinkaaressa. Se auttaa sinua:
- Tunnistamaan virheet varhaisessa vaiheessa: Nappaamaan virheet ennen kuin ne pääsevät tuotantoon, mikä säästää aikaa ja resursseja.
- Varmistamaan koodin laadun: Edistämään hyvin jäsenneltyä ja ylläpidettävää koodia.
- Ehkäisemään regressioita: Takaamaan, että uudet muutokset eivät riko olemassa olevaa toiminnallisuutta.
- Parantamaan API:n luotettavuutta: Rakentamaan luottamusta API:n vakauteen ja suorituskykyyn.
- Helpottaa yhteistyötä: Tarjoamaan selkeää dokumentaatiota odotetusta käyttäytymisestä muille kehittäjille.
FastAPI:n TestClientin esittely
FastAPI tarjoaa sisäänrakennetun TestClient-ohjelman, joka yksinkertaistaa API-päätepisteiden testaamista. TestClient toimii kevyenä asiakkaana, joka voi lähettää pyyntöjä API:lle käynnistämättä täysimittaista palvelinta. Tämä tekee testaamisesta huomattavasti nopeampaa ja kätevämpää.
TestClientin tärkeimmät ominaisuudet:
- Simuloi HTTP-pyyntöjä: Sallii GET-, POST-, PUT-, DELETE- ja muiden HTTP-pyyntöjen lähettämisen API:lle.
- Käsittelee datan serialisoinnin: Serialisoi automaattisesti pyyntödata (esim. JSON-hyötykuormat) ja deserialisoi vastausdata.
- Tarjoaa väittämämenetelmiä: Tarjoaa käteviä menetelmiä vastausten tilakoodin, otsikoiden ja sisällön varmentamiseen.
- Tukee asynkronista testausta: Toimii saumattomasti FastAPI:n asynkronisen luonteen kanssa.
- Integroituu testauskehysten kanssa: Integroituu helposti suosittujen Python-testauskehysten, kuten pytest ja unittest, kanssa.
Testausympäristön määrittäminen
Ennen testaamisen aloittamista sinun on määritettävä testausympäristö. Tämä sisältää tyypillisesti tarvittavien riippuvuuksien asentamisen ja testauskehyksen määrittämisen.
Asennus
Varmista ensin, että sinulla on FastAPI ja pytest asennettuna. Voit asentaa ne pip:in avulla:
pip install fastapi pytest httpx
httpx on HTTP-asiakas, jota FastAPI käyttää sisäisesti. Vaikka TestClient on osa FastAPI:tä, httpx:n asentaminen varmistaa sujuvan testauksen. Jotkut opetusohjelmat mainitsevat myös requests, mutta httpx on paremmin linjassa FastAPI:n asynkronisen luonteen kanssa.
Esimerkki FastAPI-sovelluksesta
Luodaan yksinkertainen FastAPI-sovellus, jota voimme käyttää testaamiseen:
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
Tallenna tämä koodi nimellä main.py. Tämä sovellus määrittää kolme päätepistettä:
/: Yksinkertainen GET-päätepiste, joka palauttaa viestin "Hello World"./items/{item_id}: GET-päätepiste, joka palauttaa kohteen sen ID:n perusteella./items/: POST-päätepiste, joka luo uuden kohteen.
Ensimmäisen testin kirjoittaminen
Nyt kun sinulla on FastAPI-sovellus, voit aloittaa testien kirjoittamisen TestClient-ohjelman avulla. Luo uusi tiedosto nimeltä test_main.py samaan hakemistoon kuin 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"}
Tässä testissä:
- Tuomme
TestClient-ohjelman ja FastAPIapp-instanssin. - Luomme
TestClient-instanssin ja välitämme siihenapp-sovelluksen. - Määritämme testifunktion
test_read_root. - Testifunktion sisällä käytämme
client.get("/")-funktiota lähettääksemme GET-pyynnön juuripäätepisteeseen. - Väitämme, että vastauksen tilakoodi on 200 (OK).
- Väitämme, että vastauksen JSON on yhtä suuri kuin
{"message": "Hello World"}.
Testien suorittaminen pytestillä
Testien suorittamiseksi avaa vain pääte hakemistossa, joka sisältää test_main.py-tiedoston, ja suorita seuraava komento:
pytest
pytest löytää ja suorittaa automaattisesti kaikki projektin testit. Sinun pitäisi nähdä samankaltainen tulos kuin tämä:
============================= 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 ===============================
Eri HTTP-menetelmien testaaminen
TestClient tukee kaikkia standardeja HTTP-menetelmiä, mukaan lukien GET, POST, PUT, DELETE ja PATCH. Katsotaanpa, miten kutakin näistä menetelmistä testataan.
GET-pyyntöjen testaaminen
Olemme jo nähneet esimerkin GET-pyynnön testaamisesta edellisessä osiossa. Tässä on toinen esimerkki, jossa testataan /items/{item_id}-päätepistettä:
def test_read_item():
response = client.get("/items/1?q=test")
assert response.status_code == 200
assert response.json() == {"item_id": 1, "q": "test"}
Tämä testi lähettää GET-pyynnön osoitteeseen /items/1 ja kyselyparametrin q=test. Se sitten väittää, että vastauksen tilakoodi on 200 ja että vastauksen JSON sisältää odotetut tiedot.
POST-pyyntöjen testaaminen
POST-pyynnön testaamiseksi sinun on lähetettävä tietoja pyynnön rungossa. TestClient serialisoi tiedot automaattisesti JSON-muotoon.
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
Tässä testissä:
- Luomme sanakirjan
item_data, joka sisältää uuden kohteen tiedot. - Käytämme
client.post("/items/", json=item_data)-funktiota lähettääksemme POST-pyynnön/items/-päätepisteeseen ja välitämmeitem_data:n JSON-hyötykuormana. - Väitämme, että vastauksen tilakoodi on 200 ja että vastauksen JSON vastaa
item_data-tietoja.
PUT-, DELETE- ja PATCH-pyyntöjen testaaminen
PUT-, DELETE- ja PATCH-pyyntöjen testaaminen on samanlaista kuin POST-pyyntöjen testaaminen. Käytät yksinkertaisesti vastaavia menetelmiä TestClient-ohjelmassa:
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
# Lisää väittämiä odotetulle vastaukselle
def test_delete_item():
response = client.delete("/items/1")
assert response.status_code == 200
# Lisää väittämiä odotetulle vastaukselle
def test_patch_item():
item_data = {"price": 29.99}
response = client.patch("/items/1", json=item_data)
assert response.status_code == 200
# Lisää väittämiä odotetulle vastaukselle
Muista lisätä väittämiä, jotta voit varmistaa, että vastaukset ovat odotetun mukaisia.
Edistyneet testaustekniikat
TestClient tarjoaa useita edistyneitä ominaisuuksia, jotka voivat auttaa sinua kirjoittamaan kattavampia ja tehokkaampia testejä.
Testaaminen riippuvuuksien kanssa
FastAPI:n riippuvuuksien injektointijärjestelmä mahdollistaa riippuvuuksien helpon injektoinnin API-päätepisteisiin. Testaamisen aikana saatat haluta ohittaa nämä riippuvuudet tarjotaksesi pilkka- tai testikohtaisia toteutuksia.
Oletetaan esimerkiksi, että sovelluksesi on riippuvainen tietokantayhteydestä. Voit ohittaa tietokantariippuvuuden testeissäsi käyttämällä muistissa olevaa tietokantaa:
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
# Tietokannan määritys
DATABASE_URL = "sqlite:///./test.db" # Muistissa oleva tietokanta testausta varten
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Määritä käyttäjämalli
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-sovellus
app = FastAPI()
# Riippuvuus tietokantaistunnon saamiseksi
def get_db():
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
# Päätepiste käyttäjän luomiseksi
@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)
# Ohita tietokantariippuvuus testausta varten
def override_get_db():
try:
db = TestingSessionLocal()
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
def test_create_user():
# Varmista ensin, että taulukot on luotu, mitä ei välttämättä tapahdu oletusarvoisesti
Base.metadata.create_all(bind=engine) # tärkeää: luo taulukot testitietokantaan
response = client.post("/users/", params={"username": "testuser", "password": "password123"})
assert response.status_code == 200
assert response.json()["username"] == "testuser"
# Siivoa ohitus testin jälkeen tarvittaessa
app.dependency_overrides = {}
Tämä esimerkki ohittaa get_db-riippuvuuden testikohtaisella funktiolla, joka palauttaa istunnon muistissa olevaan SQLite-tietokantaan. Tärkeää: Metatietojen luonti on kutsuttava eksplisiittisesti, jotta testitietokanta toimisi oikein. Jos taulukkoa ei luoda, se johtaa taulukoiden puuttumiseen liittyviin virheisiin.
Asynkronisen koodin testaaminen
FastAPI on rakennettu asynkroniseksi, joten sinun on usein testattava asynkronista koodia. TestClient tukee asynkronista testausta saumattomasti.
Jos haluat testata asynkronista päätepistettä, määritä testifunktio yksinkertaisesti nimellä async:
import asyncio
from fastapi import FastAPI
app = FastAPI()
@app.get("/async")
async def async_endpoint():
await asyncio.sleep(0.1) # Simuloi joitain asynkronisia toimintoja
return {"message": "Async Hello"}
import pytest
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
@pytest.mark.asyncio # Tarvitaan yhteensopivuutta varten pytest-asyncio:n kanssa
async def test_async_endpoint():
response = client.get("/async")
assert response.status_code == 200
assert response.json() == {"message": "Async Hello"}
Huomautus: Sinun on asennettava pytest-asyncio, jotta voit käyttää @pytest.mark.asyncio:ta: pip install pytest-asyncio. Sinun on myös varmistettava, että asyncio.get_event_loop() on määritetty, jos käytät vanhempia pytest-versioita. Jos käytät pytest-versiota 8 tai uudempaa, tätä ei ehkä tarvita.
Tiedostojen latausten testaaminen
FastAPI tekee tiedostojen latausten käsittelystä helppoa. Jos haluat testata tiedostojen latauksia, voit käyttää files-parametria TestClient-pyyntömenetelmissä.
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"]}
Tässä testissä luomme valedataa käyttämällä io.BytesIO-funktiota ja välitämme sen files-parametrille. files-parametri hyväksyy luettelon tupleja, joissa jokainen tuple sisältää kentän nimen, tiedoston nimen ja tiedoston sisällön. Sisältötyyppi on tärkeä palvelimen tarkan käsittelyn kannalta.
Virheiden käsittelyn testaaminen
On tärkeää testata, miten API käsittelee virheitä. Voit käyttää TestClient-ohjelmaa lähettääksesi virheellisiä pyyntöjä ja varmistaaksesi, että API palauttaa oikeat virhe vastaukset.
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"}
Tämä testi lähettää GET-pyynnön osoitteeseen /items/101, joka nostaa HTTPException-poikkeuksen, jonka tilakoodi on 400. Testi väittää, että vastauksen tilakoodi on 400 ja että vastauksen JSON sisältää odotetun virheilmoituksen.
Suojausominaisuuksien testaaminen
Jos API käyttää todennusta tai valtuutusta, sinun on testattava myös näitä suojausominaisuuksia. TestClient sallii sinun asettaa otsikoita ja evästeitä simuloidaksesi todennettuja pyyntöjä.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI()
# Turvallisuus
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# Simuloi todennusta
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():
# Hanki ensin tunnus
token_response = client.post("/token", data={"username": "testuser", "password": "password123"})
token = token_response.json()["access_token"]
# Käytä sitten tunnusta suojattuun reittiin pääsemiseksi
response = client.get("/protected", headers={"Authorization": f"Bearer {token}"}) # korjattu muoto.
assert response.status_code == 200
assert response.json() == {"message": "Protected data"}
Tässä esimerkissä testaamme kirjautumispäätepistettä ja käytämme sitten saatua tunnus suojattuun reittiin pääsemiseksi. TestClient-pyyntömenetelmien headers-parametri antaa sinun asettaa mukautettuja otsikoita, mukaan lukien Authorization-otsikko kantajatunnuksille.
Parhaat käytännöt FastAPI-testauksessa
Tässä on joitain parhaita käytäntöjä, joita kannattaa noudattaa testattaessa FastAPI-sovelluksia:- Kirjoita kattavia testejä: Pyri korkeaan testikattavuuteen varmistaaksesi, että kaikki API:n osat on testattu perusteellisesti.
- Käytä kuvaavia testinimiä: Varmista, että testinimesi osoittavat selvästi, mitä testi on varmentamassa.
- Noudata Järjestä-Toimi-Varmista-mallia: Järjestä testit kolmeen erilliseen vaiheeseen: Järjestä (määritä testidata), Toimi (suorita testattava toiminto) ja Varmista (varmista tulokset).
- Käytä pilkkaobjekteja: Pilkkaa ulkoisia riippuvuuksia eristääksesi testit ja välttääksesi luottamisen ulkoisiin järjestelmiin.
- Testaa reunatapauksia: Testaa API virheellisellä tai odottamattomalla syötteellä varmistaaksesi, että se käsittelee virheitä sulavasti.
- Suorita testejä usein: Integroi testaus kehitystyönkulkuun havaitaksesi virheet varhaisessa vaiheessa ja usein.
- Integroi CI/CD:n kanssa: Automatisoi testit CI/CD-putkessasi varmistaaksesi, että kaikki koodimuutokset testataan perusteellisesti ennen tuotantoon lähettämistä. Työkaluja, kuten Jenkins, GitLab CI, GitHub Actions tai CircleCI, voidaan käyttää tämän saavuttamiseen.
Esimerkki: Kansainvälistymisen (i18n) testaaminen
Kehittäessäsi API:ja globaalille yleisölle, kansainvälistyminen (i18n) on olennaista. I18n:n testaaminen sisältää sen varmistamisen, että API tukee useita kieliä ja alueita oikein. Tässä on esimerkki siitä, miten voit testata i18n:ää FastAPI-sovelluksessa:
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!"}
Tämä esimerkki asettaa Accept-Language-otsikon määrittämään halutun kielen. API palauttaa tervehdyksen määritetyllä kielellä. Testaus varmistaa, että API käsittelee erilaisia kieliasetuksia oikein. Jos Accept-Language-otsikko puuttuu, käytetään oletuskieltä "en".
Johtopäätös
Testaus on olennainen osa vankkojen ja luotettavien FastAPI-sovellusten rakentamista. TestClient tarjoaa yksinkertaisen ja kätevän tavan testata API-päätepisteitäsi. Noudattamalla tässä oppaassa esitettyjä parhaita käytäntöjä voit kirjoittaa kattavia testejä, jotka varmistavat API:idesi laadun ja vakauden. Peruspyynnöistä edistyneisiin tekniikoihin, kuten riippuvuuksien injektointiin ja asynkroniseen testaukseen, TestClient antaa sinulle mahdollisuuden luoda hyvin testattua ja ylläpidettävää koodia. Ota testaus osaksi kehitystyönkulkuasi, niin luot API:ja, jotka ovat sekä tehokkaita että luotettavia käyttäjille ympäri maailmaa. Muista CI/CD-integraation tärkeys testauksen automatisoimiseksi ja jatkuvan laadunvarmistuksen varmistamiseksi.