Spring Test Dilimleri ve Context Cache
Önce şunu oku: Test Piramidi, JUnit 5 ve Mockito
30 saniyede özet
Test dilimleri (@WebMvcTest gibi) uygulamanın yalnızca bir parçasını açar. Bütün test süresini sanıldığı kadar düşürmezler, çünkü Spring açtığı uygulamayı zaten testler arasında paylaşır. Asıl yavaşlık farklı ayarların sayısından gelir.
Testler yavaşlayınca akla ilk gelen, uygulamanın tamamı yerine yalnızca küçük bir parçasını açmak olur. Doğru yönde bir fikir — ama neden işe yaradığı çoğu zaman yanlış biliniyor.
-
Bayt: Test suite'imiz on dakika sürüyor! Hepsi @SpringBootTest, suç onda.
-
Sen: Hepsini küçük parçalara bölelim o zaman.
-
Bayt: Böldüm. Toplam süre neredeyse hiç değişmedi...
-
Bayt: Demek ki yavaşlığın asıl sebebi başka bir yerde.
Spring açtığını saklar
Spring test context’ini test sınıfı başına değil, yapılandırma başına oluşturur. Sekiz sınıf aynı annotation ve aynı ayarları kullanıyorsa context bir kez başlar, yedi kez cache’ten gelir.
Yani “sekiz @SpringBootTest sınıfı = sekiz uygulama başlatma” yanlıştır. Bu tek bilgi, suite süresini nereden düşüreceğini belirler.
Kafam karıştı, daha basit anlat
Spring, aynı ayarlarla kurulan test ortamını bir kez açar ve saklar. Sekiz test sınıfı aynı ayarı kullanıyorsa, uygulama sekiz kez değil bir kez başlar.
Kendin gör
Adım adım oku
- @SpringBootTest uygulamanın bütün katmanlarını açar: web, servis, veritabanı, güvenlik.
- @WebMvcTest gibi bir dilim yalnızca test ettiğin katmanı açar; tek sınıfı çalıştırmak çok hızlanır.
- Spring açtığı uygulamayı ayara göre saklar; aynı ayardaki test sınıfları tek bir açılışı paylaşır.
- @MockitoBean ya da farklı bir profil yeni bir ayar demektir ve yeni bir açılışa yol açar.
Spring test dilimleri — context cache, geri bildirim süresi ve izolasyon
Tohum 4UserControllerTest@SpringBootTest6 testOrderControllerTest@SpringBootTest5 testAuthControllerTest@SpringBootTest4 testUserRepositoryTest@SpringBootTest5 testOrderRepositoryTest@SpringBootTest4 testUserServiceTest@SpringBootTest8 testPricingServiceTest@SpringBootTest6 testCheckoutFlowIT@SpringBootTest3 test
Başlatılan context’ler — 0/1
- @SpringBootTest
Toplam suite
0.0 sn
12.0 sn başlatma + 16.4 sn test
Tek sınıf (soğuk)
14.4 sn
geliştiricinin günde onlarca kez ödediği süre
En büyük context
214 bean
1 farklı context · 0 cache isabeti
Şu an ne oldu?
Suite başlıyor
8 sınıf, 41 test. Spring her farklı yapılandırma için ayrı bir context başlatır ve onu suite boyunca önbellekte tutar.
Aklında kalsın: Test context’i test sınıfı başına değil, yapılandırma başına oluşturulur.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
Sırayla dene ve ortadaki kutuya dikkat et:
Her şey @SpringBootTest. Toplam ~28 sn, tek context. Tek sınıfı çalıştırmak 14.4 sn.Dilimlere geç. Toplam neredeyse aynı (~28 sn). Ama tek sınıf 2.9 sn — beş kat.Dilimler + birim testi. Toplam 22 sn’ye düşüyor: servis testleri Spring’e hiç ihtiyaç duymuyor.@MockitoBeananahtarını aç (1. kurulumda). Context 1’den 2’ye çıkıyor, süre +12 sn.- Testcontainers’ı aç. Veritabanına bağlanan her context ayrı konteyner başlatıyor.
Spring test bağlamı (application context) testler arasında yeniden kullanılır mı?
Dilimler neyi açar?
Bir controller testi @WebMvcTest ile yazıldı. Controller bir TransferService'e ihtiyaç duyuyor. Bu servis context'te kendiliğinden var mıdır? Cevabı göster
Hayır. @WebMvcTest yalnızca web katmanını açar; servisi @MockitoBean ile sen vermelisin.
| Annotation | Yükler | Yüklemez |
|---|---|---|
@WebMvcTest | Controller, @ControllerAdvice, filtreler, Jackson, MockMvc | Servis, repository, DataSource |
@DataJpaTest | Entity, repository, DataSource, TestEntityManager | Controller, servis, web katmanı |
@SpringBootTest | Her şey | — |
Dilimin yüklemediği her bean @MockitoBean ile verilmelidir. @WebMvcTest içinde controller’ın çağırdığı servis mock’lanır — zaten test edilen şey controller’dır.
@DataJpaTest bir ek davranış daha getirir: her testi transaction içinde çalıştırır ve sonunda geri alır. Testler birbirinin verisini görmez.
Kafam karıştı, daha basit anlat
Dilim, uygulamanın yalnızca bir parçasını açmaktır: @WebMvcTest sadece web katmanını, @DataJpaTest sadece veritabanı katmanını. Daha az şey açılır, test daha hızlı başlar.
`@SpringBootTest` ile `@WebMvcTest` arasındaki fark nedir?
Asıl kazanç: geri bildirim süresi. Simülatördeki ikinci adım dersin özeti. Toplam süre değişmedi ama tek sınıfı çalıştırmak 14.4 sn’den 2.9 sn’ye düştü.
Bir geliştirici gün içinde tüm suite’i birkaç kez, tek bir test sınıfını ise onlarca kez çalıştırır. Optimize edilmesi gereken sayı budur.
İkinci kazanç ölçülemez ama daha değerlidir: izolasyon. @WebMvcTest kırmızıysa web katmanı bozuktur. @SpringBootTest kırmızıysa her şey olabilir — ve hata ayıklama oradan başlar.
`@WebMvcTest(UserController.class)` çalışırken hangi bean'ler context'te bulunur?
Tuzaklar: çok fazla farklı ayar
Cache anahtarı yapılandırmadır. Aşağıdakilerin her biri yeni bir context demektir:
@MockitoBean PaymentClient paymentClient; // farklı bean grafiği@ActiveProfiles("integration") // farklı profil@TestPropertySource(properties = "...") // farklı property@SpringBootTest(webEnvironment = RANDOM_PORT) // farklı web ortamıOtuz test sınıfının her biri kendi @MockitoBean kombinasyonunu kullanıyorsa, otuz kez uygulama başlatırsın. Suite’in on dakika sürmesinin sebebi @SpringBootTest değil, otuz farklı yapılandırmadır.
Pratik kural: mock kombinasyonlarını standartlaştır. Ortak bir @TestConfiguration veya bir temel sınıf, tüm entegrasyon testlerinin aynı context’i paylaşmasını sağlar.
İlginç bir sonuç: simülatörde 3. kurulumda @MockitoBean eklemek hiçbir şeye mal olmuyor — çünkü o context’i zaten tek bir sınıf kullanıyordu. Bean override’ın maliyeti override’ın kendisinde değil, paylaşılan bir context’i bölmesindedir.
Servis testlerine Spring gerekmez. En büyük kazanç aslında en basit olanı:
class PricingServiceTest { private final DiscountPolicy policy = mock(DiscountPolicy.class); private final PricingService service = new PricingService(policy);
@Test void appliesDiscount() { ... } // context yok, 10 ms}Constructor injection kullanıyorsan servis sınıfı düz bir Java nesnesidir. Onu test etmek için uygulama başlatmak, çekiç yerine vinç kullanmaktır.
Bu, field injection’dan kaçınmanın en somut gerekçesidir: @Autowired bir alana yazıldığında sınıfı Spring olmadan kuramazsın.
Kafam karıştı, daha basit anlat
Her farklı ayar, yeni bir test ortamı demektir. Ayarları birkaç ortak sınıfta topla ki Spring aynı ortamı tekrar tekrar kullanabilsin.
`@MockBean` ile `Mockito.mock()` arasındaki fark nedir?
Testcontainers
H2 hızlıdır ama Postgres değildir. Farklı SQL lehçesi, farklı kilitleme davranışı, farklı tip sistemi — üretimde patlayan sorgu testte geçer.
Testcontainers gerçek veritabanını kullanır. Bedeli konteyner başlatma süresidir ve simülatörde bu açıkça görünür.
Çözüm, konteyneri context başına değil JVM başına bir kez başlatmaktır:
abstract class IntegrationTestBase { static final PostgreSQLContainer<?> DB = new PostgreSQLContainer<>("postgres:16");
static { DB.start(); } // bir kez başlar, JVM kapanınca Ryuk temizler
@DynamicPropertySource static void props(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", DB::getJdbcUrl); }}Bu desen aynı zamanda tüm entegrasyon testlerini tek bir temel sınıfta toplayarak context sayısını da düşürür — iki sorunu birden çözer.
Bir banka hesap servisinde bu katmanlar nasıl yan yana durur, bakalım. Her test yalnızca ihtiyaç duyduğu kadarını açıyor.
Derinleş · Hesap servisi: her katmana kendi testi 4 dosya · ~72 satır · ilk okumada atlayabilirsin
Kendini sına
Aynı ayarları kullanan test sınıfları, Spring'in tek bir açılışını paylaşır.
Bir projede 8 test sınıfı var ve hepsi aynı yapılandırmayla `@SpringBootTest` kullanıyor. Testler çalışırken Spring context kaç kez başlar?
Aklında kalacak üç şey
- 1 '@SpringBootTest yavaştır' tam doğru değil: aynı ayarlardaki test sınıfları tek bir açılışı paylaşır. Asıl soru kaç farklı açılış olduğudur.
- 2 Dilimin kazandırdığı şey toplam süre değil, tek bir sınıfı çalıştırma süresi ve netlik. @WebMvcTest patlarsa sorun web katmanındadır.
- 3 Yavaş testlerin bir numaralı sebebi fazla sayıda farklı ayar: @MockitoBean, @ActiveProfiles ve @TestPropertySource her biri yeni bir açılış demektir.
4 kart sonraki derste seni bekliyor