FastAPI Testing Rehberi: Profesyonel Test Stratejileri
Yazar: Burak Balkı | Kategori: Testing | Okuma Süresi: 9 dk
FastAPI uygulamaları için kapsamlı test stratejileri rehberi. Pytest, TestClient, asenkron testler, dependency injection mocking ve CI/CD entegrasyonu hakkın...
## FastAPI Testing Temelleri: Neden Test Yapmalıyız?
Modern yazılım geliştirme süreçlerinde, özellikle **FastAPI** gibi yüksek performanslı frameworkler kullanırken, kodun doğruluğunu garanti altına almak kritik öneme sahiptir. **FastAPI testing** süreçleri, uygulamanızın son kullanıcıya ulaşmadan önce mantıksal hatalardan arındırılmasını sağlar. Test tabanlı geliştirme (TDD), sadece hataları bulmakla kalmaz, aynı zamanda kodun dokümantasyonuna ve tasarım kalitesine de katkıda bulunur.
FastAPI, yapısı gereği **Starlette** üzerine inşa edilmiştir ve test işlemleri için doğrudan Starlette'in `TestClient` sınıfını kullanır. Bu rehberde, basit bir API'dan karmaşık asenkron işlemlere kadar tüm test senaryolarını ele alacağız.
## Test Ortamının Kurulumu: Pytest ve HTTPX
FastAPI projelerinde standart test aracı **Pytest**'tir. Pytest'in esnek yapısı ve geniş eklenti ekosistemi, API testlerini oldukça kolaylaştırır. Ayrıca asenkron testler için `httpx` kütüphanesine ihtiyaç duyacağız.
Öncelikle gerekli kütüphaneleri yükleyelim:
```bash
pip install pytest httpx fastapi uvicorn
```
Proje yapınızı şu şekilde organize etmeniz önerilir:
```text
.
├── app/
│ ├── main.py
│ └── dependencies.py
└── tests/
├── __init__.py
└── test_main.py
```
## İlk Testin Yazılması: TestClient Kullanımı
`TestClient`, FastAPI uygulamanıza gerçek bir HTTP sunucusu ayağa kaldırmadan istek atmanızı sağlar. Bu, testlerin milisaniyeler içinde tamamlanmasına olanak tanır.
### Uygulama Kodu (main.py)
```python
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Merhaba Dünya"}
```
### Test Kodu (test_main.py)
```python
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Merhaba Dünya"}
```
> **Önemli Not:** `TestClient` senkron bir yapıdır. Eğer uygulamanızda sadece `def` ile tanımlanmış endpointler varsa bu yeterlidir. Ancak `async def` kullanıyorsanız asenkron test stratejilerini uygulamanız gerekir.
## FastAPI Dependency Injection ve Mocking
FastAPI'ın en güçlü özelliklerinden biri olan **Dependency Injection** (Bağımlılık Enjeksiyonu), test sırasında bileşenleri taklit etmeyi (mocking) son derece kolaylaştırır. Örneğin, bir veritabanı bağlantısını veya dış servis çağrısını test sırasında sahte bir nesneyle değiştirebilirsiniz.
```python
from fastapi import Depends, FastAPI
from app.dependencies import get_query_token
app = FastAPI()
@app.get("/items/")
async def read_items(token: str = Depends(get_query_token)):
return {"token": token}
# Test dosyasında bağımlılığı ezme
from app.main import app
def override_get_query_token():
return "fake-token"
app.dependency_overrides[get_query_token] = override_get_query_token
def test_read_items_with_override():
response = client.get("/items/")
assert response.status_code == 200
assert response.json() == {"token": "fake-token"}
```
## Asenkron (Async) Test Stratejileri
FastAPI asenkron bir framework olduğu için, bazı durumlarda testlerinizin de asenkron olması gerekir. Bunun için `pytest-asyncio` ve `httpx.AsyncClient` kullanmalıyız.
```python
import pytest
from httpx import AsyncClient
from app.main import app
@pytest.mark.asyncio
async def test_async_endpoint():
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.get("/")
assert response.status_code == 200
```
## Veritabanı Testleri: Test Veritabanı Yönetimi
Veritabanı testlerinde en önemli kural, testlerin prodüksiyon verilerine zarar vermemesi ve her testten sonra veritabanının temizlenmesidir. Genellikle bellek içi (in-memory) **SQLite** kullanılır.
| Veritabanı Tipi | Kullanım Senaryosu | Avantajı |
| :--- | :--- | :--- |
| SQLite (In-memory) | Birim Testler | Çok hızlı, kurulum gerektirmez |
| PostgreSQL (Docker) | Entegrasyon Testleri | Gerçek dünya senaryosu sunar |
| Mock DB | Hızlı Mantık Testleri | I/O gerektirmez |
### Veritabanı Fixture Örneği
```python
import pytest
from sqlalchemy import create_mock_engine
@pytest.fixture(name="session")
def session_fixture():
# Veritabanı oluşturma ve session başlatma işlemleri
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
```
## Pytest Fixtures ile Kod Tekrarını Önleme
Fixtures, testler arasında paylaşılan kurulum ve temizleme kodlarını yönetmek için kullanılır. `conftest.py` dosyası içinde tanımlanan fixture'lar tüm test dosyaları tarafından erişilebilir.
```python
import pytest
from fastapi.testclient import TestClient
from app.main import app
@pytest.fixture
def client():
with TestClient(app) as c:
yield c
def test_with_fixture(client):
response = client.get("/")
assert response.status_code == 200
```
## Middleware ve Exception Handler Testleri
Custom middleware veya hata yakalayıcıların (exception handlers) doğru çalışıp çalışmadığını test etmek, uygulama güvenliği için kritiktir.
```python
def test_custom_exception_handler(client):
response = client.get("/force-error")
assert response.status_code == 418
assert response.json() == {"detail": "Çaydanlık Hatası"}
```
## Kimlik Doğrulama (Auth) ve Güvenlik Testleri
JWT tabanlı kimlik doğrulama sistemlerinde, testlerinize `Authorization` header'ı eklemeniz gerekir.
```python
def test_protected_route_without_token(client):
response = client.get("/users/me")
assert response.status_code == 401
def test_protected_route_with_token(client):
token = "valid-jwt-token"
response = client.get("/users/me", headers={"Authorization": f"Bearer {token}"})
assert response.status_code == 200
```
## Test Coverage ve Raporlama
Kodunuzun ne kadarının test edildiğini ölçmek için `pytest-cov` kullanabilirsiniz. Bu, test edilmemiş kritik noktaları belirlemenize yardımcı olur.
```bash
pytest --cov=app tests/
```
Bu komut size her dosya için kapsama oranını (coverage percentage) verecektir. Profesyonel projelerde bu oranın %80 üzerinde olması beklenir.
## CI/CD Entegrasyonu: GitHub Actions Örneği
Testlerin her commit sonrasında otomatik çalışması için bir CI (Continuous Integration) pipeline'ı kurmak şarttır. Aşağıda basit bir GitHub Actions yapılandırması bulunmaktadır:
```yaml
name: FastAPI Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with: {python-version: '3.9'}
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests
run: pytest
```
## FastAPI Testing Best Practices
1. **Bağımsız Testler:** Her test kendi başına çalışabilmeli ve diğer testlerin sonucuna güvenmemelidir.
2. **Gerçekçi Veriler:** Test verileriniz (mock data) gerçek dünya senaryolarını yansıtmalıdır.
3. **Hızlı Geri Bildirim:** Testler hızlı çalışmalıdır. Yavaş testler geliştirme sürecini aksatır.
4. **Parametrize Testler:** Aynı mantığı farklı girdilerle test etmek için `pytest.mark.parametrize` kullanın.
```python
@pytest.mark.parametrize("item_id, expected_status", [(1, 200), (-1, 404), (999, 404)])
def test_read_item(client, item_id, expected_status):
response = client.get(f"/items/{item_id}")
assert response.status_code == expected_status
```
## Sık Yapılan Hatalar
- **Production DB Kullanımı:** Asla gerçek veritabanı üzerinde test yapmayın.
- **Dependency Overrides Unutulması:** Mock yapılması gereken servislerin (örn: Email servisi) gerçekte çalışması.
- **Async/Await Karmaşası:** Asenkron fonksiyonları senkron `TestClient` ile çağırmaya çalışmak.
- **Global State Kirliliği:** Bir testin veritabanında bıraktığı verinin bir sonraki testi etkilemesi.
## Performans İpuçları
- **Parallel Testing:** `pytest-xdist` kullanarak testleri birden fazla CPU çekirdeğinde paralel çalıştırın.
- **Fixture Scoping:** Eğer bir fixture her test için yeniden oluşturulmak zorunda değilse, `scope="module"` veya `scope="session"` kullanın.
## Özet ve Sonuç
FastAPI uygulamalarında test yazmak, projenin sürdürülebilirliği ve güvenilirliği için opsiyonel değil, zorunludur. `TestClient` ve `Pytest` kombinasyonu, Python ekosistemindeki en güçlü test araçlarını sunar. Bu rehberdeki adımları takip ederek, uygulamanızın kalitesini artırabilir ve hata payınızı minimize edebilirsiniz.