Testcontainers — Gerçek Veritabanı, Tek Konteyner, Kilitli Kapı
Önce şunu oku: Spring Test Dilimleri ve Context Cache
30 saniyede özet
Testcontainers testlere gerçek bir PostgreSQL verir. Konteyneri nerede başlattığın, kaç kez başlayacağını ve Spring'in context cache'iyle anlaşıp anlaşamayacağını belirler. Yanlış yerde başlatırsan testler eski porta bağlanmaya çalışır.
Testlerin bilgisayarında hep yeşil. Sorgu canlıda ilk gün patlıyor, çünkü testler başka bir veritabanında koşuyordu. Bu derste testlere gerçeğini vereceğiz ve bunu yaparken düşülen küçük bir çukuru göreceğiz.
-
Bayt: Testlerde gerçek PostgreSQL kullanıyorum artık. Konteyneri ortak bir taban sınıfa koydum.
-
Sen: Süper. Hepsi geçiyor mu?
-
Bayt: İlk sınıf geçiyor. Gerisi 'Connection refused' diyor!
-
Bayt: Veritabanı ayakta. O zaman testler yanlış kapıyı çalıyor olmalı.
Neden gerçek veritabanı?
H2 gibi bellekteki bir veritabanı hızlıdır ama PostgreSQL değildir. ON CONFLICT, jsonb ya da kilitleme davranışı farklıdır. Testte geçen sorgu canlıda patlayabilir.
TestcontainersTest sırasında Docker'da gerçek bağımlılıklar (PostgreSQL, Kafka, Redis) başlatan kütüphane. Testler canlıdaki yazılımın aynısıyla koşar.Sözlükte gör → test sırasında Docker’da gerçek bir PostgreSQL başlatır. Testler, canlıdaki veritabanının aynı sürümüyle konuşur.
Kafam karıştı, daha basit anlat
Prova, gerçek sahnede yapılırsa sürpriz azalır. Testcontainers testlere gerçek sahneyi kurar.
Testlerde H2 yerine Testcontainers ile PostgreSQL kullanmanın asıl kazancı nedir?
INSERT … ON CONFLICT kullanan bir sorgu H2'de test ediliyor. Ne beklemelisin?
İki ayrı hafıza
Spring, kurduğu uygulama context’ini bir context cacheSpring'in test bağlamlarını yapılandırmaya göre önbelleğe alması. Anahtar anotasyonlar, property'ler, profiller ve mock'lardır — bir `@MockBean` farkı yeni bir bağlam demektir.Sözlükte gör →’te saklar. Aynı ayarlarla çalışan bir sonraki test sınıfı yeni context kurmaz, cache’tekini kullanır.
Testcontainers’ın JUnit eklentisi ise @Container ile işaretli statik bir konteyneri her test sınıfının başında başlatır, sonunda durdurur. İki sistem de kendi işini doğru yapıyor, ama birbirinden habersiz.
Konteyner ortak bir taban sınıfta, static ve @Container ile işaretli. Bağlantı adresi taban sınıftaki @DynamicPropertySource ile veriliyor. İkinci test sınıfı çalışınca ne olur? Cevabı göster
Connection refused, çünkü JUnit konteyneri ilk sınıftan sonra durdurdu ve yeni bir portta yeniden başlattı. Spring ise aynı ayarları görüp cache’teki context’i verdi. O context hâlâ eski portu tutuyor.
Adım adım oku
- İlk grup oda 101'deki mutfakta: testler geçti.
- JUnit konteyneri durdurdu ve yenisini başka bir portta başlattı.
- Spring cache'teki context'i verdi; o context hâlâ eski adresi gösteriyor.
- Çözüm: konteyneri bir kez başlat ve bütün sınıflar onu kullansın.
Kafam karıştı, daha basit anlat
Spring çizelgeyi bir kez yazıp saklıyor, JUnit mutfağı her seferinde başka odaya taşıyor. İkisi anlaşamayınca kapı kilitli kalıyor.
Spring'in test context cache'i bir context'i neye göre yeniden kullanır?
Taban sınıfta @Testcontainers ve static @Container var. İki alt sınıf aynı context'i paylaşıyor. İkinci sınıf neden 'Connection refused' alır?
Bir kez başlat
İlk yol: konteyneri taban sınıfta @Container olmadan, statik bir blokta bir kez start() ile başlatırsın. JVM kapanınca Testcontainers’ın temizlik konteyneri onu kaldırır.
İkinci yol, Spring Boot 3.1 ile geldi: konteyneri ortak bir test ayarında bean olarak tanımlar, üstüne @ServiceConnectionSpring Boot 3.1 ile gelen işaret: bir konteyner bean'inin adresini ve kimlik bilgilerini bağlantı ayarlarına kendiliğinden yazar.Sözlükte gör → yazarsın. Bağlantı ayarları kendiliğinden verilir ve konteyner, context yaşadıkça yaşar.
Kafam karıştı, daha basit anlat
Mutfağı bir kez aç ve her grubu aynı odaya gönder. Ya elle açarsın ya da mutfağı okulun kendisine emanet edersin.
Bütün entegrasyon testleri için tek bir konteyner ve tek bir context istiyorsun. Hangi yol doğru?
Kendin gör
Dört test sınıfı, kaç konteyner?
Tohum 147665- AccountRepositoryITbekliyor
- TransferServiceITbekliyor
- StatementQueryITbekliyor
- LedgerMigrationITbekliyor
Şu an ne oldu?
Her sınıf kendi konteyneri
Dört test sınıfı aynı PostgreSQL'e ihtiyaç duyuyor. Soru: kaç kez başlatılacak ve Spring context'i kaç kez kurulacak?
Görevler0/3
Bir test sınıfını "Connection refused" ile düşüraçık
İpucu
Konteyneri JUnit yönetsin, context'i Spring cache'lesin.
Dört konteyner ve dört context başlataçık
İpucu
Herkes kendi işini kendisi yapsın.
Dört sınıfı tek konteyner ve tek context ile, hatasız çalıştıraçık
İpucu
İki yolu var.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanla oynat. Her sınıf kendi konteynerini ve kendi context’ini kurdu: dört ve dört.
- “Ortak taban sınıf, @Container” seç. İlk sınıf geçti, diğerleri eski porta çarptı.
- “Statik blokta start()” seç. Bir konteyner, bir context, hepsi geçti.
- “@ServiceConnection bean” seç. Aynı sonuç, daha az kod.
Her test sınıfı kendi static @Container'ını ve kendi @DynamicPropertySource metodunu tanımlıyor. Dört sınıf için ne olur?
Tuzaklar
Paylaşılan veri. Bütün sınıflar aynı veritabanını kullanır. Bir testin bıraktığı satır diğerini şaşırtır; her test kendi verisini kursun ya da transaction içinde geri alınsın.
latest etiketi. postgres:latest bir gün sessizce yeni bir ana sürüme geçer. Canlıdaki sürümü sabit yaz: postgres:16-alpine gibi.
Docker’sız makine. CI’da Docker yoksa testler başlamaz. Bu bir test hatası değil, ortam eksiği; CI’ı buna göre kur.
Kafam karıştı, daha basit anlat
Ortak mutfakta herkes kendi tabağını getirir ve yıkar. Fırının modelini de sabit tut.
Bütün testler tek PostgreSQL konteynerini paylaşıyor. Ara sıra bir test, başka bir testin eklediği satırı görüp düşüyor. En doğru düzeltme nedir?
Aşağıdaki örnek bir bankanın hesap servisinden: bütün entegrasyon testleri tek PostgreSQL konteynerini ve tek Spring context’ini paylaşıyor.
Derinleş · Hesap servisi: tek konteyner, tek context 5 dosya · ~66 satır · ilk okumada atlayabilirsin
Kendini sına
H2'de geçen bir sorgu PostgreSQL'de de kesin geçer.
AccountRepositoryIT @DataJpaTest, TransferServiceIT @SpringBootTest; ikisi de @ServiceConnection bean'li aynı ayarı içe aktarıyor. Kaç konteyner başlar?
Aklında kalacak üç şey
- 1 Testcontainers testlere üretimdeki veritabanının aynısını verir. H2'de geçip PostgreSQL'de patlayan sorgular böylece testte yakalanır.
- 2 Spring context'i cache'ler, ama konteynerin hâlâ çalışıp çalışmadığını bilmez. JUnit'in her sınıfta durdurup yeniden başlattığı bir konteyner, cache'teki context'i eski bir porta bağlı bırakır.
- 3 Paylaşılan konteyner ya bir kez elle başlatılır ya da @ServiceConnection ile bir bean yapılır. Böylece bütün sınıflar tek konteyner ve tek context kullanır.
4 kart sonraki derste seni bekliyor