JAVA TESTCONTAINERS NEDİR?

Testcontainers bir unit test kütüphanesi değil — yaygın yanılgı tam burada başlıyor. Çoğu geliştirici "Mockito ile mock'ladığım servis testi yeterli" diye düşünüp Testcontainers'ı gereksiz bir yük olarak görüyor. Oysa mock'lanmış bir PostgreSQL ile gerçek bir PostgreSQL'in davranışı arasında sessiz uçurumlar var: trigger, transaction izolasyonu, lock, sequence... Test ortamında geçen kod, production'da migration sırasında patlayabiliyor. "Docker testleri yavaşlatır" söylemi de çoğu zaman ölçülmeden tekrarlanan bir refleks; doğru kullanıldığında container reuse ve singleton pattern ile bu maliyet sanıldığından çok daha düşük.
1. Testcontainers Tam Olarak Nedir?
Testcontainers, JVM tabanlı test süreçlerinde Docker container'larını programatik olarak başlatıp durduran açık kaynaklı bir Java kütüphanesidir. Test sınıfı çalışmaya başladığında bir PostgreSQL, Kafka, Redis, RabbitMQ veya Selenium container'ı ayağa kalkar; test bittiğinde otomatik temizlenir. Yani testin ihtiyacı olan altyapıyı dışarıdan kurmanız beklenmez — test kodunun kendisi altyapıyı tanımlar.
Bu, "test edilebilirliği" tamamen değiştirir. Artık CI sunucusunda "PostgreSQL kur, şu portu aç, şu kullanıcıyı oluştur" gibi bir hazırlık dökümanı yoktur. Docker daemon'u olan her yerde test aynı davranır. Bu yaklaşımın Java tarafındaki test otomasyonu pratikleriyle nasıl bütünleştiğini görmek için Java test otomasyonu eğitimi kaynaklarından yararlanabilirsiniz.
2. Integration Test ≠ Unit Test: Sınırı Doğru Çizmek
Burası en çok karıştırılan yer. Unit test, tek bir sınıfın iç mantığını izole biçimde doğrular — dış bağımlılıklar mock'lanır, milisaniyeler içinde koşar, binlercesi birkaç saniyede biter. Testcontainers ise bu katmanda DEĞİL, integration test katmanında çalışır.
- Unit test: Bir
PriceCalculator.calculate()metodu, mock repository ile saniyenin altında çalışır. Burada Testcontainers anlamsızdır. - Integration test: Repository'nin gerçekten PostgreSQL'e doğru SQL ürettiğini, JSONB kolonunun beklenen davranışı sergilediğini, unique constraint'in ihlal edildiğinde doğru exception fırlattığını doğrular. Burada mock yetmez.
- End-to-end test: Tüm sistemin uçtan uca davranışı. Testcontainers burada da çok güçlüdür çünkü tüm bağımlılıkları tek komutla ayağa kaldırır.
Yani Testcontainers, unit testlerin yerini almaz — onların altında yatan, görmezden gelinen "gerçek altyapı davranışı" katmanını test edilebilir hale getirir.

3. "Docker Yavaşlatır" Yanılgısı Gerçekten Doğru mu?
Bu, ölçüm yerine sezgi ile söylenen bir cümle. Modern Docker üzerinde PostgreSQL container'ı ortalama 2-4 saniyede ayağa kalkar. Eğer her test metodunda yeni container açıyorsanız evet, yavaş olur. Ancak Testcontainers bunun çözümünü zaten sunuyor:
- Singleton container pattern: Test sınıflarının tamamı boyunca tek bir container instance'ı yaşar.
- Reusable containers:
.withReuse(true)ile local geliştirmede container'ı testler arası ayakta tutarsınız — ilk testten sonra başlatma maliyeti sıfırdır. - Ryuk container: Test process'i çökse bile orphan container'lar otomatik temizlenir, manuel temizliğe gerek kalmaz.
- Parallel test execution: Aynı anda birden fazla container ile paralel test koşulabilir.
Bir Spring Boot projesinde 200 entegrasyon testi, container reuse ile 30-40 saniyede biter. Aynı testlerin H2 in-memory üzerinde "hızlı" görünüp production'da PostgreSQL-spesifik bir hata bırakmasının maliyeti ise saatlerce sürebilen bir debugging'dir.
4. Basit Bir Kullanım Örneği
JUnit 5 ile PostgreSQL container'ı kullanan tipik bir test sınıfı şöyle görünür:
@Testcontainersannotasyonu JUnit'e container yaşam döngüsünü yönetmesini söyler.@Containerile işaretlenen field, test başlamadan başlatılır, bittiğinde durdurulur.PostgreSQLContainer<>("postgres:16-alpine")ile istenen versiyon sabitlenir — production hangi sürümü kullanıyorsa test de onu kullanır.container.getJdbcUrl(),getUsername(),getPassword()metodlarıyla bağlantı bilgileri dinamik alınır; sabit port çakışması olmaz.
Spring Boot ile entegrasyon yapıyorsanız @DynamicPropertySource kullanarak container'ın URL'ini Spring context'ine geçirebilirsiniz. Böylece application.yml'i değiştirmeden test ortamı oluşur.
5. Hangi Bağımlılıklar Test Edilebilir?
Testcontainers'ın gücü, Docker'da çalışabilen hemen her şeyi test edebilmesinden gelir. Yaygın kullanım alanları:
- Relational DB modülleri: PostgreSQL, MySQL, MariaDB, Oracle XE, MS SQL Server, CockroachDB.
- NoSQL modülleri: MongoDB, Cassandra, Redis, Elasticsearch, Neo4j.
- Mesajlaşma: Kafka, RabbitMQ, ActiveMQ, Pulsar.
- Cloud emulator: LocalStack (AWS S3, SQS, DynamoDB taklidi), Azurite.
- Browser otomasyonu: Selenium Chrome/Firefox container'ları ile UI testleri.
- Generic container: Custom Dockerfile veya herhangi bir image — örneğin internal bir mikroservisi sahte servis olarak ayağa kaldırma.
6. Ne Zaman Kullanmamalı?
Her şeyi Testcontainers'la test etmek de yanlıştır. Şu durumlarda zorlamayın:
- Saf algoritma testleri — örneğin bir sort fonksiyonu için container açmak absürt olur.
- Servis sınıfının iş kuralları — repository'yi mock'layıp domain mantığını izole etmek daha hızlıdır.
- Docker'ın hiçbir biçimde çalışamayacağı, kısıtlı CI ortamları (bu durumda CI'yi değiştirmek genelde daha doğru cevaptır).
Yani araç, sorunla eşleştiğinde değer üretir. Modül listesinin tamamı ve her birinin yapılandırma seçenekleri için resmi dokümantasyona başvurmak en güvenilir yoldur. Repository ve adapter katmanlarında Testcontainers, domain katmanında klasik unit test — ikisi birbirini tamamlar.

7. Production Davranışını Test Etmenin Tek Dürüst Yolu
H2 ile geçen test, production'da PostgreSQL'in ON CONFLICT davranışını yakalayamaz. Mock'la geçen test, gerçek Kafka consumer'ın rebalance sırasında nasıl davrandığını göremez. Embedded MongoDB ile geçen test, gerçek MongoDB'nin index hint davranışını yansıtmaz. Testcontainers bu "gibi gibi" yaklaşımını sona erdirir: test ettiğiniz şey, üreteceğiniz şeydir. Spring, Quarkus, Micronaut gibi modüler framework'lerin tamamı Testcontainers ile birinci sınıf entegrasyon sunduğundan, projeyi kütüphaneye uydurmak için ekstra çaba da gerekmez. Doğru katmanda, doğru ölçekte kullanıldığında testler hem güvenilir hem makul hızda kalır.
Sıkça Sorulan Sorular
Testcontainers unit test için mi yoksa integration test için mi kullanılır?
Testcontainers integration ve end-to-end test katmanları için tasarlanmıştır. Unit testlerde dış bağımlılıklar mock'lanır ve milisaniye düzeyinde koşulması beklenir. Repository, adapter, mesajlaşma katmanları gibi gerçek bağımlılıkla doğrulama gerektiren yerlerde Testcontainers devreye girer. Yani unit testin yerini almaz, onun göremediği altyapı davranışlarını test edilebilir kılar.
Docker container'ları testleri çok yavaşlatmıyor mu?
Ölçülmeden tekrar edilen bir yanılgı. Modern Docker'da PostgreSQL container'ı 2-4 saniyede ayağa kalkar. Singleton container pattern, .withReuse(true) ve paralel test execution ile bu maliyet test koşusunun başında bir kez ödenir. 200 testlik bir integration suite genelde 30-40 saniyede biter; H2 ile hızlı görünüp production'da patlayan testlerden çok daha ucuzdur.
Testcontainers ile H2 in-memory database arasındaki fark nedir?
H2, PostgreSQL veya MySQL'in dialect taklidini yapar ama gerçek bir kopyası değildir. JSONB, partial index, advisory lock, sequence davranışı, exception sınıfları farklıdır. Testcontainers ise production'da hangi database varsa onun gerçek imajını çalıştırır. Bu yüzden Testcontainers'la geçen test, production davranışına dair gerçek bir garanti sunar; H2 testi yalnızca CRUD katmanını gevşek biçimde doğrular.
Testcontainers'ı CI/CD pipeline'ında çalıştırmak için ne gerekiyor?
CI runner'ın Docker daemon'una erişiminin olması yeterlidir. GitHub Actions, GitLab CI, Jenkins, CircleCI gibi yaygın platformların hepsi Docker-in-Docker veya bind-mount edilmiş Docker socket ile bunu destekler. Ryuk container, build düşse bile orphan container'ları temizler. Tek pratik kısıt: tamamen Docker'sız, kısıtlı kurumsal CI ortamlarıdır.
Spring Boot ile Testcontainers nasıl entegre olur?
@Testcontainers ve @Container annotasyonlarıyla container tanımlanır, @DynamicPropertySource ile container'ın dinamik URL, kullanıcı adı ve parolası Spring context'ine geçirilir. Spring Boot 3.1 sonrası ServiceConnection özelliği ile bu da gereksizleşti; @ServiceConnection annotasyonu ile container otomatik olarak Spring DataSource veya KafkaTemplate'e bağlanır. application.yml'de hiçbir değişiklik gerekmez.
Hangi tür projelerde Testcontainers kullanmak gereksizdir?
Saf algoritmik kod, domain mantığı ve dış bağımlılığı olmayan servis sınıfları için Testcontainers gereksizdir; mock veya pure unit test daha hızlıdır. Ayrıca Docker'ın hiçbir biçimde çalışamayacağı kısıtlı ortamlarda zorlamak yerine CI altyapısını değiştirmek daha doğrudur. Her testi container'la koşmak suite süresini gereksiz şişirir.
Testcontainers sadece Java için mi vardır?
Hayır. Java ana implementasyon olmakla birlikte Node.js, Python, Go, .NET, Rust ve Haskell için resmi veya topluluk destekli portlar mevcuttur. Hepsi aynı temel fikri paylaşır: Docker container'ını test kodundan programatik biçimde yönetmek. Polyglot ekiplerde aynı test stratejisini farklı dillerde uygulamak bu sayede mümkün olur.
Reusable container özelliği production CI'da kullanılmalı mı?
Hayır, sadece local geliştirme için tasarlanmıştır. .withReuse(true) container'ı testler arası ayakta tutar, bu yerel iterasyon hızını artırır. CI ortamında ise her build izole olmalı; reusable container kalıntı state ile yanıltıcı sonuçlar üretebilir. CI'da Ryuk'un temizlik mekanizmasına güvenip her run'da temiz container açmak doğru pratiktir.


