Spring Boot entegrasyon testlerinde TestContext cache, Testcontainers ve JUnit paralelliğini birlikte tasarlayın. Ölçülebilir test süresi, kararlı veri izolasyonu ve tekrar kullanılabilir Spring context yapısını kurun.
Spring Boot Testlerinde Context Cache ve Paralel Çalışma Tasarımı
Spring Framework eğitimi perspektifiyle TestContext cache anahtarı
Kurumsal bir spring framework eğitimi veya spring boot eğitimi içinde test süresini yalnızca test sayısıyla açıklamak eksiktir. Spring TestContext Framework, ApplicationContext nesnelerini bir LRU cache içinde saklar. Cache anahtarı; test edilen configuration class'ları, aktif profiller, test property kaynakları, context customizer'lar, parent context ve resource base path gibi bileşenlerden oluşur. Aynı @SpringBootTest kullanan iki sınıfın @ActiveProfiles değerleri ya da @TestPropertySource içeriği farklıysa ikinci sınıf yeni bir context açar. Önce cache davranışını görünür hale getirin:
logging:
level:
org.springframework.test.context.cache: DEBUG
spring:
test:
context:
cache:
maxSize: 64 Test sonunda loglarda hitCount, missCount ve size değerlerini kaydedin. Hedef, mutlak bir context sayısı değil; aynı commit üzerinde değişiklik öncesi ve sonrası missCount/test-class oranını karşılaştırmaktır.Cache'i büyütmek, anahtar parçalanmasını çözmez. Özellikle her test sınıfına rastgele port, UUID veya farklı inline property koymak LRU tahliyesini hızlandırır. Ortak test ayarlarını bir profile taşıyın ve property'yi sınıf bazında değil ortak yapılandırmada sabitleyin:
@SpringBootTest
@ActiveProfiles('integration')
@Import(IntegrationTestConfig.class)
abstract class IntegrationTestBase {
} Ardından somut test sınıfları yalnızca bu tabanı genişletsin. İncelik şudur: @DirtiesContext bir test başarısız olduğunda 'temiz başlangıç' sağlamaz; ilgili context'i cache'ten çıkarır ve sonraki testlerde tam Spring başlangıç maliyeti üretir. Paylaşılan mutable singleton, static state veya veritabanı sızıntısını @DirtiesContext ile gizlemek yerine kaynağı resetlemek gerekir.Spring Boot kursu için Testcontainers yaşam döngüsü ve bağlantı maliyeti
Bir spring boot kursu örneğinde @Testcontainers ile her sınıfta non-static PostgreSQLContainer tanımlamak kolay görünür, ancak her sınıf için container başlatma ve yeni context üretme maliyetine yol açar. Container yaşam döngüsünü test JVM'i boyunca ortak tutun; Spring datasource değerlerini de tek bir @DynamicPropertySource metodundan sağlayın:
abstract class PostgresIntegrationTest {
static final PostgreSQLContainer<?> POSTGRES =
new PostgreSQLContainer<>('postgres:16-alpine')
.withDatabaseName('app')
.withUsername('app')
.withPassword('app');
static {
POSTGRES.start();
}
@DynamicPropertySource
static void databaseProperties(DynamicPropertyRegistry registry) {
registry.add('spring.datasource.url', POSTGRES::getJdbcUrl);
registry.add('spring.datasource.username', POSTGRES::getUsername);
registry.add('spring.datasource.password', POSTGRES::getPassword);
}
} Bu yaklaşım container'ı test sınıfı yerine JVM ömrüne bağlar. CI agent'ında Docker erişimi yoksa başarısızlığın uygulama context'i hatası gibi görünmemesi için test çalıştırmadan önce docker info komutunu ayrı bir pipeline adımı olarak çalıştırın.Testcontainers ortak kullanıldığında kritik edge case veritabanı temizliğidir. @Transactional test metodu sonunda rollback yalnızca aynı thread içindeki JDBC işlemlerini geri alır; HTTP ile başlatılmış bir spring rest api isteği sunucu thread'inde çalışıyorsa bu transaction'a katılmaz. Bu nedenle WebTestClient veya gerçek port üzerinden yapılan testlerden sonra tablo temizliğini açıkça yapın. PostgreSQL için test fixture'ında foreign key sırasını elle yönetmek yerine TRUNCATE ... RESTART IDENTITY CASCADE kullanın:
@Autowired JdbcTemplate jdbc;
@AfterEach
void resetDatabase() {
jdbc.execute('TRUNCATE TABLE orders, order_items, outbox RESTART IDENTITY CASCADE');
} Tablo listesini migration'lardan türetin ve yeni tablo eklendiğinde test resetinin eksik kalmaması için bir metadata doğrulama testi ekleyin.Spring MVC ve Spring Security testlerini context parçalamadan kurmak
spring mvc katmanında her endpoint için tam @SpringBootTest açmak, controller sözleşme testini JPA, mesajlaşma ve dış HTTP istemcilerinden gereksiz biçimde bağımlı hale getirir. MVC davranışı için @WebMvcTest kullanın; üretimdeki güvenlik zincirini açıkça import edin ve mock davranışını test sınıfları arasında standardize edin:
@WebMvcTest(OrderController.class)
@Import(ApiSecurityConfig.class)
class OrderControllerTest {
@Autowired MockMvc mvc;
@MockBean OrderService orderService;
@Test
void admin_can_cancel_order() throws Exception {
given(orderService.cancel(42L)).willReturn(new CancelResult(42L));
mvc.perform(post('/api/orders/42/cancel')
.with(user('ada').roles('ADMIN')))
.andExpect(status().isOk())
.andExpect(jsonPath('$.id').value(42));
}
} spring security filtreleri import edilmeden yapılan @WebMvcTest, yetkisiz isteğin 401 mi 403 mü döndüğü veya CSRF davranışı gibi üretim sözleşmelerini doğrulamaz. Buna karşılık her test sınıfında farklı @MockBean kümesi tanımlamak context customizer anahtarını değiştirir; benzer controller testleri için ortak mock konfigürasyonu oluşturun.Güvenlik testlerinde roles('ADMIN') çağrısının gerçekte ROLE_ADMIN authority'sine dönüştüğü ayrıntısını gözden kaçırmayın. Uygulama @PreAuthorize('hasAuthority('orders:cancel')') kullanıyorsa roles ile yazılan test yanlış yetki modelini test eder. Authority tabanlı politika için with(user('ada').authorities(new SimpleGrantedAuthority('orders:cancel'))) kullanın. Bu ayrım, role hierarchy üretimde etkin olduğu halde test diliminde import edilmediğinde ortaya çıkan sahte pozitifleri azaltır.
Microservices mimarisi ve Spring Cloud bağımlılıklarını paralel test etmek
microservices mimarisi içinde spring cloud üzerinden config server, discovery veya uzak servis çağrıları test context başlangıcını ağ bağımlı hale getirebilir. Entegrasyon test profilinde discovery kaydını kapatın ve uzak HTTP bağımlılığını local bir stub ile sınırlayın. Örneğin uygulamanın config import davranışını test profiline göre ayırın:
# application-integration.yml
spring:
cloud:
discovery:
enabled: false
config:
enabled: false
clients:
catalog:
base-url: http://127.0.0.1:18081 Sabit port yalnızca tek JVM ve tek test worker için güvenlidir. CI'da paralel worker kullanılıyorsa WireMock veya MockWebServer'ı dinamik porta kaldırın, ancak her sınıf için farklı property vererek context cache'i bölmenin maliyetini ölçün.JUnit paralelliğini Gradle fork sayısıyla karıştırmayın. Spring context cache JVM içindedir; maxParallelForks değerini artırmak ayrı JVM'ler açtığı için her fork kendi boş cache'iyle başlar. Önce tek fork içinde JUnit class paralelliğini açın:
# src/test/resources/junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=same_thread
junit.jupiter.execution.parallel.mode.classes.default=concurrent
# build.gradle
tasks.named('test') {
maxParallelForks = 1
} Paylaşılan PostgreSQL temizliği, Kafka topic'i veya sabit WireMock portu kullanan sınıfları @ResourceLock('postgres-fixture') ile seri hale getirin. Aksi halde paralel testte görülen hata çoğu zaman Spring yarış koşulu değil, iki testin aynı satırı truncate etmesidir.Java Spring eğitimi için önce-sonra test profilleme planı
Bir java spring eğitimi materyalinde 'testler hızlandı' demek yerine aynı commit, aynı CI worker boyutu ve aynı Docker image cache koşullarında ölçüm yapın. Gradle HTML profilini üretmek için iki kez çalıştırın; ilk koşu bağımlılık indirme etkisini dışarıda bırakır, ikinci koşu karşılaştırma verisidir:
./gradlew cleanTest test --profile
./gradlew test --profile -Dspring.test.context.cache.maxSize=64 build/reports/profile altındaki test task süresini, Spring DEBUG logundaki cache missCount değerini ve container başlatma sayısını kaydedin. Ardından ortak IntegrationTestBase, tek JVM fork ve slice test değişikliklerini uygulayıp aynı iki komutu tekrar çalıştırın. Karşılaştırmada toplam süreye ek olarak en yavaş 10 test class'ı ve context miss başına yaklaşık başlangıç süresini inceleyin; yalnızca toplam süre, paralellik nedeniyle darboğazı gizleyebilir.CPU ve bekleme zamanını ayırmak için test JVM'inden Java Flight Recorder alın. Gradle test task'ına geçici olarak aşağıdaki JVM argümanını ekleyin ve JDK Mission Control ile socket read, class loading, JDBC ve Docker client beklemelerini ayrı inceleyin:
tasks.named('test') {
jvmArgs '-XX:StartFlightRecording=filename=build/test.jfr,dumponexit=true,settings=profile'
} Örneğin JFR'da ApplicationContext refresh sırasında tekrar eden Flyway migration görüyorsanız çözüm thread sayısını artırmak değildir; context anahtarlarını birleştirmek veya migration'ı ortak container üzerinde bir kez koşturacak fixture tasarlamaktır. Buna karşılık süre Socket Read altında birikiyorsa, testin gerçek uzak servise kaçtığını Spring Cloud test profilinden ve istemci base URL loglarından doğrulayın.İlgili Eğitim
YTÜSEM İlgili Eğitim
Java Spring Boot ReactJS FullStack Eğitimi (Yıldız Teknik Üniversitesi SEM)
Sık Sorulan Sorular
Spring Boot testlerinde context cache neden sürekli miss olur?
En yaygın nedenler farklı @ActiveProfiles, @TestPropertySource içeriği, farklı @MockBean kümeleri, @DirtiesContext ve sınıf bazında üretilen dinamik property değerleridir. org.springframework.test.context.cache DEBUG logunu açın, missCount değerini değişiklik öncesi-sonrası kaydedin ve test sınıflarını ortak bir @SpringBootTest taban sınıfında birleştirin.
Spring Security ve Spring MVC testleri için @WebMvcTest yeterli mi?
Controller HTTP sözleşmesi için yeterlidir, ancak üretimdeki SecurityFilterChain'i @Import etmezseniz gerçek yetkilendirme ve CSRF davranışını doğrulamazsınız. Method security, JPA sorguları veya gerçek bean wiring'i test edilecekse hedef senaryoya özel sayıda @SpringBootTest kullanın; tüm controller testlerini tam context'e taşımayın.
Spring Cloud kullanan microservices mimarisi testleri paralel nasıl çalıştırılır?
Test profilinde discovery ve config client'ı kapatın, dış HTTP servislerini WireMock veya MockWebServer ile stub'layın. JUnit class paralelliğini tek Gradle fork içinde açın; aynı PostgreSQL fixture'ını veya sabit stub portunu kullanan testleri @ResourceLock ile serileştirin. maxParallelForks artışı Spring context cache'ini JVM'ler arasında paylaşmaz.
Spring REST API entegrasyon testinde @Transactional neden veriyi temizlemiyor?
Test transaction'ı yalnızca test thread'inin kullandığı bağlantıyı kapsar. REST isteği embedded sunucuda farklı bir thread ve bağlantı üzerinden işleniyorsa commit edilen veri rollback olmaz. @AfterEach içinde TRUNCATE ... RESTART IDENTITY CASCADE uygulayın veya her test için izole veritabanı kullanın; ikinci seçenek context cache maliyetini artırabileceği için profilleyin.
AI / LLM Discovery
Bu makale Opendart Akademi Spring Framework eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


