Bir Spring cloud microservice projesinde integration test nasıl yapılır?

 





Integration testi, bir yazılım uygulamasının farklı bileşenlerinin veya modüllerinin bir araya getirildiğinde doğru şekilde çalışıp çalışmadığını kontrol etmek için yapılan bir test türüdür. Bu test, bileşenlerin birbirleriyle ve sistemin geri kalanıyla uyumlu bir şekilde çalışıp çalışmadığını değerlendirir.

Integration testinin ana amaçları şunlardır:

  1. Bileşenler arası iletişimi doğrulamak

  2. Veri akışını kontrol etmek

  3. Bileşenlerin birlikte çalıştığında beklenen işlevselliği sağladığını görmek

  4. Arayüzler arasındaki uyumu test etmek

  5. Entegrasyon sırasında ortaya çıkabilecek hataları tespit etmek

Bu testler genellikle birim testlerinden sonra ve sistem testlerinden önce gerçekleştirilir.

Spring Cloud mikroservis projesinde integration testi yapmak için birkaç önemli adım ve yaklaşım vardır. Size bu süreci açıklayacağım:

  1. Test Ortamının Hazırlanması:

    • Genellikle Docker kullanılarak bağımlılıklar (veritabanı, message broker gibi) ayağa kaldırılır.

    • TestContainers kütüphanesi bu iş için sıkça tercih edilir.

  2. Test Konfigürasyonu:

    • @SpringBootTest annotation'ı kullanılarak tüm uygulama context'i yüklenir.

    • @AutoConfigureMockMvc ile HTTP isteklerini simüle etmek için MockMvc hazırlanır.

  3. Bağımlılıkların Yönetimi:

    • Diğer mikroservislere olan bağımlılıklar için mock servisler oluşturulabilir.

    • Wiremock gibi araçlar kullanılarak dış servisler simüle edilebilir.

  4. Veritabanı Testleri:

    • In-memory veritabanı (H2 gibi) kullanılabilir.

    • Flyway veya Liquibase ile şema yönetimi yapılabilir.

  5. Mesajlaşma Testleri:

    • Kafka, RabbitMQ gibi mesajlaşma sistemleri için embedded versiyonlar kullanılabilir.

  6. API Testleri:

    • REST API'ler MockMvc ile test edilir.

    • GraphQL API'ler için özel test araçları kullanılabilir.

Basit bir örnek göstermek isterim. Aşağıdaki kod, bir kullanıcı servisi için integration test örneğidir:

@SpringBootTest

@AutoConfigureMockMvc

public class UserServiceIntegrationTest {


@Autowired

private MockMvc mockMvc;


@Autowired

private UserRepository userRepository;


@Test

public void testCreateUser() throws Exception {

String userJson = "{\"username\":\"testuser\",\"email\":\"test@example.com\"}";


mockMvc.perform(post("/api/users")

.contentType(MediaType.APPLICATION_JSON)

.content(userJson))

.andExpect(status().isCreated())

.andExpect(jsonPath("$.id").exists())

.andExpect(jsonPath("$.username").value("testuser"))

.andExpect(jsonPath("$.email").value("test@example.com"));


User savedUser = userRepository.findByUsername("testuser");

assertNotNull(savedUser);

assertEquals("test@example.com", savedUser.getEmail());

}


@Test

public void testGetUser() throws Exception {

User user = new User("existinguser", "existing@example.com");

userRepository.save(user);


mockMvc.perform(get("/api/users/" + user.getId()))

.andExpect(status().isOk())

.andExpect(jsonPath("$.username").value("existinguser"))

.andExpect(jsonPath("$.email").value("existing@example.com"));

}

}


Bu örnekte:

  1. @SpringBootTest ile tüm uygulama context'i yüklenir.

  2. @AutoConfigureMockMvc ile HTTP isteklerini simüle etmek için MockMvc hazırlanır.

  3. UserRepository gerçek veritabanı ile etkileşime girer (in-memory veya test veritabanı olabilir).

  4. İki test metodu bulunmaktadır:

    • testCreateUser(): Yeni bir kullanıcı oluşturma işlemini test eder.

    • testGetUser(): Var olan bir kullanıcıyı getirme işlemini test eder.

Bu tür testler, servisin beklenen şekilde çalıştığını, veritabanı işlemlerinin doğru gerçekleştiğini ve API'nin düzgün yanıt verdiğini doğrular.

Daha karmaşık senaryolarda, mesajlaşma sistemleri, dış servis çağrıları ve diğer mikroservislerle etkileşimler de test edilebilir. Bu durumda, test konfigürasyonu daha kapsamlı olacaktır.



Bu sefer, mikroservisler arası iletişimi, mesajlaşma sistemini ve dış servis çağrılarını içeren bir senaryo düşünelim.

Örneğimizde, bir e-ticaret sisteminin sipariş servisi olsun. Bu servis:

  1. Yeni siparişleri alır

  2. Stok servisini kontrol eder

  3. Ödeme servisini çağırır

  4. Sipariş onaylandığında bir Kafka topic'ine mesaj gönderir

İşte bu senaryoyu test eden bir integration test örneği:



@SpringBootTest

@AutoConfigureMockMvc

@TestPropertySource(locations = "classpath:application-test.properties")

@DirtiesContext

public class OrderServiceIntegrationTest {


@Autowired

private MockMvc mockMvc;


@Autowired

private OrderRepository orderRepository;


@MockBean

private StockServiceClient stockServiceClient;


@MockBean

private PaymentServiceClient paymentServiceClient;


@Autowired

private KafkaTemplate<String, String> kafkaTemplate;


@Autowired

private EmbeddedKafkaBroker embeddedKafkaBroker;


private KafkaMessageListenerContainer<String, String> container;

private BlockingQueue<ConsumerRecord<String, String>> records;


@BeforeEach

void setUp() {

// Kafka consumer ayarları

Map<String, Object> configs = new HashMap<>(KafkaTestUtils.consumerProps("test-group", "true", embeddedKafkaBroker));

ConsumerFactory<String, String> consumerFactory = new DefaultKafkaConsumerFactory<>(configs, new StringDeserializer(), new StringDeserializer());

ContainerProperties containerProperties = new ContainerProperties("order-confirmed");

container = new KafkaMessageListenerContainer<>(consumerFactory, containerProperties);

records = new LinkedBlockingQueue<>();

container.setupMessageListener((MessageListener<String, String>) records::add);

container.start();

}


@AfterEach

void tearDown() {

container.stop();

}


@Test

public void testCreateOrder() throws Exception {

// Mock dış servis yanıtları

when(stockServiceClient.checkStock(anyString(), anyInt())).thenReturn(true);

when(paymentServiceClient.processPayment(anyString(), anyDouble())).thenReturn(true);


String orderJson = "{\"productId\":\"prod-1\",\"quantity\":2,\"totalPrice\":100.00}";


mockMvc.perform(post("/api/orders")

.contentType(MediaType.APPLICATION_JSON)

.content(orderJson))

.andExpect(status().isCreated())

.andExpect(jsonPath("$.id").exists())

.andExpect(jsonPath("$.status").value("CONFIRMED"));


// Veritabanı kontrolü

Order savedOrder = orderRepository.findByProductId("prod-1");

assertNotNull(savedOrder);

assertEquals(OrderStatus.CONFIRMED, savedOrder.getStatus());


// Kafka mesaj kontrolü

ConsumerRecord<String, String> record = records.poll(5, TimeUnit.SECONDS);

assertNotNull(record);

assertEquals("order-confirmed", record.topic());

assertTrue(record.value().contains("prod-1"));


// Dış servis çağrıları kontrolü

verify(stockServiceClient).checkStock("prod-1", 2);

verify(paymentServiceClient).processPayment(anyString(), eq(100.00));

}


@Test

public void testCreateOrderWithInsufficientStock() throws Exception {

when(stockServiceClient.checkStock(anyString(), anyInt())).thenReturn(false);


String orderJson = "{\"productId\":\"prod-2\",\"quantity\":5,\"totalPrice\":250.00}";


mockMvc.perform(post("/api/orders")

.contentType(MediaType.APPLICATION_JSON)

.content(orderJson))

.andExpect(status().isBadRequest())

.andExpect(jsonPath("$.message").value("Insufficient stock"));


// Veritabanı kontrolü

Order savedOrder = orderRepository.findByProductId("prod-2");

assertNull(savedOrder);


// Ödeme servisi çağrılmadığından emin olma

verify(paymentServiceClient, never()).processPayment(anyString(), anyDouble());

}

}


Bu örnek, daha karmaşık bir mikroservis ortamını simüle eder ve şunları test eder:

  1. Sipariş oluşturma API'si

  2. Stok servisi ile iletişim (mock edilmiş)

  3. Ödeme servisi ile iletişim (mock edilmiş)

  4. Kafka'ya mesaj gönderimi

  5. Veritabanı işlemleri

Önemli noktalar:

  1. @MockBean kullanarak dış servisleri (stok ve ödeme) mock ediyoruz.

  2. Embedded Kafka broker kullanarak gerçek mesajlaşmayı test ediyoruz.

  3. Farklı senaryolar için ayrı test metotları yazıyoruz (başarılı sipariş ve stok yetersizliği).

  4. Asenkron işlemleri (Kafka mesajı) test etmek için bekleme mekanizması kullanıyoruz.

Bu tür kapsamlı testler, mikroservis mimarisinde farklı bileşenlerin bir arada nasıl çalıştığını doğrulamak için çok önemlidir. Ayrıca, bu testler sayesinde:

  • Servisler arası iletişimin doğru çalıştığından emin oluruz.

  • Mesajlaşma sisteminin beklenen şekilde kullanıldığını doğrularız.

  • Hata senaryolarının düzgün yönetildiğini kontrol ederiz.

  • Veritabanı işlemlerinin transaction yönetimini test ederiz.

  1. Test Verilerinin Yönetimi:

Test verilerinin doğru yönetimi, integration testlerinin güvenilirliği ve tekrarlanabilirliği için kritiktir. İşte bazı önemli stratejiler:

a) Veritabanı Sıfırlama:

  • Her test öncesinde veritabanını sıfırlamak için @DirtiesContext veya @Transactional annotasyonları kullanılabilir.

  • Alternatif olarak, özel bir @BeforeEach metodu ile veritabanı temizlenebilir.

b) Test Veri Setleri:

  • SQL script'leri veya Java tabanlı veri yükleyicileri kullanarak test verilerini yükleyin.

  • Flyway veya Liquibase gibi araçlarla şema ve veri yönetimini otomatikleştirin.

    Örnek Uygulama:

@SpringBootTest

@Sql(scripts = "/test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)

@Sql(scripts = "/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)

public class OrderServiceIntegrationTest {


@Autowired

private TestDataLoader testDataLoader;


@BeforeEach

void setUp() {

testDataLoader.loadTestData();

}


// ... test metotları ...

}


// TestDataLoader sınıfı

@Component

public class TestDataLoader {

@Autowired

private OrderRepository orderRepository;


public void loadTestData() {

Order order1 = new Order("prod-1", 2, 100.00);

Order order2 = new Order("prod-2", 1, 50.00);

orderRepository.saveAll(Arrays.asList(order1, order2));

}

}


<!-- pom.xml -->

<build>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-surefire-plugin</artifactId>

<configuration>

<parallel>classes</parallel>

<threadCount>4</threadCount>

<perCoreThreadCount>true</perCoreThreadCount>

</configuration>

</plugin>

</plugins>

</build>


<!-- application-test.properties -->

spring.datasource.url=jdbc:h2:mem:testdb

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect



pipeline {

agent any

stages {

stage('Build') {

steps {

sh 'mvn clean package -DskipTests'

}

}

stage('Unit Tests') {

steps {

sh 'mvn test'

}

}

stage('Integration Tests') {

steps {

sh 'mvn verify -Dspring.profiles.active=test'

}

}

stage('Deploy') {

when {

branch 'main'

}

steps {

sh './deploy.sh'

}

}

}

post {

always {

junit '**/target/surefire-reports/*.xml'

jacoco()

}

}

}

Please Select Embedded Mode To Show The Comment System.*

Daha yeni Daha eski

نموذج الاتصال