Quarkus Tdd

affaan-m/ECC/docs/tr/skills/quarkus-tdd

作者 affaan-mef648e01899ba3e8dc6371642deaaf64b4477775無授權條款275K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫4 天前更新

Test-driven development for Quarkus 3.x LTS using JUnit 5, Mockito, REST Assured, Camel testing, and JaCoCo. Use when adding features, fixing bugs, or refactoring event-driven services.

AI 產生的概覽

指導 Quarkus 3.x 服務的測試驅動開發,使用 JUnit 5、Mockito、REST Assured、Camel 測試與 JaCoCo。

功能
提供 Quarkus 3.x 服務的 TDD 工作流程與 Java 範例,涵蓋使用 Mockito 的單元測試、Camel 路由測試、事件服務測試、CompletableFuture 非同步測試以及 REST Assured 資源測試。它說明了 @Nested 分組、givenX_whenY_thenZ 命名、AAA 結構與互動驗證等模式,並以 JaCoCo 衡量的 80% 以上覆蓋率為目標。
適用情境
適用於新增功能或 REST 端點、修正缺陷或重構 Quarkus 服務時。它針對資料存取邏輯、安全規則、反應式流程、Apache Camel 路由、RabbitMQ 事件驅動服務以及非同步 CompletableFuture 處理的測試。
執行需求
僅為說明性內容,不附帶指令碼。所述方法假定已有 Quarkus 3.x LTS 專案,並具備 JUnit 5、Mockito、REST Assured、Apache Camel 測試支援與 JaCoCo。

Quarkus TDD İş Akışı

80%+ kapsam (unit + integration) ile Quarkus 3.x servisleri için TDD rehberi. Apache Camel ile event-driven mimariler için optimize edilmiştir.

When to Use

  • Yeni özellikler veya REST endpoint'leri
  • Bug düzeltmeleri veya refactoring'ler
  • Veri erişim mantığı, güvenlik kuralları veya reaktif akışlar ekleme
  • Apache Camel route'larını ve event handler'larını test etme
  • RabbitMQ ile event-driven servisleri test etme
  • Koşullu akış mantığını test etme
  • CompletableFuture async işlemlerini doğrulama
  • LogContext yayılımını test etme

How It Works

  1. Önce testleri yazın (başarısız olmalılar)
  2. Geçmek için minimal kod uygulayın
  3. Testleri yeşil tutarken refactor edin
  4. JaCoCo ile kapsamı zorlayın (%80+ hedef)

Examples

@Nested Organizasyonlu Unit Testler

Kapsamlı ve okunabilir testler için bu yapılandırılmış yaklaşımı izleyin:

java
@ExtendWith(MockitoExtension.class)@DisplayName("As2ProcessingService Unit Tests")class As2ProcessingServiceTest {
  @Mock  private InvoiceFlowValidator invoiceFlowValidator;
  @Mock  private EventService eventService;
  @Mock  private DocumentJobService documentJobService;
  @Mock  private BusinessRulesPublisher businessRulesPublisher;
  @Mock  private FileStorageService fileStorageService;
  @InjectMocks  private As2ProcessingService as2ProcessingService;
  private Path testFilePath;  private LogContext testLogContext;  private InvoiceValidationResult validationResult;  private StoredDocumentInfo documentInfo;
  @BeforeEach  void setUp() {    // ARRANGE - Ortak test verisi    testFilePath = Path.of("/tmp/test-invoice.xml");
    testLogContext = new LogContext();    testLogContext.put(As2Constants.STRUCTURE_ID, "STRUCT-001");    testLogContext.put(As2Constants.FILE_NAME, "invoice.xml");    testLogContext.put(As2Constants.AS2_FROM, "PARTNER-001");
    validationResult = new InvoiceValidationResult();    validationResult.setValid(true);    validationResult.setSize(1024L);    validationResult.setDocumentHash("abc123");
    documentInfo = new StoredDocumentInfo();    documentInfo.setPath("s3://bucket/path/invoice.xml");    documentInfo.setSize(1024L);  }
  @Nested  @DisplayName("processFile için testler")  class ProcessFile {
    @Test    @DisplayName("CHORUS olmayan dosyayı tüm validasyonlarla başarıyla işlemeli")    void givenNonChorusFile_whenProcessFile_thenAllValidationsApplied() throws Exception {      // ARRANGE      testLogContext.put(As2Constants.CHORUS_FLOW, "false");      CustomLog.setCurrentContext(testLogContext);
      when(invoiceFlowValidator.validateFlowWithConfig(          eq(testFilePath),          eq(ValidationFlowConfig.allValidations()),          eq(EInvoiceSyntaxFormat.UBL),          any(LogContext.class)))          .thenReturn(validationResult);
      when(invoiceFlowValidator.computeFlowProfile(any(), any()))          .thenReturn(FlowProfile.BASIC);
      when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))          .thenReturn(CompletableFuture.completedFuture(documentInfo));
      when(documentJobService.createDocumentAndJobEntities(any(), any(), any(), any(), any()))          .thenReturn(new BusinessRulesPayload());
      // ACT      assertDoesNotThrow(() -> as2ProcessingService.processFile(testFilePath));
      // ASSERT      verify(invoiceFlowValidator).validateFlowWithConfig(          eq(testFilePath),          eq(ValidationFlowConfig.allValidations()),          eq(EInvoiceSyntaxFormat.UBL),          any(LogContext.class));
      verify(eventService).createSuccessEvent(any(StoredDocumentInfo.class),          eq("PERSISTENCE_BLOB_EVENT_TYPE"));      verify(eventService).createSuccessEvent(any(BusinessRulesPayload.class),          eq("BUSINESS_RULES_MESSAGE_SENT"));      verify(businessRulesPublisher).publishAsync(any(BusinessRulesPayload.class));    }
    @Test    @DisplayName("CHORUS_FLOW için schematron validasyonu atlanmalı")    void givenChorusFlow_whenProcessFile_thenSchematronBypassed() throws Exception {      // ARRANGE      testLogContext.put(As2Constants.CHORUS_FLOW, "true");      CustomLog.setCurrentContext(testLogContext);
      when(invoiceFlowValidator.validateFlowWithConfig(          eq(testFilePath),          eq(ValidationFlowConfig.xsdOnly()),          eq(EInvoiceSyntaxFormat.UBL),          any(LogContext.class)))          .thenReturn(validationResult);
      when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))          .thenReturn(CompletableFuture.completedFuture(documentInfo));
      when(documentJobService.createDocumentAndJobEntities(any(), any(), any(),          eq(FlowProfile.EXTENDED_CTC_FR), any()))          .thenReturn(new BusinessRulesPayload());
      // ACT      assertDoesNotThrow(() -> as2ProcessingService.processFile(testFilePath));
      // ASSERT      verify(invoiceFlowValidator).validateFlowWithConfig(          eq(testFilePath),          eq(ValidationFlowConfig.xsdOnly()),          eq(EInvoiceSyntaxFormat.UBL),          any(LogContext.class));
      verify(documentJobService).createDocumentAndJobEntities(          any(), any(), any(),          eq(FlowProfile.EXTENDED_CTC_FR),          any());    }
    @Test    @DisplayName("Dosya yükleme başarısız olduğunda hata eventi oluşturulmalı")    void givenUploadFailure_whenProcessFile_thenErrorEventCreated() throws Exception {      // ARRANGE      testLogContext.put(As2Constants.CHORUS_FLOW, "false");      CustomLog.setCurrentContext(testLogContext);
      when(invoiceFlowValidator.validateFlowWithConfig(any(), any(), any(), any()))          .thenReturn(validationResult);
      when(invoiceFlowValidator.computeFlowProfile(any(), any()))          .thenReturn(FlowProfile.BASIC);
      documentInfo.setPath(""); // Boş path hatayı tetikler      when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))          .thenReturn(CompletableFuture.completedFuture(documentInfo));
      // ACT & ASSERT      As2ServerProcessingException exception = assertThrows(          As2ServerProcessingException.class,          () -> as2ProcessingService.processFile(testFilePath)      );
      assertThat(exception.getMessage())          .contains("File path is empty after upload");
      verify(eventService).createErrorEvent(          eq(documentInfo),          eq("FILE_UPLOAD_FAILED"),          contains("File path is empty"));
      verify(businessRulesPublisher, never()).publishAsync(any());    }
    @Test    @DisplayName("CompletableFuture.join() başarısızlığı ele alınmalı")    void givenAsyncUploadFailure_whenProcessFile_thenExceptionThrown() throws Exception {      // ARRANGE      testLogContext.put(As2Constants.CHORUS_FLOW, "false");      CustomLog.setCurrentContext(testLogContext);
      when(invoiceFlowValidator.validateFlowWithConfig(any(), any(), any(), any()))          .thenReturn(validationResult);
      when(invoiceFlowValidator.computeFlowProfile(any(), any()))          .thenReturn(FlowProfile.BASIC);
      CompletableFuture<StoredDocumentInfo> failedFuture =          CompletableFuture.failedFuture(new StorageException("S3 connection failed"));      when(fileStorageService.uploadOriginalFile(any(), anyLong(), any(), any()))          .thenReturn(failedFuture);
      // ACT & ASSERT      assertThrows(          CompletionException.class,          () -> as2ProcessingService.processFile(testFilePath)      );    }
    @Test    @DisplayName("Dosya yolu null olduğunda exception fırlatılmalı")    void givenNullFilePath_whenProcessFile_thenThrowsException() {      // ARRANGE      Path nullPath = null;
      // ACT & ASSERT      NullPointerException exception = assertThrows(          NullPointerException.class,          () -> as2ProcessingService.processFile(nullPath)      );
      verify(invoiceFlowValidator, never()).validateFlowWithConfig(any(), any(), any(), any());    }  }}

Temel Test Desenleri

  1. @Nested Sınıflar: Testleri test edilen metoda göre gruplandırın
  2. @DisplayName: Test raporlarında okunabilir açıklamalar sağlayın
  3. İsimlendirme Kuralı: Netlik için givenX_whenY_thenZ
  4. AAA Deseni: Açık // ARRANGE, // ACT, // ASSERT yorumları
  5. @BeforeEach: Tekrarı azaltmak için ortak test verisi kurulumu
  6. assertDoesNotThrow: Exception yakalamadan başarı senaryolarını test edin
  7. assertThrows: AssertJ kullanarak mesaj doğrulamalı exception senaryolarını test edin
  8. Kapsamlı Kapsam: Mutlu yolları, null girdileri, edge case'leri, exception'ları test edin
  9. Etkileşimleri Doğrulama: Metodların doğru çağrıldığından emin olmak için Mockito verify() kullanın
  10. Hiçbir Zaman Doğrulama: Hata senaryolarında metodların ÇAĞRILMADIĞINI sağlamak için never() kullanın

Camel Route Testi

java
@QuarkusTest@DisplayName("Business Rules Camel Route Tests")class BusinessRulesRouteTest {
  @Inject  CamelContext camelContext;
  @Inject  ProducerTemplate producerTemplate;
  @InjectMock  EventService eventService;
  private BusinessRulesPayload testPayload;
  @BeforeEach  void setUp() {    // ARRANGE - Test verisi    testPayload = new BusinessRulesPayload();    testPayload.setDocumentId(1L);    testPayload.setFlowProfile(FlowProfile.BASIC);  }
  @Nested  @DisplayName("business-rules-publisher route için testler")  class BusinessRulesPublisher {
    @Test    @DisplayName("Mesajı başarıyla RabbitMQ'ya yayınlamalı")    void givenValidPayload_whenPublish_thenMessageSentToQueue() throws Exception {      // ARRANGE      MockEndpoint mockRabbitMQ = camelContext.getEndpoint("mock:rabbitmq", MockEndpoint.class);      mockRabbitMQ.expectedMessageCount(1);
      // Test için gerçek endpoint'i mock ile değiştir      camelContext.getRouteController().stopRoute("business-rules-publisher");      AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {        advice.replaceFromWith("direct:business-rules-publisher");        advice.weaveByToString(".*spring-rabbitmq.*").replace().to("mock:rabbitmq");      });      camelContext.getRouteController().startRoute("business-rules-publisher");
      // ACT      producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
      // ASSERT — .marshal().json() sonrası body JSON String'dir      mockRabbitMQ.assertIsSatisfied(5000);
      assertThat(mockRabbitMQ.getExchanges()).hasSize(1);      String body = mockRabbitMQ.getExchanges().get(0).getIn().getBody(String.class);      assertThat(body).contains("\"documentId\":1");    }
    @Test    @DisplayName("JSON'a marshalling'i ele almalı")    void givenPayload_whenPublish_thenMarshalledToJson() throws Exception {      // ARRANGE      MockEndpoint mockMarshal = new MockEndpoint("mock:marshal");      camelContext.addEndpoint("mock:marshal", mockMarshal);      mockMarshal.expectedMessageCount(1);
      camelContext.getRouteController().stopRoute("business-rules-publisher");      AdviceWith.adviceWith(camelContext, "business-rules-publisher", advice -> {        advice.weaveAddLast().to("mock:marshal");      });      camelContext.getRouteController().startRoute("business-rules-publisher");
      // ACT      producerTemplate.sendBody("direct:business-rules-publisher", testPayload);
      // ASSERT      mockMarshal.assertIsSatisfied(5000);
      String body = mockMarshal.getExchanges().get(0).getIn().getBody(String.class);      assertThat(body).contains("\"documentId\":1");      assertThat(body).contains("\"flowProfile\":\"BASIC\"");    }  }
  @Nested  @DisplayName("document-processing route için testler")  class DocumentProcessing {
    @Test    @DisplayName("Faturayı doğru işlemciye yönlendirmeli")    void givenInvoiceType_whenProcess_thenRoutesToInvoiceProcessor() throws Exception {      // ARRANGE      MockEndpoint mockInvoice = camelContext.getEndpoint("mock:invoice", MockEndpoint.class);      mockInvoice.expectedMessageCount(1);
      camelContext.getRouteController().stopRoute("document-processing");      AdviceWith.adviceWith(camelContext, "document-processing", advice -> {        advice.weaveByToString(".*direct:process-invoice.*").replace().to("mock:invoice");      });      camelContext.getRouteController().startRoute("document-processing");
      // ACT      producerTemplate.sendBodyAndHeader("direct:process-document",          testPayload, "documentType", "INVOICE");
      // ASSERT      mockInvoice.assertIsSatisfied(5000);    }
    @Test    @DisplayName("Validasyon hatalarını zarif şekilde ele almalı")    void givenValidationError_whenProcess_thenRoutesToErrorHandler() throws Exception {      // ARRANGE      MockEndpoint mockError = camelContext.getEndpoint("mock:error", MockEndpoint.class);      mockError.expectedMessageCount(1);
      camelContext.getRouteController().stopRoute("document-processing");      AdviceWith.adviceWith(camelContext, "document-processing", advice -> {        advice.weaveByToString(".*direct:validation-error-handler.*")            .replace().to("mock:error");      });      camelContext.getRouteController().startRoute("document-processing");
      // Error event oluşturma hatasını gerçek EventService API'si üzerinden simüle et      doThrow(new ValidationException("Invalid document"))          .when(eventService)          .createErrorEvent(any(), eq("VALIDATION_ERROR"), anyString());
      // ACT      producerTemplate.sendBody("direct:process-document", testPayload);
      // ASSERT      mockError.assertIsSatisfied(5000);
      Exception exception = mockError.getExchanges().get(0).getException();      assertThat(exception).isInstanceOf(ValidationException.class);      assertThat(exception.getMessage()).contains("Invalid document");    }  }}

Event Service Testi

java
@ExtendWith(MockitoExtension.class)@DisplayName("EventService Unit Tests")class EventServiceTest {
  @Mock  private EventRepository eventRepository;
  @Mock  private ObjectMapper objectMapper;
  @InjectMocks  private EventService eventService;
  private BusinessRulesPayload testPayload;
  @BeforeEach  void setUp() {    // ARRANGE    testPayload = new BusinessRulesPayload();    testPayload.setDocumentId(1L);  }
  @Nested  @DisplayName("createSuccessEvent için testler")  class CreateSuccessEvent {
    @Test    @DisplayName("Doğru niteliklerle başarı eventi oluşturulmalı")    void givenValidPayload_whenCreateSuccessEvent_thenEventPersisted() throws Exception {      // ARRANGE      when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
      // ACT      assertDoesNotThrow(() ->          eventService.createSuccessEvent(testPayload, "DOCUMENT_PROCESSED"));
      // ASSERT      verify(eventRepository).persist(argThat(event ->          event.getType().equals("DOCUMENT_PROCESSED") &&          event.getStatus() == EventStatus.SUCCESS &&          event.getPayload().equals("{\"documentId\":1}") &&          event.getTimestamp() != null      ));    }
    @Test    @DisplayName("Payload null olduğunda exception fırlatılmalı")    void givenNullPayload_whenCreateSuccessEvent_thenThrowsException() {      // ARRANGE      Object nullPayload = null;
      // ACT & ASSERT      NullPointerException exception = assertThrows(          NullPointerException.class,          () -> eventService.createSuccessEvent(nullPayload, "EVENT_TYPE")      );
      assertThat(exception.getMessage()).isEqualTo("Payload cannot be null");      verify(eventRepository, never()).persist(any());    }  }
  @Nested  @DisplayName("createErrorEvent için testler")  class CreateErrorEvent {
    @Test    @DisplayName("Hata mesajıyla hata eventi oluşturulmalı")    void givenError_whenCreateErrorEvent_thenEventPersistedWithMessage() throws Exception {      // ARRANGE      String errorMessage = "Processing failed";      when(objectMapper.writeValueAsString(testPayload)).thenReturn("{\"documentId\":1}");
      // ACT      assertDoesNotThrow(() ->          eventService.createErrorEvent(testPayload, "PROCESSING_ERROR", errorMessage));
      // ASSERT      verify(eventRepository).persist(argThat(event ->          event.getType().equals("PROCESSING_ERROR") &&          event.getStatus() == EventStatus.ERROR &&          event.getErrorMessage().equals(errorMessage) &&          event.getPayload().equals("{\"documentId\":1}")      ));    }
    @ParameterizedTest    @DisplayName("Geçersiz hata mesajları reddedilmeli")    @ValueSource(strings = {"", " "})    void givenBlankErrorMessage_whenCreateErrorEvent_thenThrowsException(String blankMessage) {      // ACT & ASSERT      IllegalArgumentException exception = assertThrows(          IllegalArgumentException.class,          () -> eventService.createErrorEvent(testPayload, "ERROR", blankMessage)      );
      assertThat(exception.getMessage()).contains("Error message cannot be blank");    }  }}

CompletableFuture Testi

java
@ExtendWith(MockitoExtension.class)@DisplayName("FileStorageService Unit Tests")class FileStorageServiceTest {
  @Mock  private S3Client s3Client;
  @Mock  private ExecutorService executorService;
  @InjectMocks  private FileStorageService fileStorageService;
  private InputStream testInputStream;  private LogContext testLogContext;
  @BeforeEach  void setUp() {    // ARRANGE    testInputStream = new ByteArrayInputStream("test content".getBytes());    testLogContext = new LogContext();    testLogContext.put("traceId", "trace-123");  }
  @Nested  @DisplayName("uploadOriginalFile için testler")  class UploadOriginalFile {
    @Test    @DisplayName("Dosyayı başarıyla yüklemeli ve belge bilgisi döndürmeli")    void givenValidFile_whenUpload_thenReturnsDocumentInfo() throws Exception {      // ARRANGE      doAnswer(invocation -> {        ((Runnable) invocation.getArgument(0)).run();        return null;      }).when(executorService).execute(any(Runnable.class));
      when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))          .thenReturn(PutObjectResponse.builder().build());
      // ACT      CompletableFuture<StoredDocumentInfo> future =          fileStorageService.uploadOriginalFile(testInputStream, 1024L,              testLogContext, InvoiceFormat.UBL);
      StoredDocumentInfo result = future.join();
      // ASSERT      assertThat(result).isNotNull();      assertThat(result.getPath()).isNotBlank();      assertThat(result.getSize()).isEqualTo(1024L);      assertThat(result.getUploadedAt()).isNotNull();
      verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));    }
    @Test    @DisplayName("S3 yükleme başarısızlığını ele almalı")    void givenS3Failure_whenUpload_thenCompletableFutureFails() {      // ARRANGE      doAnswer(invocation -> {        ((Runnable) invocation.getArgument(0)).run();        return null;      }).when(executorService).execute(any(Runnable.class));
      when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))          .thenThrow(new StorageException("S3 unavailable"));
      // ACT      CompletableFuture<StoredDocumentInfo> future =          fileStorageService.uploadOriginalFile(testInputStream, 1024L,              testLogContext, InvoiceFormat.UBL);
      // ASSERT      assertThatThrownBy(() -> future.join())          .isInstanceOf(CompletionException.class)          .hasCauseInstanceOf(StorageException.class)          .hasMessageContaining("S3 unavailable");    }
    @Test    @DisplayName("LogContext'i async işleme yaymalı")    void givenLogContext_whenUpload_thenContextPropagated() throws Exception {      // ARRANGE      AtomicReference<LogContext> capturedContext = new AtomicReference<>();
      doAnswer(invocation -> {        capturedContext.set(CustomLog.getCurrentContext());        ((Runnable) invocation.getArgument(0)).run();        return null;      }).when(executorService).execute(any(Runnable.class));
      // ACT      fileStorageService.uploadOriginalFile(testInputStream, 1024L,          testLogContext, InvoiceFormat.UBL).join();
      // ASSERT      assertThat(capturedContext.get()).isNotNull();      assertThat(capturedContext.get().get("traceId")).isEqualTo("trace-123");    }  }}

Resource Katmanı Testleri (REST Assured)

java
@QuarkusTest@DisplayName("DocumentResource API Tests")class DocumentResourceTest {
  @InjectMock  DocumentService documentService;
  @Nested  @DisplayName("GET /api/documents için testler")  class ListDocuments {
    @Test    @DisplayName("Belge listesini döndürmeli")    void givenDocumentsExist_whenList_thenReturnsOk() {      // ARRANGE      List<Document> documents = List.of(createDocument(1L, "DOC-001"));      when(documentService.list(0, 20)).thenReturn(documents);
      // ACT & ASSERT      given()          .when().get("/api/documents")          .then()          .statusCode(200)          .body("$.size()", is(1))          .body("[0].referenceNumber", equalTo("DOC-001"));    }  }
  @Nested  @DisplayName("POST /api/documents için testler")  class CreateDocument {
    @Test    @DisplayName("Belge oluşturmalı ve 201 döndürmeli")    void givenValidRequest_whenCreate_thenReturns201() {      // ARRANGE      Document document = createDocument(1L, "DOC-001");      when(documentService.create(any())).thenReturn(document);
      // ACT & ASSERT      given()          .contentType(ContentType.JSON)          .body("""              {                "referenceNumber": "DOC-001",                "description": "Test document",                "validUntil": "2030-01-01T00:00:00Z",                "categories": ["test"]              }              """)          .when().post("/api/documents")          .then()          .statusCode(201)          .header("Location", containsString("/api/documents/1"))          .body("referenceNumber", equalTo("DOC-001"));    }
    @Test    @DisplayName("Geçersiz girdi için 400 döndürmeli")    void givenInvalidRequest_whenCreate_thenReturns400() {      // ACT & ASSERT      given()          .contentType(ContentType.JSON)          .body("""              {                "referenceNumber": "",                "description": "Test"              }              """)          .when().post("/api/documents")          .then()          .statusCode(400);    }  }
  private Document createDocument(Long id, String referenceNumber) {    Document document = new Document();    document.setId(id);    document.setReferenceNumber(referenceNumber);    document.setStatus(DocumentStatus.PENDING);    return document;  }}

Gerçek Veritabanıyla Entegrasyon Testleri

java
@QuarkusTest@TestProfile(IntegrationTestProfile.class)@DisplayName("Document Integration Tests")class DocumentIntegrationTest {
  @Test  @Transactional  @DisplayName("Belge oluşturulmalı ve API üzerinden alınabilmeli")  void givenNewDocument_whenCreateAndRetrieve_thenSuccessful() {    // ACT - API üzerinden oluştur    Long id = given()        .contentType(ContentType.JSON)        .body("""            {              "referenceNumber": "INT-001",              "description": "Integration test",              "validUntil": "2030-01-01T00:00:00Z",              "categories": ["test"]            }            """)        .when().post("/api/documents")        .then()        .statusCode(201)        .extract().path("id");
    // ASSERT - API üzerinden al    given()        .when().get("/api/documents/" + id)        .then()        .statusCode(200)        .body("referenceNumber", equalTo("INT-001"));  }}

JaCoCo ile Kapsam

Maven Yapılandırması (Tam)

xml
<plugin>  <groupId>org.jacoco</groupId>  <artifactId>jacoco-maven-plugin</artifactId>  <version>0.8.13</version>  <executions>    <!-- Test yürütmesi için agent'ı hazırla -->    <execution>      <id>prepare-agent</id>      <goals>        <goal>prepare-agent</goal>      </goals>    </execution>
    <!-- Kapsam raporu oluştur -->    <execution>      <id>report</id>      <phase>verify</phase>      <goals>        <goal>report</goal>      </goals>    </execution>
    <!-- Kapsam eşiklerini zorla -->    <execution>      <id>check</id>      <goals>        <goal>check</goal>      </goals>      <configuration>        <rules>          <rule>            <element>BUNDLE</element>            <limits>              <limit>                <counter>LINE</counter>                <value>COVEREDRATIO</value>                <minimum>0.80</minimum>              </limit>              <limit>                <counter>BRANCH</counter>                <value>COVEREDRATIO</value>                <minimum>0.70</minimum>              </limit>            </limits>          </rule>        </rules>      </configuration>    </execution>  </executions></plugin>

Kapsam ile testleri çalıştırın:

bash
mvn clean testmvn jacoco:reportmvn jacoco:check
# Rapor: target/site/jacoco/index.html

Test Bağımlılıkları

xml
<dependencies>    <!-- Quarkus Test -->    <dependency>        <groupId>io.quarkus</groupId>        <artifactId>quarkus-junit5</artifactId>        <scope>test</scope>    </dependency>    <dependency>        <groupId>io.quarkus</groupId>        <artifactId>quarkus-junit5-mockito</artifactId>        <scope>test</scope>    </dependency>
    <!-- Mockito -->    <dependency>        <groupId>org.mockito</groupId>        <artifactId>mockito-core</artifactId>        <scope>test</scope>    </dependency>
    <!-- AssertJ (JUnit assertion'larına tercih edilir) -->    <dependency>        <groupId>org.assertj</groupId>        <artifactId>assertj-core</artifactId>        <version>3.24.2</version>        <scope>test</scope>    </dependency>
    <!-- REST Assured -->    <dependency>        <groupId>io.rest-assured</groupId>        <artifactId>rest-assured</artifactId>        <scope>test</scope>    </dependency>
    <!-- Camel Test -->    <dependency>        <groupId>org.apache.camel.quarkus</groupId>        <artifactId>camel-quarkus-junit5</artifactId>        <scope>test</scope>    </dependency></dependencies>

En İyi Uygulamalar

Test Organizasyonu

  • Testleri test edilen metoda göre gruplandırmak için @Nested sınıflar kullanın
  • Raporlarda görünür okunabilir açıklamalar için @DisplayName kullanın
  • Test metodları için givenX_whenY_thenZ isimlendirme kuralını izleyin
  • Tekrarı azaltmak için ortak test verisi kurulumunda @BeforeEach kullanın

Test Yapısı

  • Açık yorumlarla AAA desenini izleyin (// ARRANGE, // ACT, // ASSERT)
  • Başarı senaryoları için assertDoesNotThrow kullanın
  • Mesaj doğrulamalı exception senaryoları için assertThrows kullanın
  • AssertJ contains() veya isEqualTo() kullanarak exception mesajlarının beklenen değerlerle eşleştiğini doğrulayın

Test Kapsamı

  • Tüm public metodlar için mutlu yolları test edin
  • Null girdi işlemeyi test edin
  • Edge case'leri test edin (boş koleksiyonlar, sınır değerleri, negatif ID'ler, boş string'ler)
  • Exception senaryolarını kapsamlı biçimde test edin
  • Tüm harici bağımlılıkları mock'layın (repository'ler, servisler, Camel endpoint'leri)
  • %80+ satır kapsamı, %70+ branch kapsamı hedefleyin

Assertion'lar

  • Değer kontrolleri için JUnit assertion'ları yerine AssertJ'yi tercih edin (assertThat)
  • Okunabilirlik için akıcı AssertJ API'si kullanın: assertThat(list).hasSize(3).contains(item)
  • Exception'lar için: JUnit assertThrows ile yakalayın, ardından AssertJ ile mesajı doğrulayın
  • Fırlatılmayan başarı yolları için: JUnit assertDoesNotThrow kullanın
  • Koleksiyonlar için: extracting(), filteredOn(), containsExactly()

Entegrasyon Testi

  • Entegrasyon testleri için @QuarkusTest kullanın
  • Quarkus testlerinde bağımlılıkları mock'lamak için @InjectMock kullanın
  • API testi için REST Assured'ı tercih edin
  • Test'e özel yapılandırma için @TestProfile kullanın

Event-Driven Test

  • AdviceWith ve MockEndpoint ile Camel route'larını test edin
  • @CamelQuarkusTest annotasyonu kullanın (bağımsız Camel testleri kullanıyorsanız)
  • Mesaj içeriğini, başlıklarını ve yönlendirme mantığını doğrulayın
  • Hata işleme route'larını ayrı ayrı test edin
  • Unit testlerde harici sistemleri (RabbitMQ, S3, veritabanları) mock'layın

Camel Route Testi

  • Mesaj akışını doğrulamak için MockEndpoint kullanın
  • Test için route'ları değiştirmek üzere AdviceWith kullanın (endpoint'leri mock'larla değiştirin)
  • Mesaj dönüşümünü ve marshalling'i test edin
  • Exception işleme ve dead letter queue'ları test edin

Async İşlem Testi

  • CompletableFuture başarı ve başarısızlık senaryolarını test edin
  • Async tamamlanmayı beklemek için testlerde .join() kullanın
  • CompletableFuture'dan exception yayılımını test edin
  • LogContext yayılımını async işlemlere doğrulayın

Performans

  • Testleri hızlı ve izole tutun
  • Testleri sürekli modda çalıştırın: mvn quarkus:test
  • Girdi varyasyonları için parametreli testler (@ParameterizedTest) kullanın
  • Yeniden kullanılabilir test verisi builder'ları veya factory metodları oluşturun

Quarkus'a Özgü

  • En son LTS sürümünde kalın (Quarkus 3.x)
  • Native derleme uyumluluğunu periyodik olarak test edin
  • Farklı senaryolar için Quarkus test profillerini kullanın
  • Yerel test için Quarkus dev servislerinden yararlanın
  • @MockBean yerine @InjectMock kullanın (Quarkus'a özgü)

Doğrulama En İyi Uygulamaları

  • Mock'lanmış bağımlılıklardaki etkileşimleri her zaman doğrulayın
  • Hata senaryolarında metodların ÇAĞRILMADIĞINI sağlamak için verify(mock, never()) kullanın
  • Karmaşık argüman eşleştirme için argThat() kullanın
  • Önem taşıdığında çağrı sırasını doğrulayın: Mockito'dan InOrder

來源與署名

來源:affaan-m/ECC位於docs/tr/skills/quarkus-tdd提交ef648e0

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架