quarkus-tdd
JUnit 5、Mockito、REST Assured、Camelテスト、JaCoCoを使用したQuarkus 3.xのテスト駆動開発。機能追加、バグ修正、またはイベント駆動サービスのリファクタリング時に使用。
DeepseekModel
官方收录技能
质量 优秀 · 90
v1.0.0
获取
https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-docs-ja-jp-skills-quarkus-tdd-skill-md&format=skill
下载 .skill
标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用
.skill 文件中 system_prompt 字段的实际内容。
name quarkus-tdd description JUnit 5、Mockito、REST Assured、Camelテスト、JaCoCoを使用したQuarkus 3.xのテスト駆動開発。機能追加、バグ修正、またはイベント駆動サービスのリファクタリング時に使用。 origin ECC Quarkus TDD Workflow 80%以上のカバレッジ(ユニット+統合)を備えたQuarkus 3.xサービスのTDD指導。Apache Camelを使用したイベント駆動アーキテクチャに最適化。 When to Use 新機能またはRESTエンドポイント バグ修正またはリファクタリング データアクセスロジック、セキュリティルール、またはリアクティブストリーム追加 Apache Camelルートとイベントハンドラーテスト RabbitMQを使用したイベント駆動サービステスト 条件フローロジック検証 CompletableFuture非同期操作検証 LogContextプロパゲーション テスト Workflow テストを先に書く(失敗するはず) 最小限のコードで合格実装 テストが緑の状態でリファクタリング JaCoCoでカバレッジ実装(80%以上を目標) Unit Tests with @Nested Organization 包括的で読みやすいテストのため、以下の構造化されたアプローチに従います: @ExtendWith(MockitoExtension.class) @DisplayName("OrderService Unit Tests") class OrderServiceTest { @Mock private OrderRepository orderRepository; @Mock private EventService eventService; @Mock private FulfillmentPublisher fulfillmentPublisher; @InjectMocks private OrderService orderService; private CreateOrderCommand validCommand; @BeforeEach void setUp () { validCommand = new CreateOrderCommand ( "customer-123" , List.of( new OrderLine ( "sku-123" , 2 )) ); } @Nested @DisplayName("createOrder のテスト") class CreateOrder { @Test @DisplayName("有効なコマンドが与えられた場合、注文を永続化してフルフィルメントイベントを発行する") void givenValidCommand_whenCreateOrder_thenPersistsAndPublishes () { // ARRANGE doNothing(). when (orderRepository).persist(any(Order.class)); // ACT OrderReceipt receipt = orderService.createOrder(validCommand); // ASSERT assertThat(receipt).isNotNull(); assertThat(receipt.customerId()).isEqualTo( "customer-123" ); verify(orderRepository).persist(any(Order.class)); verify(fulfillmentPublisher).publishAsync(receipt); verify(eventService).createSuccessEvent(receipt, "ORDER_CREATED" ); } @Test @DisplayName("顧客IDが無い場合、BadRequestをスロー") void givenMissingCustomerId_whenCreateOrder_thenThrowsBadRequest () { // ARRANGE CreateOrderCommand invalid = new CreateOrderCommand ( "" , validCommand.lines()); // ACT & ASSERT WebApplicationException exception = assertThrows( WebApplicationException.class, () -> orderService.createOrder(invalid) ); assertThat(exception.getResponse().getStatus()).isEqualTo( 400 ); verify(orderRepository, never()).persist(any(Order.class)); verify(fulfillmentPublisher, never()).publishAsync(any()); } @Test @DisplayName("永続化失敗時、エラーイベントを記録") void givenPersistenceFailure_whenCreateOrder_thenRecordsErrorEvent () { // ARRANGE doThrow( new PersistenceException ( "database unavailable" )) . when (orderRepository).persist(any(Order.class)); // ACT & ASSERT PersistenceException exception = assertThrows( PersistenceException.class, () -> orderService.createOrder(validCommand) ); assertThat(exception.getMessage()).contains( "database unavailable" ); verify(eventService).createErrorEvent( eq(validCommand), eq( "ORDER_CREATE_FAILED" ), contains( "database unavailable" ) ); verify(fulfillmentPublisher, never()).publishAsync(any()); } @Test @DisplayName("nullコマンドが与えられた場合、NullPointerExceptionをスロー") void givenNullCommand_whenCreateOrder_thenThrowsNullPointerException () { // ACT & ASSERT assertThrows( NullPointerException.class, () -> orderService.createOrder( null ) ); verify(orderRepository, never()).persist(any(Order.class)); } } } Key Testing Patterns @Nested クラス : テストするメソッド別にテストをグループ化 @DisplayName : テストレポート用の読みやすい説明提供 命名規則 : 明確性のため givenX_whenY_thenZ AAA パターン : 明示的な // ARRANGE , // ACT , // ASSERT コメント @BeforeEach : 重複削減のためテストデータを共通設定 assertDoesNotThrow : 例外をキャッチせずに成功シナリオをテスト assertThrows : AssertJを使用したメッセージ検証で例外シナリオをテスト 包括的カバレッジ : 正常系、null入力、エッジケース、例外をテスト 相互作用検証 : Mockito verify() でメソッド呼び出しが正しく行われたか確認 Never検証 : never() でエラーシナリオでメソッドが呼ばれていないことを確認 Testing Camel Routes @QuarkusTest @DisplayName("Business Rules Camel Route Tests") class BusinessRulesRouteTest { @Inject CamelContext camelContext; @Inject ProducerTemplate producerTemplate; @InjectMock EventService eventService; @InjectMock DocumentValidator documentValidator; private BusinessRulesPayload testPayload; @BeforeEach void setUp () { // ARRANGE - テストデータ testPayload = new BusinessRulesPayload (); testPayload.setDocumentId( 1L ); testPayload.setFlowProfile(FlowProfile.BASIC); } @Nested @DisplayName("business-rules-publisher ルートのテスト") class BusinessRulesPublisher { @Test @DisplayName("有効なペイロードが与えられた場合、メッセージをRabbitMQに送信") void givenValidPayload_whenPublish_thenMessageSentToQueue () throws Exception { // ARRANGE MockEndpoint mockRabbitMQ = camelContext.getEndpoint( "mock:rabbitmq" , MockEndpoint.class); mockRabbitMQ.expectedMessageCount( 1 ); // テスト用の実エンドポイントをモックに置き換え 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(JsonLibrary.Jackson)の後、bodyはJSON文字列 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に整形") 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 ルートのテスト") class DocumentProcessing { @Test @DisplayName("請求書タイプが与えられた場合、正しいプロセッサーにルーティング") 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("検証エラーが与えられた場合、エラーハンドラーにルーティング") 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" ); // バリデータビーンをモック化して例外をスロー when (documentValidator.validate(any())).thenThrow( new ValidationException ( "Invalid document" )); // 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" ); } } } Testing Event Services @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 のテスト") class CreateSuccessEvent { @Test @DisplayName("有効なペイロードが与えられた場合、正しい属性でサクセスイベント作成") 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("nullペイロードが与えられた場合、例外をスロー") 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 のテスト") class CreateErrorEvent { @Test @DisplayName("エラーが与えられた場合、エラーメッセージ付きエラーイベント作成") 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("不正なエラーメッセージが与えられた場合、例外をスロー") @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" ); } } } Testing CompletableFuture @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 のテスト") class UploadOriginalFile { @Test @DisplayName("有効なファイルが与えられた場合、ファイルアップロード成功とドキュメント情報を返す") 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
Agent 识别该技能的关键词,点击任意一个即可复制。
该技能未提供触发词。
下载的 .skill 包内含以下字段。
| 字段 | 说明 |
|---|---|
| format | 格式标识(skill/v1) |
| skill_id | 技能唯一 ID |
| name | 技能名称 |
| version | 版本号 |
| description | 技能描述 |
| category | 所属分类(数组) |
| trigger_words | 触发词列表 |
| tags | 标签列表 |
| source | 来源标识 |
| source_url | 来源链接(本页地址) |
| exported_at | 导出时间(每次下载生成) |
| system_prompt | 系统提示词正文 |
| model_config | 模型参数:provider / model / temperature / max_tokens / top_p |
| examples | 示例 |
| install_guide | 各平台导入说明(Coze / Dify / Claude / 自定义框架) |