Spring uygulamalarında doğru test stratejisi hız, güvenilirlik ve sürdürülebilirlik sağlar. Unit, slice, integration testleri ve Testcontainers ile veritabanı izolasyonunu pratik örneklerle anlatıyorum.
Spring ile Test Otomasyonu: Unit, Integration ve Testcontainers Pratikleri
Modern Spring projelerinde testler sadece kod kalitesini artırmakla kalmaz; hızlı geribildirim, güvenli refaktörizasyon ve CI/CD boru hattında güvenilir dağıtım için hayati öneme sahiptir. Bu makalede unit testlerden integration testlere, Spring test dilimlerinden (slice tests) Testcontainers tabanlı veritabanı izolasyonuna kadar uygulamalı ve uygulanabilir pratikleri ele alacağım. Hedef: test sürelerini akılcı şekilde minimize ederken doğruluğu korumak.
1. Test stratejisini belirlemek: Hızlı vs Güvenilir
Her test türünün bir amacı vardır. Unit testler küçük, hızlı ve deterministik olmalı — dış bağımlılıklardan izole edilir. Integration testlerse bileşenlerin birlikte doğru çalıştığını doğrular; daha yavaştır ama gerçek dünya senaryolarını kapsar. En mantıklı strateji piramitte açık: çok sayıda unit, orta sayıda integration, az sayıda end-to-end testi.
Spring projelerinde testlerin yavaşlamasının en yaygın nedeni gereksiz Spring konteyner başlatmalarıdır. Bu yüzden @SpringBootTest'i her yerde kullanmak yerine, test dilimleri (slice tests) ve mocking ile testi hedefe odaklamak önemlidir. Ayrıca CI ortamında paralel çalıştırmayı desteklemek için testlerin yan etkisiz olması gerekir.
2. Unit testler: IoC, Mockito ve test edilen sınıfın sınırları
Unit testlerde amaç bir sınıfın davranışını bağımlılıkları izole ederek doğrulamaktır. Bunun için Mockito veya benzeri mock framework'ler kullanılır. Spring Boot projelerinde yalnızca Mockito ile sınıfınızı test etmek genellikle yeterlidir; Spring konteyneri başlatmaya gerek yoktur.
<!-- Örnek: JUnit 5 + Mockito -->
class UserServiceTest {
private UserRepository repo = mock(UserRepository.class);
private PasswordEncoder encoder = mock(PasswordEncoder.class);
private UserService svc = new UserService(repo, encoder);
@Test
void createUser_hashesPasswordAndSaves() {
var dto = new CreateUserDto("alice", "secret");
when(encoder.encode("secret")).thenReturn("hashed");
svc.createUser(dto);
verify(repo).save(argThat(u -> u.getPassword().equals("hashed") && u.getUsername().equals("alice")));
}
}
Bu yaklaşım testlerin çok hızlı çalışmasını sağlar ve davranış odaklı test yazmayı teşvik eder. Sınıfın dış bağımlılıklarından beklentilerinizi açıkça belirtmek, testleri daha okunur kılar.
3. Spring slice testleri ve Web/Controller testleri
Controller, repository gibi Spring bileşenlerini test ederken tüm uygulamayı ayağa kaldırmak gereksizdir. Spring, belirli katmanları hedefleyen anotasyonlar sağlar: @WebMvcTest, @DataJpaTest, @WebFluxTest gibi. Bu testler sadece ilgili beanleri yükler, bu sayede hız kazanılır.
<!-- Örnek: Web layer testi -->
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
MockMvc mockMvc;
@MockBean
UserService userService;
@Test
void getUser_returnsOk() throws Exception {
when(userService.getUser(1L)).thenReturn(new UserDto(1L, "alice"));
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("alice"));
}
}
MockMvc veya WebTestClient (reactive uygulamalar için) ile HTTP seviyesinde controller davranışını test etmek etkin bir yöntemdir. Bu testlerde servis katmanını @MockBean ile taklit etmek, controller'a odaklanmayı kolaylaştırır.
4. Integration testler ve Testcontainers ile izolasyon
Integration testler gerçek bağımlılıklarla çalışır: veritabanı, mesajlaşma sistemi, cache gibi. Geçmişte in-memory veritabanları (H2) sık kullanıldı ancak H2 ile PostgreSQL'in davranışı farklı olabilir. Bu yüzden Testcontainers kullanarak gerçek DB görüntülerini CI üzerinde çalıştırmak giderek standart hale geldi.
Testcontainers, Docker tabanlı capsule'lar sağlar; testin başında konteyner başlatılır, test izolasyonu sağlanır ve test sonunda konteyner durdurulur. Spring Boot ile birlikte spring-boot-testcontainers veya manuel Testcontainers konfigürasyonu kullanabilirsiniz.
<!-- Örnek: Testcontainers ile PostgreSQL -->
@Testcontainers
@SpringBootTest
class UserRepositoryIT {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:14")
.withDatabaseName("test")
.withUsername("sa")
.withPassword("sa");
@Autowired
UserRepository repo;
@Test
void saveAndFind() {
var u = new User(null, "bob");
repo.save(u);
assertTrue(repo.findByUsername("bob").isPresent());
}
}
Testcontainers, CI konteyner ortamlarında da çalıştığı için gerçekçi entegrasyon testleri sağlar. Ancak başlatma süresi ve resource tüketimini göz önünde bulundurarak sadece gerekli integration testleri yazın.
5. Performans ve güvenilirlik için pratik ipuçları
Test paketinizi hızlı tutmak için birkaç pratik kural uygulayın: (1) Unit testleri maksimum düzeyde izolasyonla yazın; (2) Spring konteynerini sadece gerektiğinde başlatın; (3) Test verisini fixture veya builder desenleriyle yönetin; (4) Tekrarlayan context yüklemelerini önlemek için @DirtiesContext kullanımını sınırlayın.
Ek olarak, paralel test çalıştırma (JUnit 5 config) ile CI süresini azaltabilirsiniz. Testlerin yan etkisiz olduğundan emin olun; Paylaşılan dosya sistemleri, statik durum ve sabit port kullanımı paralel çalışmayı bozabilir. Testlerde sabit porttan kaçının veya dinamik port konfigürasyonu kullanın.
Sık Sorulan Sorular
1) Ne zaman @SpringBootTest kullanmalıyım?
@SpringBootTest uygulamanın tamamını yükler; end-to-end veya tam entegrasyon testi gerektiğinde kullanın. Ancak birçok senaryo için slice testleri (@WebMvcTest, @DataJpaTest) yeterlidir ve çok daha hızlıdır.
2) Testcontainers yerine H2 kullanmak yanlış mı?
H2 tamamen yanlış değildir; hızlı prototip ve basit repository testleri için uygundur. Ancak veritabanı sürümü ve SQL davranışı prod ortamından farklı olabilir. Kritik entegrasyonlar için Testcontainers ile gerçek DB görüntüsü tercih edin.
3) Test verisini nasıl yönetmeliyim?
Test verisini programatik olarak oluşturmak (fixture builder), test izolasyonu sağlar ve okunabilirliği artırır. SQL script veya veri yükleme gerekiyorsa, test-specific migration veya Flyway/Liquibase ile birlikte container bazlı veritabanı kullanın. Testlerin bağımsız ve tekrarlanabilir olmasına dikkat edin.
Sonuç
Spring projelerinde etkin bir test stratejisi, doğru test türünü seçmekle başlar: hızlı unit testler, hedeflenmiş slice testleri ve ihtiyaç duyulan yerde gerçekçi integration testleri. Testcontainers gibi araçlar, entegrasyon testi güvenilirliğini artırır; ancak maliyet ve süreyi göz önünde bulundurarak dikkatle uygulanmalıdır. Unutmayın: testlerin amacı sadece %100 kapsama değil, hızlı ve güvenilir geribildirim döngüleri oluşturmaktır.
Eğitim bağlantısı EKLEME
