Python ile REST API Testi: requests ve pytest
Neden API Testi?
Modern yazılım geliştirmede REST API'ler uygulamaların omurgasını oluşturuyor. Frontend, mobil uygulama, üçüncü parti entegrasyonlar — hepsi API üzerinden haberleşiyor. Bir endpoint'in bozulması tüm sistemi etkileyebilir.
Manuel test (Postman ile tek tek istek atma) küçük projelerde işe yarar ama ölçeklenmez. Otomatik API testleri ile her commit'te tüm endpoint'ler test edilir, regression hataları anında yakalanır ve refactoring güvenle yapılabilir.
Python, API testi için mükemmel bir dildir. requests kütüphanesi HTTP isteklerini kolay hale getirir, pytest ise test framework olarak güçlü ve esnektir.
requests Kütüphanesi ile HTTP İstekleri
requests Python'un en popüler HTTP kütüphanesidir. Temel kullanım:
import requests
BASE_URL = "http://localhost:8080/api"
# GET istegi
response = requests.get(f"{BASE_URL}/users")
print(response.status_code) # 200
print(response.json()) # JSON response body
# POST istegi
new_user = {
"name": "Tolgahan Gencer",
"email": "tolgahan@example.com",
"password": "securePassword123"
}
response = requests.post(f"{BASE_URL}/users", json=new_user)
created_user = response.json()
# PUT istegi
update_data = {"name": "Tolgahan G."}
response = requests.put(
f"{BASE_URL}/users/{created_user['id']}",
json=update_data,
headers={"Authorization": f"Bearer {token}"}
)
# DELETE istegi
response = requests.delete(f"{BASE_URL}/users/{created_user['id']}")Session ile Token Yönetimi
Her istekte header eklemek yerine Session kullanabilirsiniz:
session = requests.Session()
session.headers.update({
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs...",
"Content-Type": "application/json"
})
# Artik her istekte header otomatik eklenir
response = session.get(f"{BASE_URL}/users")
response = session.post(f"{BASE_URL}/orders", json=order_data)pytest ile Test Yapısı
pytest, Python'un en güçlü test framework'üdür. Basit bir test dosyası:
# tests/test_users_api.py
import requests
import pytest
BASE_URL = "http://localhost:8080/api"
class TestUsersAPI:
def test_get_all_users(self):
response = requests.get(f"{BASE_URL}/users")
assert response.status_code == 200
assert isinstance(response.json(), list)
def test_create_user(self):
payload = {
"name": "Test User",
"email": "test@example.com",
"password": "Test1234!"
}
response = requests.post(f"{BASE_URL}/users", json=payload)
assert response.status_code == 201
data = response.json()
assert data["name"] == "Test User"
assert "password" not in data
def test_get_user_not_found(self):
response = requests.get(f"{BASE_URL}/users/999999")
assert response.status_code == 404
def test_create_user_invalid_email(self):
payload = {"name": "Test", "email": "invalid", "password": "Test1234!"}
response = requests.post(f"{BASE_URL}/users", json=payload)
assert response.status_code == 400pytest Fixtures ile Setup/Teardown
Fixtures, testler arasında paylaşılan setup kodunu merkezi hale getirir:
# tests/conftest.py
import pytest
import requests
BASE_URL = "http://localhost:8080/api"
@pytest.fixture(scope="session")
def api_session():
session = requests.Session()
login_data = {"email": "admin@example.com", "password": "Admin123!"}
response = session.post(f"{BASE_URL}/auth/login", json=login_data)
token = response.json()["accessToken"]
session.headers.update({"Authorization": f"Bearer {token}"})
return session
@pytest.fixture
def create_user(api_session):
created_ids = []
def _create_user(name="Test User", email=None):
if email is None:
import uuid
email = f"test-{uuid.uuid4().hex[:8]}@example.com"
payload = {"name": name, "email": email, "password": "Test1234!"}
response = api_session.post(f"{BASE_URL}/users", json=payload)
user = response.json()
created_ids.append(user["id"])
return user
yield _create_user
for user_id in created_ids:
api_session.delete(f"{BASE_URL}/users/{user_id}")
@pytest.fixture
def sample_user(create_user):
return create_user(name="Sample User")Kullanım:
class TestUserOperations:
def test_update_user(self, api_session, sample_user):
user_id = sample_user["id"]
response = api_session.put(
f"{BASE_URL}/users/{user_id}",
json={"name": "Updated Name"}
)
assert response.status_code == 200
assert response.json()["name"] == "Updated Name"@pytest.mark.parametrize ile Veri Odaklı Testler
Aynı testi farklı verilerle çalıştırmak için parametrize kullanın:
class TestInputValidation:
@pytest.mark.parametrize("email,expected_status", [
("valid@example.com", 201),
("", 400),
("gecersiz-email", 400),
("user@domain.com", 201),
])
def test_email_validation(self, api_session, email, expected_status):
payload = {"name": "Test", "email": email, "password": "Test1234!"}
response = api_session.post(f"{BASE_URL}/users", json=payload)
assert response.status_code == expected_status
@pytest.mark.parametrize("password,expected_status", [
("Short1!", 400),
("nouppercase1!", 400),
("ValidPass123!", 201),
])
def test_password_validation(self, api_session, password, expected_status):
import uuid
payload = {
"name": "Test",
"email": f"test-{uuid.uuid4().hex[:8]}@example.com",
"password": password
}
response = api_session.post(f"{BASE_URL}/users", json=payload)
assert response.status_code == expected_statusMock Server ile Bağımsız Test
Harici API bağımlılığını kaldırmak için responses kütüphanesini kullanın:
import responses
import requests
BASE_URL = "https://api.payment-provider.com"
class TestPaymentService:
@responses.activate
def test_successful_payment(self):
responses.add(
responses.POST,
f"{BASE_URL}/charges",
json={"id": "ch_123", "status": "succeeded", "amount": 5000},
status=200
)
response = requests.post(
f"{BASE_URL}/charges",
json={"amount": 5000, "currency": "TRY", "source": "tok_visa"}
)
assert response.status_code == 200
assert response.json()["status"] == "succeeded"
@responses.activate
def test_payment_failure(self):
responses.add(
responses.POST,
f"{BASE_URL}/charges",
json={"error": {"code": "card_declined"}},
status=402
)
response = requests.post(f"{BASE_URL}/charges", json={"amount": 5000})
assert response.status_code == 402CI/CD Entegrasyonu
GitHub Actions ile her push'ta API testlerini otomatik çalıştırma:
name: API Tests
on: [push, pull_request]
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install pytest requests responses
- name: Run API tests
run: pytest tests/ -v --tb=short --junitxml=test-results.xmlResponse Validation
API yanıtlarının yapısını doğrulamak için schema validation kullanın:
def assert_user_schema(data):
required_fields = ["id", "name", "email", "createdAt"]
for field in required_fields:
assert field in data, f"'{field}' alani eksik"
assert isinstance(data["id"], int)
assert "@" in data["email"]
assert "password" not in data
class TestUserSchema:
def test_response_schema(self, api_session, sample_user):
response = api_session.get(f"{BASE_URL}/users/{sample_user['id']}")
assert response.status_code == 200
assert_user_schema(response.json())Özet
requests kütüphanesi ile HTTP isteklerini kolay ve okunabilir şekilde yapın
pytest fixtures ile test setup/teardown kodunu merkezileştirin
parametrize ile aynı testi farklı verilerle çalıştırarak coverage artırın
Mock server (responses) ile harici API bağımlılıklarını ortadan kaldırın
CI/CD pipeline ile testleri her commit'te otomatik çalıştırın
Schema validation ile response yapısını doğrulayın
Bu yazıyı beğendiniz mi?
Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.
Bu konuyu derinlemesine öğrenmek ister misin?
Python Programlama: Sıfırdan İleri Seviyeye
İlgili Yazılar
Python Generators ve Iterators: Bellek Verimli Programlamanın Sırrı
Python'da generator ve iterator yapıları nasıl çalışır? Lazy evaluation, yield, generator expression, pipeline pattern v...
Python Decorators (Dekoratörler): Fonksiyonlarını Güçlendir
Python'da decorator'lar nasıl çalışır, nasıl yazılır ve gerçek projelerde nasıl kullanılır? Sıfırdan ileri seviyeye, kod...
Python Nedir? Neden Python Öğrenmelisiniz?
Python nedir, ne işe yarar? Yapay zeka, veri bilimi, web geliştirme ve otomasyon için neden Python? Kariyer fırsatları v...