基于 SOC 职业分类
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/affaan-m/ECC --skill quarkus-tdd命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
Transform Claude Code into a fully autonomous agent system with persistent memory, scheduled operations, computer use, and task queuing. Replaces standalone agent frameworks (Hermes, AutoGPT) by leveraging Claude Code's native crons, dispatch, MCP tools, and memory. Use when the user wants continuous autonomous operation, scheduled tasks, or a self-directing agent loop.
Fact-forcing gate that blocks Edit/Write/Bash (including MultiEdit) and demands concrete investigation (importers, data schemas, user instruction) before allowing the action. Measurably improves output quality by +2.25 points vs ungated agents.
Instinct-based learning system that observes sessions via hooks, creates atomic instincts with confidence scoring, and evolves them into skills/commands/agents. v2.1 adds project-scoped instincts to prevent cross-project contamination. Use when capturing lessons from a session, managing instincts, or promoting them into skills, commands, or agents.
| name | quarkus-tdd |
| description | JUnit 5、Mockito、REST Assured、Camelテスト、JaCoCoを使用したQuarkus 3.xのテスト駆動開発。機能追加、バグ修正、またはイベント駆動サービスのリファクタリング時に使用。 |
| origin | ECC |
80%以上のカバレッジ(ユニット+統合)を備えたQuarkus 3.xサービスのTDD指導。Apache Camelを使用したイベント駆動アーキテクチャに最適化。
包括的で読みやすいテストのため、以下の構造化されたアプローチに従います:
@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));
}
}
}
givenX_whenY_thenZ// ARRANGE, // ACT, // ASSERT コメントverify() でメソッド呼び出しが正しく行われたか確認never() でエラーシナリオでメソッドが呼ばれていないことを確認@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();
producerTemplate.sendBody(, testPayload);
mockRabbitMQ.assertIsSatisfied();
assertThat(mockRabbitMQ.getExchanges()).hasSize();
mockRabbitMQ.getExchanges().get().getIn().getBody(String.class);
assertThat(body).contains();
}
Exception {
();
camelContext.addEndpoint(, mockMarshal);
mockMarshal.expectedMessageCount();
camelContext.getRouteController().stopRoute();
AdviceWith.adviceWith(camelContext, , advice -> {
advice.weaveAddLast().to();
});
camelContext.getRouteController().startRoute();
producerTemplate.sendBody(, testPayload);
mockMarshal.assertIsSatisfied();
mockMarshal.getExchanges().get().getIn().getBody(String.class);
assertThat(body).contains();
assertThat(body).contains();
}
}
{
Exception {
camelContext.getEndpoint(, MockEndpoint.class);
mockInvoice.expectedMessageCount();
camelContext.getRouteController().stopRoute();
AdviceWith.adviceWith(camelContext, , advice -> {
advice.weaveByToString().replace().to();
});
camelContext.getRouteController().startRoute();
producerTemplate.sendBodyAndHeader(,
testPayload, , );
mockInvoice.assertIsSatisfied();
}
Exception {
camelContext.getEndpoint(, MockEndpoint.class);
mockError.expectedMessageCount();
camelContext.getRouteController().stopRoute();
AdviceWith.adviceWith(camelContext, , advice -> {
advice.weaveByToString()
.replace().to();
});
camelContext.getRouteController().startRoute();
(documentValidator.validate(any())).thenThrow( ());
producerTemplate.sendBody(, testPayload);
mockError.assertIsSatisfied();
mockError.getExchanges().get().getException();
assertThat(exception).isInstanceOf(ValidationException.class);
assertThat(exception.getMessage()).contains();
}
}
}
@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ペイロードが与えられた場合、例外をスロー")
{
;
assertThrows(
NullPointerException.class,
() -> eventService.createSuccessEvent(nullPayload, )
);
assertThat(exception.getMessage()).isEqualTo();
verify(eventRepository, never()).persist(any());
}
}
{
Exception {
;
(objectMapper.writeValueAsString(testPayload)).thenReturn();
assertDoesNotThrow(() ->
eventService.createErrorEvent(testPayload, , errorMessage));
verify(eventRepository).persist(argThat(event ->
event.getType().equals() &&
event.getStatus() == EventStatus.ERROR &&
event.getErrorMessage().equals(errorMessage) &&
event.getPayload().equals()
));
}
{
assertThrows(
IllegalArgumentException.class,
() -> eventService.createErrorEvent(testPayload, , blankMessage)
);
assertThat(exception.getMessage()).contains();
}
}
}
@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
CompletableFuture<StoredDocumentInfo> future =
fileStorageService.uploadOriginalFile(testInputStream, ,
testLogContext, InvoiceFormat.UBL);
future.join();
assertThat(result).isNotNull();
assertThat(result.getPath()).isNotBlank();
assertThat(result.getSize()).isEqualTo();
assertThat(result.getUploadedAt()).isNotNull();
verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));
}
{
doAnswer(invocation -> {
((Runnable) invocation.getArgument()).run();
;
}).(executorService).execute(any(Runnable.class));
(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class)))
.thenThrow( ());
CompletableFuture<StoredDocumentInfo> future =
fileStorageService.uploadOriginalFile(testInputStream, ,
testLogContext, InvoiceFormat.UBL);
assertThatThrownBy(() -> future.join())
.isInstanceOf(CompletionException.class)
.hasCauseInstanceOf(StorageException.class)
.hasMessageContaining();
}
Exception {
AtomicReference<LogContext> capturedContext = <>();
doAnswer(invocation -> {
capturedContext.set(CustomLog.getCurrentContext());
((Runnable) invocation.getArgument()).run();
;
}).(executorService).execute(any(Runnable.class));
fileStorageService.uploadOriginalFile(testInputStream, ,
testLogContext, InvoiceFormat.UBL).join();
assertThat(capturedContext.get()).isNotNull();
assertThat(capturedContext.get().get()).isEqualTo();
}
}
}
@QuarkusTest
@DisplayName("DocumentResource API Tests")
class DocumentResourceTest {
@InjectMock
DocumentService documentService;
@Nested
@DisplayName("GET /api/documents のテスト")
class ListDocuments {
@Test
@DisplayName("ドキュメントが存在する場合、ドキュメント一覧を返す")
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 のテスト")
class CreateDocument {
@Test
@DisplayName("有効なリクエストが与えられた場合、ドキュメント作成して201を返す")
void givenValidRequest_whenCreate_thenReturns201() {
// ARRANGE
Document document = createDocument(1L, "DOC-001");
when(documentService.create(any())).thenReturn(document);
// ACT & ASSERT
given()
.contentType(ContentType.JSON)
.body()
.().post()
.then()
.statusCode()
.header(, containsString())
.body(, equalTo());
}
{
given()
.contentType(ContentType.JSON)
.body()
.().post()
.then()
.statusCode();
}
}
Document {
();
document.setId(id);
document.setReferenceNumber(referenceNumber);
document.setStatus(DocumentStatus.PENDING);
document;
}
}
@QuarkusTest
@TestProfile(IntegrationTestProfile.class)
@DisplayName("Document Integration Tests")
class DocumentIntegrationTest {
@Test
@Transactional
@DisplayName("新規ドキュメントをAPIで作成・取得、成功する")
void givenNewDocument_whenCreateAndRetrieve_thenSuccessful() {
// ACT - APIで作成
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で取得
given()
.when().get("/api/documents/" + id)
.then()
.statusCode(200)
.body("referenceNumber", equalTo("INT-001"));
}
}
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.13</version>
<executions>
<!-- テスト実行用エージェント準備 -->
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<!-- カバレッジレポート生成 -->
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<!-- カバレッジ閾値を強制 -->
<execution>
<id>check</id>
check
BUNDLE
LINE
COVEREDRATIO
0.80
BRANCH
COVEREDRATIO
0.70
カバレッジ付きテスト実行:
mvn clean test
mvn jacoco:report
mvn jacoco:check
# レポート: target/site/jacoco/index.html
<dependencies>
<!-- Quarkus Testing -->
<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アサーション推奨) -->
<dependency>
<groupId>org.assertj</groupId>
assertj-core
3.24.2
test
io.rest-assured
rest-assured
test
org.apache.camel.quarkus
camel-quarkus-junit5
test
@Nestedクラス使用@DisplayName使用givenX_whenY_thenZ規則に従う// ARRANGE, // ACT, // ASSERT)でAAAパターン従うassertDoesNotThrow使用assertThrows使用contains()またはisEqualTo()で例外メッセージ検証assertThat使用)assertThat(list).hasSize(3).contains(item)assertThrowsでキャプチャ、AssertJでメッセージ検証assertDoesNotThrow使用extracting(), filteredOn(), containsExactly()使用@QuarkusTest使用@InjectMock使用@TestProfile使用AdviceWithとMockEndpointでCamelルートテスト@CamelQuarkusTest注釈使用(スタンドアロンCamelテスト)MockEndpoint使用AdviceWith使用.join()使用mvn quarkus:test@ParameterizedTest)使用@MockBean代わりに@InjectMock(Quarkus固有)使用verify(mock, never())使用argThat()使用InOrder(Mockitoから)で検証