← Phase 2 Phase 2 • Day 17 Implementation

Local SE Agent Masterclass

MacBook M3 16GB • Complete Implementation Standard
📍 ส่วนที่ 1: หมุดตั้งโจทย์ & วัตถุประสงค์หลัก (Title Connection)

Chapter 17 — Developing Your First Enterprise Skill: Unit Test Synthesizer

ทำไมต้องชื่อนี้และเรียนไปเพื่ออะไร?

ใน Day 16 เราสร้างเครื่องยนต์ SkillRegistry และ SkillLifecycleManager ไว้เรียบร้อยแล้ว แต่มันเป็นเพียง "เครื่องเล่นเกมที่ยังไม่มีตลับเกมเสียบ"

ปัญหาใหญ่ในโลกแห่งความเป็นจริง: หากคุณสั่งให้ AI ทั่วไปเขียน Unit Test มันจะเขียนแบบมือสมัครเล่นทันที—ไม่มีการ Mock ระบบภายนอก, ตั้งชื่อมั่ว, และเขียนทดสอบเฉพาะ Happy Path บรรทัดเดียว เมื่อรันจริงบน CI/CD ก็จะพังเพราะระบบไปแตะ Database จริง!

Day 17 เราจึงจะลงมือสร้าง Enterprise Skill ตัวจริงตัวแรก ในชื่อ unit_test_synthesizer ให้ครบเครื่องทั้ง 3 ชิ้นส่วน:
1. metadata.json: สารบัญย่อระดับ Stage 1 (~25 tokens)
2. instructions.md: กฎเหล็กระดับ Staff QA Architect (AAA Pattern, Isolated Mocking, Trio Coverage: Happy, Edge, Exception paths)
3. tools.py: Dedicated Tool สำหรับวัดเปอร์เซ็นต์ Test Coverage จริงผ่านคำสั่ง Maven โดยไม่ทำให้ Tool List หลักบวมพอง!

✓ Phase 1: Core ReAct & Workspace (Day 1-14) ✓ Day 15: Dynamic Skills Blueprint ✓ Day 16: SkillRegistry & Lifecycle Engine Day 17: First Enterprise Skill 🧪⚡ Day 18: AST Parsing with Tree-sitter
📂 ส่วนที่ 2: ผังโครงสร้างโปรเจกต์จริง & ตำแหน่งไฟล์ (Project File Structure)

ใน Day 17 เราจะเติมเต็มโครงสร้างของตลับสกิล skills/unit_test_synthesizer/ ให้สมบูรณ์แบบครบทั้ง 3 ไฟล์ พร้อมสร้างสคริปต์ทดสอบ test_enterprise_skill.py:

se_agent/
├── config.json                             # Model config & limits
├── se-agent                                # CLI wrapper
├── requirements.txt                       # ollama, chromadb, rich
├── main.py                               # Interactive CLI Cockpit
├── test_enterprise_skill.py               # [ไฟล์ใหม่ Day 17] สคริปต์ทดสอบการสังเคราะห์ Unit Test และการเรียก Dedicated Tool
├── skills/                                 # แหล่งเก็บ Pluggable Skill Cartridges
│   ├── __init__.py                         # Export: SkillRegistry, SkillLifecycleManager
│   ├── registry.py                         # Discovery & Catalog Engine
│   ├── lifecycle.py                        # Lifecycle Manager & Eviction
│   └── unit_test_synthesizer/              # [ตลับสกิลที่ 1: ติดตั้งครบชุดใน Day 17]
│       ├── metadata.json                   # [เสาหลักที่ 1] บัตรประจำตัวสกิล (id, triggers, short_description)
│       ├── instructions.md                 # [เสาหลักที่ 2] คู่มือปฏิบัติการกฎเหล็ก AAA Pattern + Mockito Isolation
│       └── tools.py                        # [เสาหลักที่ 3: ไฟล์ใหม่ Day 17] Dedicated Tool: run_coverage_check
├── memory/                                # Sliding Window & ChromaDB
├── core/                                  # ReAct Engine, Dispatcher, Guardrails, Workspace
└── sample_maven_project/
📍 1. metadata.json

บัตรประจำตัวสารบัญย่อสำหรับ Stage 1 ให้ Agent มองเห็นภาพรวมว่ามีทักษะนี้ในระบบ กินพื้นที่เพียง ~25 tokens

📍 2. instructions.md

คัมภีร์กฎเกณฑ์ระดับ Enterprise คุมโมเดลให้เขียนเทส 3 มิติ (Happy, Boundary, Exception) และตัดขาด Real DB ด้วย Mock

📍 3. tools.py (Dedicated)

เครื่องมือตรวจวัด Coverage เฉพาะทาง จะถูกเปิดเผยให้โมเดลมองเห็นเฉพาะตอนสกิลนี้กำลัง Active เพื่อรักษาความสะอาดของ Context

🏛️ ส่วนที่ 3: สถาปัตยกรรม Enterprise Test Synthesis (Mental Model)
โครงสร้างทดสอบระดับอุตสาหกรรม: The Enterprise AAA Pattern Architecture
1. ARRANGE (จัดเตรียม) Mocking

- จำลองข้อมูลนำเข้า (Mock Inputs)
- สกัดกั้น DB / Network ด้วย Mockito when(...).thenReturn(...)
- ประกาศ Expected Exception ถ้าเป็น Sad path

2. ACT (ลงมือทดสอบ) Execution

- สั่งรันเพียง 1 เมธอดเป้าหมายเท่านั้น
- เก็บผลลัพธ์ลงตัวแปรผลลัพธ์ (Actual Value)
- ไม่ผสมตรรกะอื่นในจังหวะนี้

3. ASSERT (ตรวจสอบผล) Verification

- ตรวจสอบค่าผลลัพธ์ด้วย assertEquals()
- ตรวจสอบพฤติกรรม Mock ด้วย verify(mock, times(1))
- ป้องกัน Regression 100%

ทำไมการเขียน Unit Test ของ AI ต้องมี Three Critical Paths เสมอ?
1. Happy Path

ทดสอบกรณีข้อมูลถูกต้อง 100% เพื่อยืนยันว่า Business Logic ทำงานตามเป้าหมาย

2. Boundary / Edge Case

ทดสอบค่าว่าง (null, empty string, list ว่าง) หรือค่าขอบสุด (0, ค่าติดลบ, MAX_INT)

3. Exception Path

ทดสอบว่าเมื่อข้อมูลผิดปกติ ฟังก์ชันต้องโยน Domain Exception ที่ถูกต้อง (เช่น UserNotFoundException)

💻 ส่วนที่ 4: ซอร์สโค้ดจริง & คำอธิบายทีละบรรทัด (Line-by-Line Implementation)
📄 1. skills/unit_test_synthesizer/metadata.json (Stage 1 Discovery Card) ขนาดจิ๋ว (~25 tokens) สแกนเข้า Catalog
{
  "id": "unit_test_synthesizer",
  "name": "Enterprise Unit Test Synthesizer",
  "description": "สร้าง Unit Test คุณภาพระดับ Enterprise ตามมาตรฐาน AAA Pattern, Mockito Isolation และครอบคลุมทั้ง Happy, Boundary และ Exception Paths",
  "triggers": ["test", "unit test", "junit", "pytest", "test case", "mock", "coverage"],
  "version": "1.0.0"
}
📄 2. skills/unit_test_synthesizer/instructions.md (Stage 2 Deep Operational Rules) คู่มือปฏิบัติการตัวเต็ม ขยายเข้า Memory เฉพาะตอน Active
# Role: Senior QA Automation & Test Architect
You are operating in specialized 'Unit Test Synthesizer' mode.
Your objective is to generate bulletproof, enterprise-standard unit test suites.

## Mandatory Architectural Rules:
1. **Strict AAA Structure**: Every test method MUST be clearly partitioned into:
   - `// Arrange`: Setup test doubles (Mocks), inputs, and expected outcomes.
   - `// Act`: Execute the single target method under test.
   - `// Assert`: Verify return values, exceptions, and verify mock invocation counts.
2. **Mocking & Isolation**: NEVER call real external databases, networks, or file systems. Mock all repositories and clients with Mockito.
3. **Naming Standard**: Test method names must strictly follow:
   `should_[ExpectedBehavior]_When_[StateUnderTest]()`
   Example: `should_ThrowUserNotFoundException_When_UserIdDoesNotExist()`
4. **Three Critical Paths (Mandatory Trio)**: Every tested method must have:
   - Happy Path: Valid input, expected successful outcome.
   - Boundary Path: Empty collections, null checks, edge values (0, MAX_INT).
   - Exception Path: Verify expected domain exceptions with `assertThrows`.
5. **Behavior Verification**: Always verify mock interactions with:
   `verify(repositoryMock, times(1)).findById(any());`
📄 3. skills/unit_test_synthesizer/tools.py (Dedicated Skill Tools) เครื่องมือเฉพาะทางตรวจวัด Coverage จริงผ่าน Maven Process
import subprocess
import shutil
from pathlib import Path
from typing import Dict, Any, Optional

def run_coverage_check(project_dir: str = "sample_maven_project") -> Dict[str, Any]:
    """
    Dedicated Tool ประจำสกิล Unit Test Synthesizer:
    สั่งรัน Maven Test เพื่อตรวจสอบผลลัพธ์และความพร้อมของชุดทดสอบ
    :param project_dir: ไดเรกทอรีโปรเจกต์ที่มี pom.xml
    :return: ผลการทดสอบและข้อความสรุป
    """
    proj_path = Path(project_dir).resolve()
    pom_file = proj_path / "pom.xml"

    if not pom_file.exists():
        return {
            "status": "error",
            "message": f"ไม่พบไฟล์ pom.xml ในโฟลเดอร์: {project_dir}"
        }

    mvn_bin = shutil.which("mvn")
    if not mvn_bin:
        return {
            "status": "error",
            "message": "ไม่พบคำสั่ง 'mvn' ในระบบ กรุณาตรวจสอบ PATH"
        }

    try:
        # สั่งรันคำสั่ง mvn test ในโหมด batch
        result = subprocess.run(
            [mvn_bin, "test", "-B"],
            cwd=str(proj_path),
            capture_output=True,
            text=True,
            timeout=120,
            encoding="utf-8",
            errors="replace"
        )

        passed = (result.returncode == 0)
        output = result.stdout or result.stderr or ""
        
        # สกัดข้อความ 15 บรรทัดสุดท้ายของบันทึกผล
        lines = [line.strip() for line in output.splitlines() if line.strip()]
        summary = "\n".join(lines[-15:]) if len(lines) > 15 else "\n".join(lines)

        return {
            "status": "success" if passed else "failure",
            "exit_code": result.returncode,
            "passed": passed,
            "summary": summary
        }
    except subprocess.TimeoutExpired:
        return {"status": "error", "message": "Maven test timed out after 120s"}
    except Exception as err:
        return {"status": "error", "message": f"Execution failed: {str(err)}"}
📄 4. test_enterprise_skill.py (สคริปต์ทดสอบครบวงจร) ทดสอบ Activation ➔ Synthesize Test โค้ดจริง ➔ เรียก Dedicated Tool ยืนยันผล
import sys
from pathlib import Path
from skills.registry import SkillRegistry
from skills.lifecycle import SkillLifecycleManager
from skills.unit_test_synthesizer.tools import run_coverage_check
from core import OllamaLLMClient

def main():
    print("=" * 70)
    print("🚀 เริ่มทดสอบ Day 17: Developing Your First Enterprise Skill")
    print("=" * 70)

    # 1. โหลด SkillRegistry และตรวจสอบตลับสกิล
    skills_folder = Path("./skills").resolve()
    registry = SkillRegistry(skills_dir=str(skills_folder))
    lifecycle = SkillLifecycleManager(registry)

    skill_id = "unit_test_synthesizer"
    assert registry.get_skill_info(skill_id) is not None, f"❌ ไม่พบสกิล {skill_id} ใน Registry!"
    print(f"📦 1. ตรวจพบตลับสกิล '{skill_id}' ในระบบเรียบร้อย")

    # 2. จำลองคำสั่งผู้ใช้ และสั่งให้ Lifecycle Manager ขยาย Instructions
    user_prompt = "ช่วยสร้าง Unit Test ที่ได้มาตรฐานสำหรับ UserService คลาสจัดการผู้ใช้งาน"
    matched_id = registry.find_skill_by_intent(user_prompt)
    print(f"\n💬 คำสั่งผู้ใช้: \"{user_prompt}\"")
    print(f"🎯 เจตนาตรงกับสกิล: {matched_id}")
    assert matched_id == skill_id

    # 3. สั่ง Activate สกิล
    instructions = lifecycle.activate_skill(matched_id)
    print("⚡ 2. ขยาย Instructions เข้าสู่ Working Memory สำเร็จ (ตัวอย่างกฎเหล็ก):")
    print("-" * 50)
    for line in instructions.splitlines()[:10]:
        print(f"   {line}")
    print("-" * 50)

    # 4. ทดสอบความพร้อมของโมเดล Local และสังเคราะห์โค้ดเทสตัวอย่าง
    client = OllamaLLMClient(model_name="qwen2.5-coder:7b")
    if not client.check_health():
        print("⚠️ ไม่พบ Ollama Daemon ข้ามขั้นตอนสังเคราะห์โค้ดด้วยโมเดลสด")
    else:
        print("\n🧠 3. ส่งคำสั่งให้ qwen2.5-coder:7b สังเคราะห์ Unit Test ตามกฎเหล็ก...")
        messages = [
            {"role": "system", "content": instructions},
            {"role": "user", "content": "เขียน Unit Test สำหรับฟังก์ชัน findById(Long id) ใน UserService ให้ครอบคลุม AAA Pattern"}
        ]
        sample_test_output = client.chat(messages, temperature=0.1)
        print("\n📝 ตัวอย่างโค้ดเทสที่สังเคราะห์ได้ (บางส่วน):")
        print("-" * 50)
        print(sample_test_output[:400] + "\n...")
        print("-" * 50)

    # 5. ทดสอบการเรียกใช้งาน Dedicated Tool ของสกิล
    print("\n🧪 4. ทดสอบเรียกใช้ Dedicated Tool: 'run_coverage_check()'...")
    tool_result = run_coverage_check(project_dir="sample_maven_project")
    print(f"👉 ผลลัพธ์จาก Tool: สถานะ={tool_result['status']}")
    print(f"📄 บันทึกผลลัพธ์:\n{tool_result.get('summary', tool_result.get('message'))}")

    # 6. ทดสอบการ Evict ปลดคืนพื้นที่
    print("\n🧹 5. งานเสร็จสิ้น สั่ง Evict สกิลออกจาก Working Memory...")
    lifecycle.evict_active_skill()
    assert lifecycle.active_skill_id is None
    print("✅ Eviction สำเร็จ: เคลียร์ Context สะอาดเอี่ยม 100%!")

    print("\n" + "=" * 70)
    print("🎉 ผ่านการทดสอบ Enterprise Skill: Unit Test Synthesizer ครบ 100%!")
    print("🏆 คุณมีตลับสกิลระดับอุตสาหกรรมพร้อมใช้งานจริงบน MacBook M3 แล้ว!")
    print("=" * 70)

if __name__ == "__main__":
    main()
ชำแหละไวยากรณ์สำคัญในตลับสกิล unit_test_synthesizer
"triggers": ["test", "unit test", "junit", "mock", ...]

Keyword-based Intent Binding: ช่วยให้ SkillRegistry.find_skill_by_intent() ตรวจพบเจตนาของผู้ใช้ได้ในระดับ Microseconds โดยไม่ต้องเสียเวลาถาม LLM ซ้ำ

should_[ExpectedBehavior]_When_[StateUnderTest]()

Living Documentation Standard: การบังคับไวยากรณ์การตั้งชื่อเทสผ่าน System Instructions ช่วยเปลี่ยนโค้ดเทสให้กลายเป็นเอกสารอธิบายระบบที่มีชีวิต อ่านปุ๊บรู้ปั๊บว่ากำลังทดสอบอะไร

subprocess.run([mvn_bin, "test", "-B"], cwd=...)

Sandboxed Process Execution: รันคำสั่งคอมไพล์ในโฟลเดอร์โปรเจกต์เป้าหมายแบบ Batch Mode เพื่อนำตัวเลขความสำเร็จและ Stack Trace กลับมาตรวจทานโค้ดที่เพิ่งสังเคราะห์

⚡ ส่วนที่ 5: ปฏิบัติการจริงบน Terminal Mac M3 (Step-by-Step Lab)

เปิด Terminal บน MacBook M3 แล้วทำตาม 5 ขั้นตอนนี้เพื่อติดตั้งตลับสกิล unit_test_synthesizer ให้สมบูรณ์แบบและสั่งรันการทดสอบทันที:

ขั้นตอนที่ 1: เข้าโฟลเดอร์โปรเจกต์และเปิด Virtual Environment
cd ~/se_agent
source .venv/bin/activate
ขั้นตอนที่ 2: สร้างโฟลเดอร์และไฟล์สำหรับ Day 17
mkdir -p skills/unit_test_synthesizer
touch skills/unit_test_synthesizer/metadata.json
touch skills/unit_test_synthesizer/instructions.md
touch skills/unit_test_synthesizer/tools.py
touch test_enterprise_skill.py
ขั้นตอนที่ 3: วางโค้ดลงในแต่ละไฟล์

- นำโค้ดใน ส่วนที่ 4 (ไฟล์ที่ 1) ไปวางใน skills/unit_test_synthesizer/metadata.json
- นำโค้ดใน ส่วนที่ 4 (ไฟล์ที่ 2) ไปวางใน skills/unit_test_synthesizer/instructions.md
- นำโค้ดใน ส่วนที่ 4 (ไฟล์ที่ 3) ไปวางใน skills/unit_test_synthesizer/tools.py
- นำโค้ดใน ส่วนที่ 4 (ไฟล์ที่ 4) ไปวางใน test_enterprise_skill.py

ขั้นตอนที่ 4: สั่งรันสคริปต์ทดสอบ Enterprise Skill
python test_enterprise_skill.py

🎉 ผลลัพธ์: ระบบจะสแกนพบตลับสกิล ➔ ขยายคู่มือกฎเหล็ก AAA Pattern ➔ สั่งให้โมเดล Qwen2.5-Coder สังเคราะห์โค้ดเทสจริง ➔ รัน Dedicated Tool วัดผลผ่าน Maven ➔ และทำการ Evict ปลดคืนพื้นที่แรมบน Mac M3 ครบวงจร 100%!

ขั้นตอนที่ 5 (สังเกตผล): ตรวจดูความพร้อมสำหรับ Day 18

ตอนนี้เรามีตลับสกิลสังเคราะห์ Unit Test ระดับ Enterprise เสียบเข้ากับสถาปัตยกรรม Microkernel แล้ว ใน Day 18 เราจะก้าวข้ามการมองโค้ดเป็นตัวอักษรดิบๆ (Raw Text) สู่การใช้ Tree-sitter AST Parsing เพื่อสกัดโครงกระดูกโค้ด ลด Token ลงได้ถึง 85% ครับ!

📝 ส่วนที่ 6: ชุดควิซประเมินความรู้เชิงวิศวกรรม 15 ข้อ (ตอบปุ๊บรู้ผลทันที)

ข้อที่ 1: วัตถุประสงค์หลักของ Day 17: Developing Your First Enterprise Skill คืออะไร?

ข้อที่ 2: โครงสร้าง AAA Pattern ในการเขียน Unit Test ประกอบด้วย 3 ขั้นตอนใดบ้าง?

ข้อที่ 3: ทำไมคำสั่งใน `instructions.md` ของ Unit Test Synthesizer จึงต้องสั่งห้ามไม่ให้ Unit Test เรียกใช้งาน Real Database โดยเด็ดขาด?

ข้อที่ 4: การตั้งชื่อฟังก์ชันเทสตามมาตรฐาน `should_ExpectedBehavior_When_StateUnderTest` มีประโยชน์อย่างไร?

ข้อที่ 5: ในมาตรฐาน Enterprise Unit Testing "Three Critical Paths" หมายถึงการครอบคลุมเคสประเภทใดบ้าง?

ข้อที่ 6: ไฟล์ `tools.py` ที่อยู่ในโฟลเดอร์ของสกิล (Dedicated Tools) มีบทบาทต่างจาก Tools ทั่วไปใน Day 4-5 อย่างไร?

ข้อที่ 7: ฟังก์ชัน `run_coverage_check()` ใน `tools.py` ทำหน้าที่อะไรในกระบวนการทำงานของสกิลนี้?

ข้อที่ 8: เมื่อ Agent รันฟังก์ชัน `run_coverage_check()` แล้วพบว่า Coverage ได้เพียง 40% และมี 1 เทสพัง (Failed) สิ่งที่ Agent ควรทำต่อคืออะไร?

ข้อที่ 9: ข้อมูลในไฟล์ `metadata.json` ของสกิลนี้มีผลต่อการทำงานของ `SkillRegistry` อย่างไร?

ข้อที่ 10: หลังจากที่เขียน Unit Test เสร็จสิ้นและรันตรวจสอบผ่านเรียบร้อยแล้ว `SkillLifecycleManager` ควรดำเนินการสิ่งใด?

ข้อที่ 11: หากเราต้องการเพิ่ม Enterprise Skill ที่สอง เช่น `git_release_manager` ในสัปดาห์หน้า เราต้องทำอย่างไร?

ข้อที่ 12: การใช้ Mockito `verify(mock, times(1)).method()` ใน Unit Test มีจุดประสงค์เพื่ออะไร?

ข้อที่ 13: ทำไมการมี Skill Instructions ที่เฉพาะเจาะจง จึงช่วยให้ Local LLM ขนาด 7B–14B เขียนเทสได้ดีกว่าโมเดลทั่วไป?

ข้อที่ 14: หลังจากที่เราสร้างและทดสอบ Custom Skill แรกสำเร็จใน Day 17 แล้ว ใน Day 18 เราจะก้าวไปสู่เรื่องใดต่อ?

ข้อที่ 15: ในฐานะสถาปนิกผู้ออกแบบระบบ Unit Test Synthesizer Skill สำเร็จใน Day 17 คุณรู้สึกอย่างไรต่อสถาปัตยกรรมนี้?

🏁 สรุปหมุดเชื่อมโยง Day 17 (Chapter Wrap-up)

เนื้อหาใน Day 17 — Developing Your First Enterprise Skill (Unit Test Synthesizer) ได้พิสูจน์แล้วว่า การสร้าง Skill Cartridge แยกเป็นโมดูลอิสระ (Metadata + AAA Instructions + Dedicated Tools) ช่วยให้ Agent ผลิต Unit Test ที่มีคุณภาพระดับ Enterprise ได้จริง โดยคงความเร็วและความประหยัด Token สูงสุดบน MacBook M3 16GB พร้อมให้เราก้าวสู่ Day 18: Beyond Raw Text — AST Parsing with Tree-sitter ในวันถัดไปแล้วครับ!