JAVA TESTCONTAINERS NEDİR?

Java Testcontainers ile entegrasyon testi için Docker tabanlı test container kutusu ve PostgreSQL bağlantısı

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.

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.

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.

Test piramidi: unit, integration ve e2e katmanlarında Testcontainers'ın konumunu gösteren diyagram

"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:

  1. Singleton container pattern: Test sınıflarının tamamı boyunca tek bir container instance'ı yaşar.
  2. 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.
  3. Ryuk container: Test process'i çökse bile orphan container'lar otomatik temizlenir, manuel temizliğe gerek kalmaz.
  4. 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.

Basit Bir Kullanım Örneği

JUnit 5 ile PostgreSQL container'ı kullanan tipik bir test sınıfı şöyle görünür:

  • @Testcontainers annotasyonu JUnit'e container yaşam döngüsünü yönetmesini söyler.
  • @Container ile 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.

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.

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.

Testcontainers ile PostgreSQL, Kafka ve Redis bağımlılıklarını yöneten çoklu container akış diyagramı

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.