Dağıtık sistemleri test etmek, açık konuşayım, çoğu zaman dümdüz bir sinir testi gibi. Sen butona basıyorsun, arka tarafta bir servis Kafka’ya mesaj atıyor, başka bir servis onu kapıyor, üçüncü servis de gidip veritabanını güncelliyor… sonra oturup “bu akış gerçekten çalıştı mı?” diye bakıyorsun ekrana. İşin can sıkıcı kısmı da tam şu: UI tarafında her şey yeşil görünürken mesaj kuyruğunda sessizce bir hata patlayabiliyor. Kimse fark etmiyor. Ta ki production’da bir şeyler ters gidene kadar.
Ben bu tip akışlarla ilk kez 2021’de İstanbul’da bir fintech projesinde uğraşmıştım. Frontend ekibi “sipariş oluşturuldu” ekranını düzgün görüyordu ama Kafka consumer tarafında payload bazen eksik geliyordu. Bazen. Yani tutarsız — ki bu daha da sinir bozucu. O gün anladım ki sadece ekran testi yapmak yetmiyor; olayın omurgasını da yoklamak gerekiyor. İşte bu yazıda tam olarak o omurgayı,. Playwright ile kullanıcı aksiyonunu tetikleyip Kafka mesajını doğrulama işini konuşacağız.
Evet, doğru duydunuz.
Neden bu kadar uğraşıyoruz?
Çünkü mikroservis dünyasında tek bir tık, arkada beş ayrı sistemin sıraya girmesi demek olabiliyor. UI testi “buton çalıştı” der. Tamam, güzel. Ama asıl soru şu: o buton doğru olayı üretti mi? Mesaj schema’sı sağlam mı? Topic’e düşen veri beklediğin formatta mı? Hani biraz tersten bakınca mesele çok daha netleşiyor aslında.
Geçen sene Mart ayında Ankara’da çalışan küçük bir SaaS ekibiyle sohbet etmiştim. Onlar E2E testlerini sadece tarayıcı üstünden yapıyordu — ki bu başlangıç için mantıklı bir tercih, bunu söylemeliyim. Ama production’a çıkan bir hata yüzünden kampanya event’leri yanlış topic’e gidiyordu. İki gün kayıp yaşadılar. Sonra test stratejisini değiştirip UI + message validation modeline geçtiler; olay baya toparlandı.
Ne yalan söyleyeyim, Bu yaklaşımın güzel yani şu: hem kullanıcı yolculuğunu hem de altyapı davranışını aynı test senaryosunda yakalıyorsun. Kötü yani da var tabii — testler biraz ağırlaşıyor ve kurulum başta göz korkutuyor. Ama dürüst olayım, distributed system test ediyorsan zaten kolay hayat sana göre değil. Seçim o zaten.
Kurulum mantığı: Önce lokal ortamı ayağa kaldır
Bak şimdi, Lafı gevelemeden söyleyeyim: önce yerelde çalışan bir Kafka ortamı kurman lazım. Docker burada hayat kurtarıyor çünkü Confluent image’larıyla yarım saatte ayağa kalkabilecek bir düzen kuruyorsun. Zookeeper konusu bazılarına eski moda geliyor olabilir — haklılar da bir açıdan —. Bu örnekte klasik kurgu hâlâ iş görüyor, geçiyoruz.
Aşağıdaki yapı kaba taslak olarak yeterli olur; üretimde elbette daha sağlam ağ ayarları, kalıcılık ve güvenlik detayları gerekir ama test ortamında işini görüyor:
version: '3.8'
services:
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
kafka:
image: confluentinc/cp-kafka:7.5.0
depends_on:
— zookeeper
ports:
— "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
Küçük bir detay: Bunu ben en son Şubat 2025’te evdeki masaüstünde denedim; Docker Desktop açıldıktan sonra sistem fanı hafif bağırdı ama kurulum sorunsuz ilerledi. Bir dakika, şunu da ekleyeyim: healthcheck koymazsan “container up” sanırsın ama broker henüz hazır değildir, sonra testler saçma sapan timeout verir. Başa bela. O yüzden ihmal etme. Daha fazla bilgi için Yerelde Çalışan Yapay Zekâ: CISO’nun Görmediği Yeni Risk yazımıza bakabilirsiniz.
Küçük startup için bu setup gayet yeterli olabilir; hatta fazla bile gelir bazen. Ama enterprise tarafta replication factor, security config, schema registry ve network isolation gibi şeyler devreye girince oyun değişiyor… orası artık çocuk parkı değil.
| Bileşen | Küçük ekip | Kurumsal ortam |
|---|---|---|
| Kafka kurulumu | Docker Compose ile hızlı başlangıç | Kubernetes veya managed service |
| Test kapsamı | Kritik akışlar | Tüm event zinciri + edge case’ler |
| Saklama/izleme | Lokal log yeterli olabilir | Metrikler, tracing ve audit şart |
Playwright tarafı neden iyi çalışıyor?
Playwright’ın olayı sadece hızlı olması değil. Senaryo kontrolünü çok temiz veriyor. Kullanıcı login oluyor mu, form submit ediyor mu, hangi butona basıyor… bunların hepsini okunabilir biçimde yazabiliyorsun. Ben bunu özellikle dashboard yoğun projelerde seviyorum çünkü test dosyası birkaç hafta sonra bile okunabiliyor — yani öyle mistik regex cehennemi olmuyor. Bakıyorsun, anlıyorsun, geçiyorsun.
Ekrandan olaya geçiş nasıl kuruluyor?
Düşünce aslında basit görünüyor ama hayati nokta şu: UI aksiyonu tetiklenince backend’in ürettiği event’i yakalayacak ayrı bir consumer süreci gerekiyor. Playwright sahneyi başlatıyor. Kafka consumer ise perde arkasını dinliyor. İkisi birlikte çalışıyor.
Bunu şöyle düşün; restoran siparişi veriyorsun (UI), mutfakta fiş kesiliyor (event), garson yemeği getiriyor (consumer sonucu). Sadece masada boş tabak görmek yetmez, mutfağın doğru siparişi aldığına emin olmak istersin ya… aynen öyle. Güzel bir analoji, değil mi?
- Kullanıcı aksiyonunu Playwright ile tetikle.
- Aksiyon sonrası oluşan unique identifier’ı al.
- Kafka consumer ile ilgili topic’i dinle.
- Gelen mesajın schema ve içerik kontrolünü yap.
Kafkajs ile tüketici yazmak zor mu?
Açıkçası hayır, ama dikkat istiyor. Bilhassa de offset yönetimi ve timeout davranışı önemli oluyor çünkü test koşarken sonsuza kadar beklemek istemezsin — itiraf edeyim, beklentimin üstündeydi —. Kasım 2024’te yaptığım bir denemede consumer grup adını sabit bırakmıştım; ikinci koşuda eski offset yüzünden yeni mesaj gelmedi sanıp paniklemiştim. Sorun bende çıktı tabii. Hep öyle. O yüzden testlerde izolasyon şart, bunu atlama. Bu konuyla ilgili Hormuz Gerilimi Petrolü Zıplattı: Piyasada Neler Oluyor? yazımıza da göz atmanızı tavsiye ederim.
import { Kafka } from 'kafkajs';
const kafka = new Kafka({
clientId: 'e2e-test',
brokers: ['localhost:9092'],
});
export async function waitForMessage(topic, matcherFn, timeoutMs = 10000) {
const consumer = kafka.consumer({ groupId: 'test-group-' + Date.now() });
await consumer.connect();
await consumer.subscribe({ topic, fromBeginning: false });
return new Promise(async (resolve, reject) => {
const timer = setTimeout(async () => {
await consumer.disconnect();
reject(new Error('Mesaj zaman aşımı'));
}, timeoutMs);
await consumer.run({
eachMessage: async ({ message }) => {
const value = message.value?.toString();
if (matcherFn(value)) {
clearTimeout(timer);
await consumer.disconnect();
resolve(value);
}
}
});
});
}
Tasarlarken nerede tökezlenir?
En büyük tuzak flaky testlerdir. Mesaj gecikir, network dalgalanır, topic henüz dolmamıştır — sen de “test bozuk” dersin, halbuki sorun timing’dedir. Burada sabit sleep koymak çoğu zaman kötü fikirdir; bunun yerine polling ya da event-driven bekleme çok daha temiz durur. Ben kendi projelerimde genelde maksimum bekleme süresini kısa tutup retry mekanizmasını kontrollü kullanıyorum. Şimdiye kadar işe yaradı. Bu konuyla ilgili Vecstore mı Imagga mı? Görsel Aramada Asıl Fark Nerede yazımıza da göz atmanızı tavsiye ederim.
Açıkçası, Bana göre asıl hayal kırıklığı burada çıkıyor zaten: testlerin stabil görünmesi için ekstra mühendislik harcıyorsun, ama karşılığında gerçek bir güven kazanıyorsun (kendi tecrübem). Kağıt üstünde lüks gibi duran şey pratikte ciddi hata önlüyor. Yine de her akışı E2E’ye boğmaya gerek yok; bazı kontroller unit ya da integration seviyesinde çok daha mantıklı kalıyor. Hani her şeyi uçtan uca koşturayım dersen CI süren şişer de şişer! Ramen.tools Neden Bu Kadar Çekici? SaaS Gösterişinin Yeni Yüzü yazımızda da bu konuya değinmiştik. OpenAI ile Musk Davası: Son Dakika Hamlesi Ne Anlatıyor? yazımızda da bu konuya değinmiştik.
Sorunsuz çalışan akış için neye bakmalı?
- Mesaj içinde correlation id var mı? (bence en önemlisi)
- Payload alanları schema ile uyumlu mu? (bu kritik)
- Zaman aşımı makul mü?
- Aynı testi paralel koşunca çakışma oluyor mu?
Performans tarafını da unutma. Hele bir de büyük ekiplerde aynı pipeline içinde on tane benzer E2E varsa build süresi uzuyor. Bunun ilacı biraz disiplin, biraz da seçicilik… her şeyi teste sokmak yerine kilit müşteri yolculuklarını korumak çok daha sağlıklı olur.
Küçük startup ile kurumsal yapı aynı değil
Şunu fark ettim: Küçük startup’larda genelde hız baskısı vardır. İki geliştirici, bir ürün sahibi, bir de sürekli acil çıkan bug listesi… orada amaç sistemi korumak kadar hareket kabiliyetini kaybetmemek olur. Bu yüzden minimal Docker Compose + Playwright + kafkajs üçlüsü fazlasıyla işe yarar. Gerçekten.
Kurumsal tarafta ise hikâye farklıdır; güvenlik politikaları, özel ağ segmentleri, topic izinleri, secret yönetimi… hepsi masaya gelir. Ve açık konuşayım, o noktada örnek repo seni ancak başlangıca taşır. Gerisini kendin çözeceksin.
Bence ideal yaklaşım ne?
Cevabım net değilmiş gibi gelebilir ama aslında şu: kritik iş akışlarını seç, onları uçtan uca doğrula, geri kalan yerde daha hafif katmanlara yaslan. Mesela ödeme oluşturma, abonelik aktivasyonu ya da stok düşümü gibi olaylar E2E için biçilmiş kaftan. Ama her küçük modal pencereyi de Kafka üzerinden sınamaya kalkarsan iş kontrolden çıkar. Söz veriyorum.
Daha sağlam hale getirmek için birkaç pratik ipucu
Ha bu arada önemli nokta şu: mesaj doğrulamasını string eşleşmesine hapsetme (ciddiyim). Schema validation yapabilirsen çok daha rahat edersin; özellikle JSON payload büyüdükçe küçük farklar gözden kaçabiliyor. Bir bakarsan alanın tipi değişmiş, bir bakarken fazladan nested obje girmiş… string karşılaştırması bunları yakalamaz.
Bunu biraz açayım.
Bi saniye — Bir diğer püf nokta da unique test data kullanmak. Aynı kullanıcı adı ya da order id ile tekrar tekrar koşarsan eski mesajlar seni yanıltabilir. Çok basit ama çok sık atlanan bir şey bu.
- Etkileşim başına benzersiz ID üret.
- Tetiklenen event’i correlation id ile takip et.
- Mümkünse schema contract kullan.
Bu içerik işinize yaradı mı?
Benzer içerikleri kaçırmamak için beni sosyal medyada takip edin.



