Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Desarrollo guiado por pruebas para Quarkus 3.x LTS usando JUnit 5, Mockito, REST Assured, pruebas Camel y JaCoCo. Usar al agregar funcionalidades, corregir bugs o refactorizar servicios orientados a eventos.
.claude/skills/affaan-m-quarkus-tdd/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 191% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 212% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 317% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 1306% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 373% | 0% |
80%+ kapsam (unit + integration) ile Quarkus 3.x servisleri için TDD rehberi. Apache Camel ile event-driven mimariler için optimize edilmiştir.
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()); } } }
givenX_whenY_thenZ// ARRANGE, // ACT, // ASSERT yorumlarıverify() kullanınnever() kullanınjava@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"); } } }
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"); } } }
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"); } } }
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; } }
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")); } }
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:
bashmvn clean test mvn jacoco:report mvn jacoco:check # Rapor: target/site/jacoco/index.html
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>
@Nested sınıflar kullanın@DisplayName kullanıngivenX_whenY_thenZ isimlendirme kuralını izleyin@BeforeEach kullanın// ARRANGE, // ACT, // ASSERT)assertDoesNotThrow kullanınassertThrows kullanıncontains() veya isEqualTo() kullanarak exception mesajlarının beklenen değerlerle eşleştiğini doğrulayınassertThat)assertThat(list).hasSize(3).contains(item)assertThrows ile yakalayın, ardından AssertJ ile mesajı doğrulayınassertDoesNotThrow kullanınextracting(), filteredOn(), containsExactly()@QuarkusTest kullanın@InjectMock kullanın@TestProfile kullanınAdviceWith ve MockEndpoint ile Camel route'larını test edin@CamelQuarkusTest annotasyonu kullanın (bağımsız Camel testleri kullanıyorsanız)MockEndpoint kullanınAdviceWith kullanın (endpoint'leri mock'larla değiştirin).join() kullanınmvn quarkus:test@ParameterizedTest) kullanın@MockBean yerine @InjectMock kullanın (Quarkus'a özgü)verify(mock, never()) kullanınargThat() kullanınInOrder| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 16,115 | 10,366 | -36% | 1 | 1 | 0% | 3,591 | 10,450 | +191% | 0 | 0 | — |
case-02 | fail→pass | 16,613 | 12,501 | -25% | 1 | 1 | 0% | 3,381 | 10,536 | +212% | 0 | 0 | — |
case-03 | fail→fail | 11,391 | 7,081 | -38% | 1 | 1 | 0% | 2,326 | 9,609 | +313% | 0 | 0 | — |
case-04 | pass→pass | 7,646 | 7,951 | +4% | 1 | 1 | 0% | 1,717 | 9,611 | +460% | 0 | 0 | — |
case-05 | pass→pass | 9,288 | 8,161 | -12% | 1 | 1 | 0% | 1,968 | 9,506 | +383% | 0 | 0 | — |
case-06 | fail→pass | 11,144 | 8,258 | -26% | 1 | 1 | 0% | 2,307 | 9,612 | +317% | 0 | 0 | — |
case-07 | pass→pass | 8,330 | 6,610 | -21% | 1 | 1 | 0% | 1,661 | 9,312 | +461% | 0 | 0 | — |
case-08 | fail→pass | 3,641 | 4,701 | +29% | 1 | 1 | 0% | 621 | 8,730 | +1306% | 0 | 0 | — |
case-09 | fail→pass | 9,376 | 8,944 | -5% | 1 | 1 | 0% | 2,096 | 9,924 | +373% | 0 | 0 | — |
case-10 | pass→pass | 7,779 | 6,131 | -21% | 1 | 1 | 0% | 1,673 | 9,130 | +446% | 0 | 0 | — |
case-11 | fail→pass | 11,812 | 10,070 | -15% | 1 | 1 | 0% | 2,291 | 10,083 | +340% | 0 | 0 | — |
case-12 | fail→pass | 7,716 | 4,260 | -45% | 1 | 1 | 0% | 1,670 | 8,807 | +427% | 0 | 0 | — |
case-13 | pass→pass | 18,956 | 13,314 | -30% | 1 | 1 | 0% | 3,756 | 10,541 | +181% | 0 | 0 | — |
case-14 | pass→pass | 7,865 | 5,127 | -35% | 1 | 1 | 0% | 1,487 | 8,795 | +491% | 0 | 0 | — |
case-15 | fail→pass | 8,618 | 5,801 | -33% | 1 | 1 | 0% | 1,546 | 8,939 | +478% | 0 | 0 | — |
case-16 | pass→pass | 10,533 | 6,979 | -34% | 1 | 1 | 0% | 1,889 | 9,226 | +388% | 0 | 0 | — |
case-17 | pass→pass | 6,714 | 3,050 | -55% | 1 | 1 | 0% | 1,170 | 8,441 | +621% | 0 | 0 | — |
case-18 | fail→fail | 8,896 | 6,981 | -22% | 1 | 1 | 0% | 1,887 | 9,259 | +391% | 0 | 0 | — |
case-19 | pass→pass | 7,194 | 5,162 | -28% | 1 | 1 | 0% | 1,579 | 8,967 | +468% | 0 | 0 | — |
case-20 | pass→pass | 12,871 | 11,716 | -9% | 1 | 1 | 0% | 2,826 | 10,590 | +275% | 0 | 0 | — |
case-21 | pass→pass | 6,105 | 5,877 | -4% | 1 | 1 | 0% | 1,275 | 9,100 | +614% | 0 | 0 | — |
case-22 | pass→pass | 11,280 | 9,549 | -15% | 1 | 1 | 0% | 2,265 | 10,023 | +343% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +36 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.