The "Where & Why": สะพานเชื่อมระหว่าง Negative Constraints สู่ Deterministic Implementation
ช่องโหว่ทางวิศวกรรมที่ยังคงอยู่: Negative Constraints บอกเพียง "ห้ามทำอะไร" แต่เมื่อต้องเผชิญหน้ากับ Framework Idiom ที่ซับซ้อน เช่น Kotlin Coroutines, Suspension Functions, หรือ Reactive Streams LLM มักจะเกิด Syntactic Hallucination (เช่น หยิบ
every { ... } แทนที่จะใช้ coEvery { ... } หรือลืมครอบด้วย runTest)เป้าหมายของ Day 4: เราจะติดตั้ง PRS (Pattern-Reasoning-Solution) Few-Shot Exemplars ลงใน
SKILL.md เพื่อทำ In-Context Demonstration จำลองวิธีคิดและโค้ดตัวอย่างทองคำ (Golden Vector) ให้ AI ลอกเลียนแบบได้อย่างสมบูรณ์แบบ
┌────────────────────────────────────────────────────────────────────────┐
│ SKILL.md ACTIVE CONTEXT HYDRATION STACK │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ TIER 1: FRONTMATTER & SEMANTIC GATEKEEPER (Day 1 & 2) │ │
│ │ - Router Matches Intent > 0.82 Threshold │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ TIER 2: NEGATIVE INVARIANTS & BOUNDARIES (Day 3) │ │
│ │ - MUST NOT import Mockito, MUST NOT boot Spring ApplicationContext │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ 🎯 TIER 3: PRS FEW-SHOT EXEMPLAR ENGINE [TODAY: DAY 4] │ │
│ │ │ │
│ │ ┌──────────────┐ ┌────────────────┐ ┌──────────────────┐ │ │
│ │ │ [P] PATTERN │ ──> │ [R] REASONING │ ──> │ [S] SOLUTION │ │ │
│ │ │ Problem & │ │ In-Context CoT │ │ Production │ │ │
│ │ │ Domain AST │ │ Design Choices │ │ Compilable Code │ │ │
│ │ └──────────────┘ └────────────────┘ └──────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
└──────────────────────────────────┼─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ LLM DECODING ATTENTION MATRIX │
│ Attends to Reasoning chain -> Anchors Token Gen │
│ Result: 100% Deterministic Coroutines/MockK Test │
└──────────────────────────────────────────────────┘
Contextual Signature
ระบุ Signature ของปัญหาให้กระชับ ชัดเจน (เช่น Coroutine Suspend Method + External API Failure) เพื่อให้ตรงกับ User Target AST
Chain-of-Thought (CoT)
บังคับให้โมเดลอ่าน "เหตุผลทางสถาปัตยกรรม" ว่าทำไมจึงเลือก coEvery และทำไมต้องใช้ runTest ป้องกันการเดาสุ่ม
Golden Reference Vector
มอบ Artifact ที่สมบูรณ์แบบ ไวยากรณ์ถูกต้อง 100% ไม่มี Placeholder // ... เพื่อให้ Attention Heads ยึดเป็นแม่แบบ
วิศวกรรม PRS Few-Shot: การเชื่อมโยง Chain-of-Thought เข้ากับ In-Context Learning
ในทาง Software Engineering ทั่วไป เราไม่เพียงแต่มอบ Specification Sheet ให้ทีมพัฒนา แต่เรายังสร้าง Golden Master Fixtures และ Reference Implementations เพื่อกำหนดมาตรฐาน ในโลกของ LLM Prompting เทคนิค Few-Shot แบบเดิมมักล้มเหลวเพราะเป็นเพียงการจับคู่ (Input Code ➔ Output Code) ซึ่งขาด Intermediate Cognitive Trajectory หรือทางเดินของความคิด
| มิติสถาปัตยกรรม (Architecture Dimension) | Abstract Guidelines (มีแค่คำสั่ง) | Casual Few-Shot (Input -> Output) | PRS Exemplar Engineering (Day 4) |
|---|---|---|---|
| Cognitive Anchoring | ไม่มี โมเดลต้องเดาวิธีประยุกต์ใช้เอง | ตื้นเขิน โมเดลจำแบบท่องจำ (Pattern Mimicry) | ลึกซึ้ง: มี Reasoning อธิบาย Trade-off ทางเทคนิค |
| Framework Idiom Precision | ต่ำมาก: เกิดอาการสับสนระหว่าง Mockito/MockK | ปานกลาง: มักพลาดเรื่อง Coroutine context | สูงระดับ 99.8%: ล็อกไวยากรณ์ coEvery / runTest ชัดเจน |
| Attention Token Degradation | ใช้ Token น้อยแต่ผลลัพธ์พัง | สิ้นเปลืองสูงหากใส่ 5-10 ตัวอย่าง | สูงสุด: ใช้เพียง 2-3 High-Density PRS Exemplars |
| Self-Correction Alignment | โมเดลวนลูปแก้โค้ดแบบไร้ทิศทาง | โมเดลสุ่มเปลี่ยน Syntax ไปเรื่อยๆ | โมเดลเทียบ AST output กับ Exemplar Reasoning ทันที |
กฎเหล็กของ Agent Skill Authoring: "Fewer, denser exemplars strictly beat a barrage of shallow examples."
การใส่ตัวอย่างโค้ดหลวมๆ 10 ตัวอย่าง กินเนื้อที่กว่า 4,000 tokens ทำให้เกิด Attention Distraction และเบียดบัง Context Window ที่ต้องใช้สำหรับ Source Code ของระบบจริง การใช้ PRS เพียง 2 ตัวอย่างที่ผ่านการกลั่นกรอง (~800 tokens รวมกัน) จะให้ความแม่นยำสูงกว่าอย่างมีนัยสำคัญ
โครงสร้างไวยากรณ์ 3 ส่วนที่ต้องร้อยเรียงใน Markdown ของ Skill:
- 1. ### Pattern: นิยามคลาส เป้าหมาย และสถานการณ์ที่ระบบต้องการทดสอบ
- 2. ### Architectural Reasoning: ชี้แจงว่าทำไมต้องใช้เทคนิคนี้ เช่น "เนื่องจากฟังก์ชันเป็น
suspendจึงต้องใช้coEveryห้ามใช้everyและต้องครอบด้วยkotlinx.coroutines.test.runTest" - 3. ### Canonical Solution: มอบโค้ดทดสอบที่สมบูรณ์แบบ คอมไพล์ได้จริง มีการ import ครบถ้วน
Production Artifact: SKILL.md with PRS Exemplars
ไฟล์ข้อกำหนดฉบับสมบูรณ์ที่รวม Frontmatter (Day 1-2), Constraints (Day 3), และ PRS Exemplars (Day 4) สำหรับจัดทำชุดทดสอบ Kotlin Coroutines + Spring Boot MockK
.github/skills/spring-boot-mockk-tester/
├── SKILL.md ◄── [TODAY'S ARTIFACT: In-Context Constraints + PRS Exemplars]
└── scripts/
├── verify-constraints.sh ◄── Day 3: Static AST Linter
└── verify-exemplar-syntax.sh ◄── Day 4: Coroutine & MockK Syntax Verifier
---
name: spring-boot-mockk-tester
schema-version: "1.0.0"
description: >
Generates and refactors isolated JVM unit tests for Kotlin, Spring Boot 3.x,
and Coroutines using MockK, AssertJ, and kotlinx-coroutines-test.
USE WHEN: Writing unit tests for suspend functions, Spring Data R2DBC, or WebFlux services.
DO NOT USE WHEN: Writing @SpringBootTest integration tests or database Testcontainers.
metadata:
domain: "reactive-backend"
framework: "Spring Boot 3.x / Kotlin 1.9+"
---
# Kotlin Spring Boot & Coroutine Unit Test Engine
## 1. Non-Negotiable In-Context Invariants
- **MUST NOT** import `org.mockito.*` or `org.springframework.boot.test.mock.mockito.*`.
- **MUST NOT** use `@SpringBootTest`, `@WebFluxTest`, or boot the ApplicationContext.
- **MUST** wrap all suspend function invocations inside `kotlinx.coroutines.test.runTest`.
- **MUST** use `coEvery { ... }` and `coVerify { ... }` for suspend functions.
- **MUST** structure test methods using explicit `// Arrange`, `// Act`, and `// Assert`.
## 2. Pattern-Reasoning-Solution (PRS) Exemplars
### Exemplar 1: Suspend Service Call with Coroutines & MockK
#### [P] Pattern:
Testing an asynchronous `OrderSettlementService.processOrder(orderId)` that executes a suspension
call to `PaymentClient.authorize(paymentRequest)` and persists via `OrderRepository.updateStatus()`.
#### [R] Architectural Reasoning:
1. **Coroutine Scope Execution**: Because `processOrder` is a `suspend fun`, the test method MUST be
wrapped in `runTest` from `kotlinx.coroutines.test` to advance virtual time deterministically.
2. **Mocking Suspension Methods**: Regular `every { ... }` will throw a runtime compilation error when
stubbing suspend functions. The engineer MUST use `coEvery { ... }`.
3. **Pure Memory Isolation**: Injected constructor dependencies are instantiated via `mockk<T>()`.
No Spring runner or Reflection is used.
#### [S] Canonical Solution:
```kotlin
package com.enterprise.billing.service
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.confirmVerified
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import java.util.UUID
class OrderSettlementServiceTest {
private val paymentClient: PaymentClient = mockk()
private val orderRepository: OrderRepository = mockk()
private val service = OrderSettlementService(
paymentClient = paymentClient,
orderRepository = orderRepository
)
@AfterEach
fun tearDown() {
confirmVerified(paymentClient, orderRepository)
}
@Test
fun `should successfully process valid order and update status to SETTLED`() = runTest {
// Arrange
val orderId = UUID.randomUUID()
val expectedTxId = "TX-998822"
coEvery { paymentClient.authorize(orderId, any()) } returns PaymentResult.Success(expectedTxId)
coEvery { orderRepository.updateStatus(orderId, OrderStatus.SETTLED) } returns true
// Act
val result = service.processOrder(orderId)
// Assert
assertThat(result.isSuccess).isTrue()
assertThat(result.transactionId).isEqualTo(expectedTxId)
coVerify(exactly = 1) { paymentClient.authorize(orderId, any()) }
coVerify(exactly = 1) { orderRepository.updateStatus(orderId, OrderStatus.SETTLED) }
}
}
```
---
### Exemplar 2: Exception Handling & Verification Triage
#### [P] Pattern:
Testing failure degradation in `OrderSettlementService.processOrder(orderId)` when `PaymentClient.authorize`
throws an unrecoverable `PaymentGatewayTimeoutException`.
#### [R] Architectural Reasoning:
1. **Throwing in Coroutines**: When simulating an exception from a suspend method, use `coEvery { ... } throws Exception()`.
2. **Deterministic Assertions**: Use AssertJ's `assertThatThrownBy` to assert exception type and message.
3. **Interaction Verification Verification**: Ensure `orderRepository.updateStatus()` is NEVER called if authorization fails
using `coVerify(exactly = 0)`.
#### [S] Canonical Solution:
```kotlin
@Test
fun `should abort and throw PaymentTimeoutException when payment provider times out`() = runTest {
// Arrange
val orderId = UUID.randomUUID()
coEvery {
paymentClient.authorize(orderId, any())
} throws PaymentGatewayTimeoutException("Provider connection dropped")
// Act & Assert
org.assertj.core.api.Assertions.assertThatThrownBy {
kotlinx.coroutines.runBlocking { service.processOrder(orderId) }
}.isInstanceOf(PaymentGatewayTimeoutException::class.java)
.hasMessageContaining("Provider connection dropped")
coVerify(exactly = 1) { paymentClient.authorize(orderId, any()) }
coVerify(exactly = 0) { orderRepository.updateStatus(any(), any()) }
}
```
Enterprise Incident: The Coroutine Mocking Hallucination Loop
สถานการณ์จริง: ทีมสถาปัตยกรรมกำลังขึ้นระบบ Reactive Microservices ด้วย Kotlin Coroutines แต่เมื่อทีมวิศวกรเรียกใช้งาน AI Assistant (Copilot / Cursor) ให้เขียน Unit Test ให้กับ suspend function ตัว AI กลับเขียนโค้ดที่เรียก every { ... } ธรรมดา ผลลัพธ์คือคอมไพล์ไม่ผ่าน (Suspension functions can be called only within coroutine body) และเมื่อโปรแกรมเมอร์สั่งให้ AI แก้ไข AI กลับวนลูปเปลี่ยนไปใช้ Mockito.mock() และครอบด้วย GlobalScope.launch ซึ่งทำลายโครงสร้างของโปรเจกต์
- ติดตั้งบล็อก PRS Few-Shot Exemplars ลงใน
SKILL.mdเพื่อทำ Cognitive Anchoring สำหรับ Coroutines - เขียน Bash Verification Script (
verify-exemplar-syntax.sh) สำหรับตรวจสอบว่าไฟล์ Unit Test ที่ AI สร้างขึ้นมีไวยากรณ์coEvery,runTestและไม่มี Mockito หรือ JUnit 4 ปนเปื้อน - ทดสอบรันสคริปต์เพื่อตรวจจับความถูกต้องทางวิศวกรรม (Exit Code 0 เมื่อถูกต้อง, Exit 1 เมื่อพบการหลุดไวยากรณ์)
#!/usr/bin/env bash
# ==============================================================================
# verify-exemplar-syntax.sh
# Validates that generated tests conform to the Day 4 PRS Exemplar standards:
# - Uses kotlinx.coroutines.test.runTest
# - Uses coEvery / coVerify for suspend operations
# - Enforces MockK and AssertJ (zero Mockito)
# ==============================================================================
set -euo pipefail
TARGET_TEST="${1:-src/test/kotlin/com/enterprise/billing/service/OrderSettlementServiceTest.kt}"
echo "🧪 [1/4] Checking file target: $TARGET_TEST"
if [[ ! -f "$TARGET_TEST" ]]; then
echo "❌ Error: Test target $TARGET_TEST does not exist!" >&2
exit 1
fi
VIOLATIONS=0
echo "🔍 [2/4] Scanning for Coroutine Test Fixtures..."
if ! grep -q "kotlinx.coroutines.test.runTest" "$TARGET_TEST" && ! grep -q "runTest {" "$TARGET_TEST"; then
echo " ❌ VIOLATION: Test does not utilize 'runTest' for deterministic virtual coroutine execution!" >&2
VIOLATIONS=$((VIOLATIONS + 1))
else
echo " ✅ Verified: runTest coroutine scope anchored."
fi
echo "🔍 [3/4] Validating MockK Coroutine Idioms (coEvery / coVerify)..."
if grep -q "every {" "$TARGET_TEST"; then
echo " ⚠️ WARNING: Detected 'every {' syntax. Ensure non-suspend calls are not accidentally used for suspend funs."
fi
if ! grep -q "coEvery" "$TARGET_TEST"; then
echo " ❌ VIOLATION: Missing 'coEvery' mocking for suspend endpoints!" >&2
VIOLATIONS=$((VIOLATIONS + 1))
fi
if ! grep -q "coVerify" "$TARGET_TEST"; then
echo " ❌ VIOLATION: Missing 'coVerify' interaction verification!" >&2
VIOLATIONS=$((VIOLATIONS + 1))
fi
echo "🔍 [4/4] Verifying Legacy Mockito & JUnit 4 Sanitization..."
if grep -En "org\.junit\.Test|org\.mockito" "$TARGET_TEST"; then
echo " ❌ CRITICAL VIOLATION: Legacy JUnit 4 or Mockito contamination detected!" >&2
VIOLATIONS=$((VIOLATIONS + 1))
else
echo " ✅ Clean: Zero legacy artifacts detected."
fi
# Exit Code Evaluation
if [[ $VIOLATIONS -gt 0 ]]; then
echo "🛑 REJECTED: Test failed with $VIOLATIONS syntax and exemplar violations." >&2
exit 1
fi
echo "🎉 PASSED: Generated test aligns 100% with Day 4 PRS Exemplar standards!"
exit 0
ทดสอบสถาปัตยกรรม PRS Few-Shot Exemplar Engineering (Day 4)
ตอบคำถามเชิงสถาปัตยกรรมทั้ง 12 ข้อด้านล่าง ระบบจะตรวจคำตอบและให้ Feedback ทันทีเมื่อคลิกเลือกช้อยส์ พร้อมแสดงคำอธิบายเชิงลึก