
Contract Testing vs End-to-End: Mikroservislerde Entegrasyon Açmazı ve Test Piramidi

Giriş
2018 yılının Kasım ayıydı. E-ticaret platformumuzda Black Friday trafiğine sadece 48 saat kalmıştı ve PaymentService üzerinde kritik bir hata düzeltmesi çıkmamız gerekiyordu. Kod hazırdı, birim testler 12 saniyede geçmişti. Ancak deployment pipeline’ı tam 48 dakika sürdü. Neden? Çünkü 120 mikroservisi ayağa kaldıran, veritabanlarını seed eden ve Selenium üzerinden koşan 450 adet End-to-End (E2E) testimiz vardı. Testlerden üçü timeout yediği için pipeline failed statüsüne düştü. Retrigger ettik, 48 dakika daha bekledik. Sonuç: Pipeline geçti, kodu canlıya aldık ve 5 dakika sonra OrderService çöktü. E2E testlerimiz ödeme akışını doğrulamıştı ama OrderService‘in beklediği 32-bit integer transaction_id alanı, PaymentService tarafından 64-bit string olarak dönülmeye başlanmıştı. O devasa E2E suite’i, bu spesifik entegrasyon kontratını yakalayamamıştı.
Mikroservis mimarilerinde dağıtık sistemlerin sayısını artırdıkça, test piramidi genellikle bir “dondurma külahına” (ice-cream cone anti-pattern) dönüşür. Birim testler yazılır ama güven vermediği için tüm yük E2E katmanına bindirilir. 50 adet mikroservisiniz varsa, aralarındaki entegrasyon yolları O(N²) karmaşıklığına doğru ilerler. Her bir entegrasyonu gerçek bir staging ortamında test etmeye çalışmak; network gecikmeleri, test verisi çakışmaları ve %15-20 bandında gezinen flaky (istikrarsız) test oranlarıyla sonuçlanır. İşte bu noktada Contract Testing (Kontrat Testleri) devreye girer. Bu analizde, E2E testlerinin yarattığı yanılsamayı, Consumer-Driven Contract (CDC) yaklaşımının matematiğini ve production ortamında hangi katmana, neden ağırlık vermeniz gerektiğini teknik detaylarıyla inceleyeceğiz.
İçindekiler
- End-to-End (E2E) İllüzyonu ve Maliyeti
- Contract Testing (CDC) Mimarisi ve Çalışma Prensibi
- Gerçek Dünya Örneği: Node.js ile Pact Implementasyonu
- Doğrudan Karşılaştırma: E2E vs Contract Testing
- Hangi Senaryoda Hangisini Kullanmalıyım? (Trade-off Analizi)
- Production Vaka Analizi: Sepet Servisi Optimizasyonu
- Pratik Öneriler ve CI/CD Pipeline Entegrasyonu
- Sık Sorulan Sorular
- Sonuç
End-to-End (E2E) İllüzyonu ve Maliyeti
End-to-End testler, bir sistemi tam olarak kullanıcının deneyimleyeceği şekilde dışarıdan içeriye doğru (black-box) test eder. Teoride kusursuzdur. Pratiğe geldiğimizde ise durum bir mühendislik kabusudur.
Bir E2E testinin çalışması için Frontend -> API Gateway -> Auth Service -> Cart Service -> Inventory Service -> Database zincirinin tamamının ayakta ve sağlıklı olması gerekir. Eğer Inventory Service‘in bağlandığı Redis instance’ında anlık bir 200ms’lik latency spike olursa, testiniz başarısız olur. Siz Cart Service üzerinde çalışıyordunuz, ancak testiniz alakasız bir altyapı sorunu yüzünden kırmızıya döndü. Bu durumun CI/CD süreçlerindeki maliyeti devasadır.
“E2E testleri, entegrasyon hatalarını bulmak için değil, sistemin kritik kullanıcı yolculuklarını (Critical User Journeys) baştan sona doğrulayabilmek için yazılmalıdır. Mikroservislerin birbirleriyle doğru konuşup konuşmadığını E2E ile test etmek, mikroskop yerine teleskop kullanmaya benzer.”
Sayılarla konuşalım: Geleneksel bir E2E test ortamında, testlerin ayağa kalkması ve veritabanlarının seed edilmesi ortalama 5-10 dakika alır. Her bir UI tabanlı testin icrası 15-30 saniye sürer. 500 testiniz varsa, paralelleştirme yapsanız bile pipeline süreniz 30 dakikanın altına kolay kolay düşmez. Ayrıca, %15’lik bir false-positive oranı, geliştiricilerin test sonuçlarına olan güvenini sıfırlar. Geliştirici, kırılan bir test gördüğünde “Kodumda hata var” yerine “Test ortamı yine bozuldu” demeye başlar.
Contract Testing (CDC) Mimarisi ve Çalışma Prensibi
Contract Testing, iki servis arasındaki iletişimin (HTTP REST, gRPC veya asenkron event’ler) önceden belirlenmiş bir “kontrata” uyup uymadığını izole bir şekilde doğrular. En yaygın metodoloji Consumer-Driven Contracts (CDC) yaklaşımıdır. Bu yaklaşımda kuralları belirleyen taraf API’yi tüketen (Consumer), bu kurallara uymak zorunda olan taraf ise API’yi sunan (Provider) servistir.
Süreç şu şekilde işler:
- Consumer, Provider’dan beklentilerini (istek formatı, header’lar, beklenen yanıt gövdesi ve statü kodu) kod üzerinden tanımlar.
- Bu tanımlama çalıştırıldığında bir JSON kontrat dosyası (Pact dosyası) üretilir. Bu işlem sırasında gerçek Provider ayakta değildir; Pact framework’ü bir mock server ayağa kaldırır ve Consumer’ın bu mock ile doğru konuşup konuşmadığını (yaklaşık 15-20 milisaniye içinde) doğrular.
- Üretilen bu JSON kontratı, merkezi bir sunucuya (Pact Broker) gönderilir.
- Provider CI/CD pipeline’ında, Pact Broker’dan bu kontratı çeker. Kendi kodunu ayağa kaldırır ve kontrattaki istekleri kendi üzerine replay eder (tekrar oynatır). Yanıtların kontratla birebir eşleşip eşleşmediğini kontrol eder.
Bu mimaride, Consumer ve Provider hiçbir zaman aynı anda ayakta olmak zorunda değildir. Testler milisaniyeler seviyesinde çalışır, ağ bağımlılığı yoktur, veritabanı seed işlemine gerek yoktur.
Gerçek Dünya Örneği: Node.js ile Pact Implementasyonu
Aşağıda, bir OrderService (Consumer) ile PaymentService (Provider) arasındaki iletişimi güvence altına alan bir PactV3 test örneği görebilirsiniz. Burada amaç, ödeme servisine gönderilen tutarın ve dönen işlem ID’sinin tip güvenliğini (type safety) ve veri formatını doğrulamaktır.
const { PactV3, MatchersV3 } = require('@pact-foundation/pact');
const { OrderClient } = require('../src/orderClient');
const path = require('path');
// Pact Mock Provider Konfigürasyonu
const provider = new PactV3({
consumer: 'OrderService',
provider: 'PaymentService',
dir: path.resolve(process.cwd(), 'pacts'),
});
describe('PaymentService API Contract', () => {
it('should process payment and return transaction ID', () => {
// 1. Beklentiyi (Kontratı) Tanımla
provider
.given('payment service is healthy and user has balance')
.uponReceiving('a valid payment request')
.withRequest({
method: 'POST',
path: '/v1/payments',
headers: { 'Content-Type': 'application/json' },
body: {
amount: MatchersV3.decimal(150.50),
currency: 'USD'
}
})
.willRespondWith({
status: 201,
headers: { 'Content-Type': 'application/json' },
body: {
transaction_id: MatchersV3.uuid(),
status: 'SUCCESS'
}
});
// 2. Mock Server üzerinde kodu test et
return provider.executeTest(async (mockServer) => {
const client = new OrderClient(mockServer.url);
const response = await client.processPayment(150.50, 'USD');
// Node.js assert
expect(response.transaction_id).toBeDefined();
expect(response.status).toEqual('SUCCESS');
});
});
});
Yukarıdaki test çalıştığında, gerçek bir ağ isteği atılmaz. Pact, amount alanının kesinlikle bir ondalıklı sayı (decimal), transaction_id alanının ise geçerli bir UUID formatında olması gerektiğini JSON kontratına yazar. Provider takımı bu alanı integer olarak değiştirmeye kalktığında, kendi pipeline’ında 400ms içinde kırmızı bir hata mesajı alacaktır.
Doğrudan Karşılaştırma: E2E vs Contract Testing
Bu iki test yaklaşımını production gerçeklerine göre masaya yatıralım.
| Karşılaştırma Kriteri | End-to-End (E2E) Testleri | Contract Testing (CDC) |
|---|---|---|
| Test Ortamı İhtiyacı | Tüm servisler, veritabanları, message broker’lar (Tam teşekküllü Staging) | Sadece test edilen servisin kendisi (İzole, Localhost) |
| Çalışma Süresi (50 test için) | 15 – 35 dakika (Ağ gecikmesi ve browser render dahil) | 800ms – 2 saniye (Sadece HTTP request/response süresi) |
| Stabilite (Flakiness) | Düşük. Ağ, data, altyapı sorunları testi kırabilir. (%10-20 false-positive) | Çok Yüksek. Sadece kontrat eşleşmezse kırılır. (%0.1 false-positive) |
| Hata Bulma Konumu | Geç. Pipeline’ın son adımlarında, deployment öncesi. | Erken. Geliştiricinin kendi makinesinde veya PR oluşturulduğu an. |
| Bakım Maliyeti | Çok yüksek. UI değişiklikleri, CSS selector güncellemeleri, data seed yönetimi. | Orta. Servis sınırları değiştikçe kontratların versiyonlanması gerekir. |
| Hata Kaynağını Bulma | Zor. “Checkout patladı” der ama hatanın Auth’ta mı Cart’ta mı olduğunu söylemez. | Nokta atışı. “PaymentService, OrderService’in beklediği UUID’yi dönmüyor.” |
Hangi Senaryoda Hangisini Kullanmalıyım? (Trade-off Analizi)
En büyük hata, E2E testlerini tamamen silip yerine Contract testleri yazmaktır. İkisi birbirinin tam ikamesi değildir. İşte mimari karar alma rehberiniz:
Contract Testing Kullanmanız Gereken Senaryolar
- Mikroservisler arası iç iletişim (Internal APIs): A servisi B servisine REST veya gRPC ile bağlanıyorsa, aradaki bağımlılığı kesinlikle Pact veya Spring Cloud Contract ile güvence altına alın.
- Event-Driven mimariler: Kafka veya RabbitMQ üzerinden asenkron mesajlaşıyorsanız (örn:
OrderCreatedEvent), payload’un şemasını doğrulamak için Async Contract testing kullanın. - Büyük takımların bağımsız deployment ihtiyacı: Eğer A takımı kod çıkmak için B takımının staging ortamındaki testlerini bitirmesini bekliyorsa, Contract Testing sizi bu bağımlılıktan kurtarır.
End-to-End (E2E) Kullanmanız Gereken Senaryolar
- Kritik Kullanıcı Yolculukları (Critical User Journeys – CUJ): Sisteminizin para kazandıran 3-5 temel akışı vardır. Örneğin; Login olma, ürünü sepete ekleme ve kredi kartı ile satın alma. Bu akışları, tüm sistem entegre haldeyken günde birkaç kez veya sadece production deployment öncesi çalışacak şekilde (Smoke Test) E2E ile test edin.
- Frontend ve Backend arasındaki entegrasyonun son kontrolü: Contract testleri API şemasını doğrular ama Frontend’in o veriyi ekranda doğru render edip etmediğini bilemez. DOM manipülasyonu, CSS hataları ve tarayıcı spesifik durumlar için Cypress/Playwright ile minimal bir E2E suite’i şarttır.
Production Vaka Analizi: Sepet Servisi Optimizasyonu
Avrupa merkezli bir perakende müşterimizde monolithic bir mimariden mikroservis mimarisine geçiş sürecini yönetiyorduk. CartService‘in her PR’ı için çalışan 180 adet Cypress E2E testimiz vardı. Pipeline p99 süresi 42 dakika ölçülüyordu. Geliştiriciler context-switch yaşıyor, review süreçleri uzuyordu.
Aksiyon: E2E test suite’ini analiz ettik. 180 testin 160 tanesi, aslında CartService‘in PricingService ve PromotionService ile olan entegrasyon varyasyonlarını (indirim kuponu geçersiz, sepet tutarı eşiği altında, vb.) test ediyordu. Bu 160 varyasyonu E2E’den silip, yerine Pact ile 160 adet Contract Test yazdık. Geriye sadece “Kullanıcı siteye girer, sepete ekler ve ödeme sayfasına geçer” diyen 20 adet temel E2E testi bıraktık.
Sonuçlar:
- CI Pipeline Süresi: 42 dakikadan 6 dakikaya düştü (%85 düşüş).
- Testlerin çalışma maliyeti (AWS EC2): m5.4xlarge (32 core) instance’lardan t3.medium instance’lara geçiş yapıldı. Aylık CI altyapı maliyetinde %60 tasarruf sağlandı.
- Flaky test oranı: %18’den %0.5’e (sadece gerçek network kesintilerinde kırılan E2E testleri) geriledi.
- Deployment sıklığı: Günde 2’den günde ortalama 14’e çıktı.
Pratik Öneriler ve CI/CD Pipeline Entegrasyonu
Contract Testing teoride harika görünse de, deployment pipeline’ında doğru orkestre edilmezse bir “versiyonlama cehennemine” dönüşebilir. Üretim ortamında uyguladığımız birkaç altın kural şunlardır:
- Pact Broker Kurulumu: Kontratlarınızı git repository’lerinde taşımayın. Self-hosted bir Pact Broker veya SaaS olan PactFlow kullanın. Tüm versiyon grafiğini ve bağımlılık haritasını buradan yönetin.
can-i-deployCLI Aracı: Deployment öncesinde CD pipeline’ınıza mutlakapact-broker can-i-deploy --pacticipant PaymentService --version 1.0.4 --to-environment productionadımını ekleyin. Bu komut, “Çıkmak istediğim bu versiyon, production’da an itibariyle çalışan diğer servislerin kontratlarıyla uyumlu mu?” sorusuna matematiksel bir yanıt verir.- Consumer Testlerini PR Aşamasına Koyun: Consumer kontratı değiştirdiğinde, Pact Broker’daki webhook’lar tetiklenmeli ve Provider’ın master branch’ine karşı asenkron bir doğrulama (verification) çalıştırmalıdır. Böylece Consumer, başkasının API’sini kıracak bir istek formüle ettiğinde PR’ı merge edemez.
- Fallback Stratejisi: Contract testleri logic hatalarını bulmaz. Eğer
PricingServiceindirim hesaplamasında matematikte hata yapıyorsa (10 – 2 = 9 dönüyorsa) ve bu sayı format olarak doğruysa, Contract testi geçer. İş mantığı (business logic) doğrulamaları için Unit Test ve Component Test katmanlarına güvenin.
Sık Sorulan Sorular
Swagger/OpenAPI spesifikasyonlarımız var. Zaten şema validasyonu yapmıyor muyuz, neden Contract Testing’e ihtiyacım var?
OpenAPI bir dokümantasyon ve sunucu tarafı şema validasyon aracıdır. “Benim API’m bu şekilde çalışır” der. Ancak hangi Consumer’ın API’nin hangi alanını kullandığını söyleyemez. Eğer API’nizden bir alanı (örneğin middle_name) silmek isterseniz, OpenAPI bunun kimin uygulamasını kıracağını bilemez. Contract Testing ise “OrderService şu anda middle_name alanına muhtaç” der ve silmenizi engeller.
Veritabanı şeması değişikliklerinde Contract testleri bizi korur mu?
Hayır. Contract testleri HTTP/gRPC/Event sınırında yaşar. Eğer veritabanında bir kolonu silerseniz ve kodunuz patlarsa, Provider kendi iç unit/integration testlerinde (örneğin Testcontainers ile) bunu yakalamalıdır. Contract testleri, Provider’ın Consumer’a verdiği dışsal sözü kontrol eder, içeride verinin nasıl saklandığını değil.
Pact dosyaları çok büyürse ve testler yavaşlarsa ne yapmalıyız?
Pact testlerinde her bir iş mantığı varyasyonunu test etmek anti-patterndir. State makinelerini değil, kontrat formatlarını test etmelisiniz. Bir uç nokta için statü 200, 400 ve 404 dönen üç ana case yazmanız format doğrulama için genellikle yeterlidir. 50 farklı validasyon hatası için 50 kontrat yazmayın.
Consumer sayımız 100’ü geçerse Provider pipeline’ı çok yavaşlamaz mı?
Pact Broker üzerinde “Pending Pacts” özelliğini aktifleştirdiğinizde, Provider sadece değişen veya yeni olan kontratları çalıştırır. Ayrıca, Provider doğrulamaları kendi içlerinde asenkron çalışacak şekilde paralelleştirilebilir. 100 Consumer testinin doğrulanması genellikle 1-2 dakikayı geçmez.
Sonuç
Yazılım mühendisliğinde “gümüş kurşun” yoktur, ancak E2E testlerinin kontrolsüz büyümesi endüstri genelinde kanıtlanmış bir anti-patterndir. Mikroservis mimarisine sahipseniz, entegrasyon ağınızın genişlediği noktada E2E testlerinin hantallığı ve stabilite sorunları, çevikliğinizi (agility) tamamen ortadan kaldıracaktır.
Aksiyon planı basittir: Sisteminizdeki mevcut E2E test suite’ini açın. Servislerin birbirleriyle olan mesajlaşma varyasyonlarını kontrol eden testleri tespit edin. Bunları Consumer-Driven Contract testlerine (Pact) taşıyın. E2E katmanını ise sadece son kullanıcının gözünden sistemi değerlendiren, sayısını kasıtlı olarak düşük tuttuğunuz (maksimum 15-30 adet) High-Value (Yüksek Değerli) senaryolara ayırın. Test piramidinizi düzeltmek, sadece pipeline sürenizi kısaltmakla kalmayacak; aynı zamanda ekiplerinizin birbirlerinin kodunu bozma korkusu olmadan, bağımsız ve güvenle canlıya çıkmalarını sağlayacaktır.
Bunları da beğenebilirsiniz

Sıfır-Atış Öğrenme ile Endüstriyel Anomali Tespiti: Etiketlenmemiş Veriden Üretim Hatalarını Yakalama Rehberi
Endüstriyel üretimde sıfır-atış öğrenme tekniklerini kullanarak etiketlenmemiş veriden anormallikleri nasıl tespit edeceğinizi öğrenin. Bu rehber, üretim hatalarını yakalamak için yenilikçi stratejiler sunar.

