ในการทดสอบแบบ automation นั่น การทำความเข้าใจในเรื่อง SUT นี้ สำคัญมาก — มันเป็นเหมือน "แผนที่" ที่ทำให้ tester รู้ว่าต้องทดสอบอะไร ต้อง mock อะไร และต้องใช้เครื่องมืออะไร
เราไปลงลึกในแต่ละส่วนกัน...
วิธีทำความเข้าใจ SUT ใน 4 ขั้นตอน
ก่อนที่เราจะเขียน SUT Documents ต้องผ่าน 4 คำถามนี้ก่อนเสมอ

ขั้นที่ 1: วิธี Map Architecture ของระบบที่ต้องทดสอบ
ขั้นตอนนี้ คือ ขั้นที่สำคัญที่สุด ถ้า map ผิด ทุกอย่างที่ตามมาจะ scope ผิดหมด
กระบวนการ map ทำผ่าน 5 คำถาม ตามลำดับ:

3 วิธีที่ใช้ map ตามสถานการณ์
1. มี dev/architect ช่วยได้: ทำ Event Storming หรือ Architecture Review session 1–2 ชั่วโมง วาด whiteboard ร่วมกัน แล้ว tester จด component + connection ลงใน template
2. ไม่มีคนช่วย อ่านเอง: อ่าน codebase จาก entry point เช่น router.ts, main.py, App.java แล้วตาม import/dependency ออกไปเรื่อยๆ พร้อมกับอ่าน docker-compose.yml และ .env เพื่อดู infrastructure ที่ระบบต้องการ
3. ระบบรันอยู่แล้ว: ใช้ network tracing เช่น Jaeger, Datadog, หรือแค่ browser DevTools Network tab trace request จริงแล้วดูว่ามันเรียก service อะไรบ้าง
Output ที่ควรได้ ของ Architecture Map
นี่คือ format ที่ tester ควรได้หลังจาก map เสร็จ ก่อนจะไปขั้น 2:
═══════════════════════════════════════════
ARCHITECTURE MAP — [ชื่อระบบ / Feature]
═══════════════════════════════════════════
ENTRY POINT (Q1)
Input: POST /api/orders (HTTP REST, JSON body)
Trigger: ทุกครั้งที่ user กด "สั่งซื้อ" จาก frontend
Output: 201 Created + orderId, event → queue
COMPONENTS (Q2)
Name | Role (1 ประโยค)
──────────────────────────────────────────────────
API Gateway | รับ request, verify JWT, route ไป service
Order Service | validate + สร้าง order, publish event
Inventory Service | ตรวจ stock, reserve item
Notification Svc | subscribe event, ส่ง email/push
COMMUNICATION (Q3)
Gateway → Order Service : REST (sync)
Order → Inventory Service : REST (sync)
Order → RabbitMQ : Publish event (async)
RabbitMQ → Notification : Subscribe event (async)
EXTERNAL DEPENDENCIES (Q4)
PostgreSQL : เก็บ order + inventory data
RabbitMQ : message broker
Redis : cache session + rate limit
Stripe : ชำระเงิน (external API)
SendGrid : ส่ง email (external API)
JWT Provider : verify token (internal auth service)
DATA FLOW — Happy Path (Q5)
Client
→ [POST /api/orders + JWT]
→ API Gateway (verify JWT)
→ Order Service (validate, create order in DB)
→ Inventory Service (check + reserve stock)
→ Stripe (charge payment)
→ DB commit (order = CONFIRMED)
→ Publish event: order.confirmed
→ RabbitMQ
→ Notification Service (subscribe)
→ SendGrid (send confirmation email)
→ Client ← 201 { orderId }Architecture Map Output
ตัวอย่าง ก่อน และ หลัง map
ปัญหาที่เกิดขึ้นบ่อย คือ tester เริ่ม write test case โดยไม่มี map ผล คือ scope กว้างเกินไป หรือ พลาด dependency สำคัญ
ก่อน map: tester เขียน TC ว่า "ทดสอบ create order" แต่ไม่รู้ว่า Inventory Service ถูกเรียกด้วย → integration test ผ่านบน mock แต่ fail บน staging เพราะ stock logic ไม่ถูก test
หลัง map: tester เห็นว่า Order → Inventory เป็น sync REST call → ต้องรวมไว้ใน integration scope → ตัดสินใจ run Inventory Service จริงใน Docker Compose ไม่ใช่ mock
เมื่อ map เสร็จแล้ว ก็พร้อมไปขั้น 2: ขีด boundary ว่าอะไร in-scope อะไร out-of-scope และอะไรควร mock
ขั้นที่ 2: ระบุ boundary แบ่ง In-scope vs Out-of-scope ให้ชัดเจน
สำหรับการระบุ boundary เป็นทักษะที่สำคัญมาก ถ้า scope ผิด จะ over-test หรือ under-test ทั้งคู่มีปัญหา ขอแบ่งเป็น 3 ส่วน
1: แนวคิด boundary คือ อะไรและทำไมถึงสำคัญ
Boundary ของ SUT คือ เส้นที่ขีดรอบสิ่งที่ tester "เป็นเจ้าของ" ทุกอย่างข้างในต้องมี Test Case ครอบคลุม ทุกอย่างข้างนอกต้อง mock/stub หรือ ไม่สนใจ
ปัญหาที่เกิดจาก boundary ไม่ชัด:
- Scope กว้างเกิน → test ช้า, ยากดูแล, fail เพราะ external service ล่ม ไม่ใช่ bug จริง
- Scope แคบเกิน → พลาด bug ที่เกิดตรง integration point จริงๆ
- Boundary คลุมเครือ → dev กับ QA เข้าใจต่างกัน ทำให้ coverage ซ้ำบ้าง หายบ้าง

2: กระบวนการระบุ boundary
มี 4 คำถามตามลำดับ ทำในลำดับนี้เสมอ ไม่ข้าม เพราะแต่ละขั้นใช้ผลจากขั้นก่อน
คำถามที่ 1: "ใครเป็น owner ของแต่ละ component?"
ระบุทีมหรือคนที่รับผิดชอบแต่ละ component ซึ่งโดยทั่วไปแล้ว boundary ของ SUT มักตรงกับ ownership boundary อยู่แล้ว
เช่น
Component | Owner | ใน scope เรา?
────────────────────────────────────────────────────────────────
Order service | Order team | ใช่
User service | Auth team | ขึ้นอยู่กับ sprint
Payment gateway | Vendor (Stripe) | ไม่ใช่
PostgreSQL | Platform team | ไม่ใช่ (แต่ใช้ Docker)
Notification svc | Comms team | ไม่ใช่คำถามที่ 2: "Feature ที่ต้อง test ครั้งนี้มี scope แค่ไหน?"
Boundary ไม่ใช่สิ่งคงที่ตลอด แต่มันจะเปลี่ยนตาม sprint และ feature ที่กำลัง test
เช่น
Sprint A — "สร้าง order":
In scope: Order service
Out: User service (ใช้ mock JWT), Notification (ใช้ stub)
Sprint B — "order + notification integration":
In scope: Order service + Notification service
Out: User service (ยังคง mock), External email (ใช้ WireMock)คำถามที่ 3: "Connection ไหนข้ามทีม/ข้ามระบบ?"
ทุก connection ที่ข้าม ownership boundary = boundary point ที่ต้องตัดสินใจว่า จะ mock หรือ run จริง
เช่น
Connection | ข้าม boundary?| ตัดสินใจ
─────────────────────────────────────────────────────────────────────────
Order → User service | ใช่ (ข้ามทีม) | Mock JWT, stub user data
Order → PostgreSQL | ใช่ (infra) | Docker container
Order → Stripe | ใช่ (vendor) | WireMock / Stripe sandbox
Order → Notification service | ใช่ (ข้ามทีม) | Stub หรือ Docker compose
Order service → Order repository| ไม่ (ภายใน) | รวมใน scope เลยคำถามที่ 4: "Boundary point แต่ละจุดมี contract อะไร?"
เมื่อรู้แล้วว่าตรงไหนเป็น boundary ต้องระบุ input/output contract ที่จุดนั้น เพื่อใช้เป็นพื้นฐาน test และ mock spec
เช่น
Boundary point: Order service → User service
Direction: Order เรียก User
Protocol: REST GET /users/{id}
Input: userId (string, UUID format)
Output: { id, email, role, isActive }
Error case: 404 ถ้า user ไม่มี, 401 ถ้า token หมดอายุ
Mock spec: stub ให้ return { id: "u1", role: "customer", isActive: true }
และ stub 404 สำหรับ TC negative cases3: SUT Boundary Map
tester ต้องระบุให้ชัดว่า ต้องการที่จะทดสอบในส่วนไหน ซึ่งมันจะตรงข้ามกับการทดสอบแบบ manual ที่ tester จะทดสอบ E2E ทั้งระบบ
แต่ในมุมของ automation เราจำเป็นต้อง scope services/functions ที่เราต้องการจะทดสอบ เพื่อลด dependency ที่จะทำให้การทดสอบแบบ automation ยากขึ้น
ตัวอย่าง
e-commerce ที่มี microservices - Tester ต้องเห็นภาพนี้ก่อนเสมอ:

ส่วนนี้เป็นการ map boundary เข้าหากัน ซึ่ง tester จะต้องระบุให้ได้ว่า แต่ละ boundary นั่นจะใช้วิธีการแบบไหน
- in-scope
- mock / stub
- docker / sandbox
- out of scope
ในระบบที่เป็น Microservices นั้น เราจะเจอกับการเรียก services หลายตัว ดังนั้น tester จำเป็นต้องเห็นภาพรวมของระบบ และ scope ให้ได้ว่า ส่วนไหนที่เป็นเราที่จะทดสอบ และ ส่วนไหนที่ไม่ใช่ scope ของเรา เราจะไม่ทดสอบการทำงานของส่วนนั้น เราจะสนใจแค่ input/output เท่านั้น
เราจะตัดสินใจยังไง ว่าอะไรอยู่ใน scope หรือ ไม่อยู่
มี decision framework ให้ใช้ ตอบคำถาม 5 คำถามนี้ ตามลำดับ คำตอบจะออกมาเองโดยไม่ต้องเดา

ขั้นที่ 3: จำแนก dependency ตัดสินใจ
นี่ คือ ทักษะที่สำคัญที่สุดในการออกแบบ test เพราะถ้าตัดสินใจผิดตรงนี้ test ทั้ง suite จะมีปัญหา
นี่คือส่วนที่ tester มักสับสนมากที่สุด เนื่องจากต้องถามตัวเองว่า "dependency นี้ ใน test level นี้ ควรเป็นอะไร?"

หลักการตัดสินใจ
- Unit → mock/stub เสมอ
- Integration → Docker สำหรับ infra, mock สำหรับ external
- E2E → real/sandbox เมื่อเป็น critical path
- ทีมอื่น → contract + mock
Decision Tree: ใช้ 4 คำถามตามลำดับ

stub vs mock ต่างกันยังไง - ต้องรู้ก่อนจะจำแนกได้
คนมักใช้สองคำนี้ปนกัน แต่มีจุดประสงค์ต่างกันชัดเจน
กฎง่ายๆ:
- ถ้า test สนใจแค่ "ได้รับข้อมูลกลับมา" → stub
- ถ้า test สนใจ "เรียก dependency ถูกต้องไหม" → mock
ตัดสินใจ mock vs docker vs sandbox
หากใครอยากได้ decision tree ที่ใช้ตัดสินใจ ซึ่ง tester สามารถเอาไว้ถามตัวเองเวลาเจอ dependency แต่ละตัวได้
เจอ dependency X → ถามคำถามนี้ตามลำดับ
1. X อยู่ใน in-scope หรือเปล่า?
→ ไม่ใช่ = out of scope, ไม่ต้องทำอะไร (ใช้ mock contract เพื่อ isolate)
→ ใช่ = ไปคำถาม 2
2. กำลัง test ที่ level ไหน?
→ Unit = mock/stub เสมอ (ไม่มีข้อยกเว้น)
→ Integration / API = ไปคำถาม 3
→ E2E = ไปคำถาม 4
3. X เป็น internal infrastructure (DB, Queue, Cache)?
→ ใช่ = Docker container (isolated, reproducible)
→ ไม่ใช่ (external API) = WireMock / MSW / mock server
ยกเว้น partner มี sandbox = ใช้ sandbox
4. X เป็น critical external path (payment, auth)?
→ ใช่ = Sandbox ของ vendor (Stripe test mode, Google OAuth test)
→ ไม่ใช่ = Mock/stub ก็พอ ไม่ต้องรัน end-to-end จริงdecision tree
หลักการ 3 ข้อที่ทำให้ scope ชัด
1.mock ที่ boundary: ทุก dependency ที่อยู่นอก boundary ของ SUT ต้องถูก mock เสมอ ไม่ว่าจะเป็น unit หรือ integration test ไม่มีกรณียกเว้น
2.Docker สำหรับ infrastructure จริง: DB, Queue, Cache เป็น infrastructure ที่ควรรันจริงใน integration test ไม่ใช่ mock เพราะ behavior ของมัน (transaction, locking, event ordering) ต่างจาก mock มาก
3.Sandbox สำหรับ external partner ที่ critical: Stripe, PayPal, Google Auth มี test mode/sandbox ให้ใช้ใน E2E ควรใช้แทนการ mock เพราะครอบคลุม error cases ที่ real API จะส่งกลับมา
สัญญาณที่บอกว่าเรา classify ผิด
ถ้า test suite มีอาการเหล่านี้ แปลว่า dependency classification ผิดอยู่
- test ช้าผิดปกติใน unit test → มี dependency จริงหลุดเข้ามา ควรเป็น mock แต่ไม่ได้ mock
- test ผ่านทั้งหมดแต่ staging fail → mock ที่เขียนไม่ตรงกับ real dependency เกิดจากไม่มี contract test
- test flaky ไม่สม่ำเสมอ → async dependency ใน integration test ไม่ได้ใช้ Docker จริง หรือ timeout ไม่พอ
- test ไม่จับ bug ที่ production เจอ → under-mock เกินไปใน integration ควรใช้ Docker แต่ยังคง stub อยู่
ขั้น 4: Assign test levels (Unit / Integration / API / E2E ครอบคลุมตรงไหน)
ขั้นตอนสุดท้ายของ SUT analysis เมื่อเรา map, scope, classify dependency ครบแล้ว ก็ assign level ได้เลย
โดยเราต้องเข้าใจก่อนว่า เราไม่จำเป็นต้องทดสอบ 100% ทุก level เพราะการทำแบบนั้น อาจจะกินเวลานานเกินไป หรือ อาจจะไม่ได้สร้างประโยชน์มากขนาดนั้น

จากตัวอย่าง ตัวเลข 70/20/7/3 ไม่ใช่กฎตายตัว เป็นแนวทางที่ทำให้ suite เร็ว เชื่อถือได้ และดูแลง่าย ในระบบที่ heavy integration อาจเป็น 50/40/7/3 ก็ได้
เกณฑ์การ assign: ถามตัวเองแค่ 3 ข้อ
ก่อน assign level ให้ component ไหน ถามตามลำดับนี้:
1. "component นี้มี logic อะไรที่ทดสอบได้แบบ isolated?"
→ ถ้ามี = ต้องมี Unit test เสมอ (แทบทุก component)
2. "component นี้ interact กับ dependency จริงยังไง?"
→ ถ้า interact กับ DB/Queue/Cache = ต้องมี Integration test
3. "component นี้อยู่ใน user-facing flow ที่ critical ไหม?"
→ ถ้าใช่ = ต้องมี API test ที่ boundary + E2E test ที่ flowตัวอย่าง Level Assignment
TEST LEVEL ASSIGNMENT — Order service Sprint 12
─────────────────────────────────────────────────────────────────
Component │ Unit │ Integration │ API │ E2E │ หมายเหตุ
───────────────────┼──────┼─────────────┼─────┼─────┼──────────
Order domain │ ✓ │ — │ — │ — │ pure logic
OrderService │ ✓ │ ✓ │ — │ — │ unit=logic, int=transaction
OrderRepository │ △ │ ✓ │ — │ — │ △ = optional, int เป็นหลัก
OrderController │ — │ — │ ✓ │ — │ HTTP contract
DiscountCalculator │ ✓ │ — │ — │ — │ pure function
EventPublisher │ ✓ │ ✓ │ — │ — │ unit=spy, int=real queue
Checkout flow │ — │ — │ — │ ✓ │ critical user journey
Payment flow │ — │ — │ — │ ✓ │ critical, Stripe sandbox
─────────────────────────────────────────────────────────────────
รวม TC ประมาณ: ~60 ~15 ~20 ~3สัญญาณที่บอกว่า assign ผิด level
TC อยู่ผิด level มักแสดงออกแบบนี้
- unit test ช้าผิดปกติ (>50ms/test) → มี real I/O แอบอยู่ ควรย้าย dependency ออกหรือ mock แทน
- integration test fail เพราะ validation error → validation logic ควรอยู่ใน unit ไม่ใช่ integration
- E2E test ครอบคลุม edge case validation → เปลืองทรัพยากรมาก ควรย้ายลงมา unit หรือ API test
- ไม่มี unit test เลยสำหรับ service layer → จะ debug ยากมากเมื่อ integration test fail เพราะไม่รู้ว่า logic หรือ infra ผิด
- อยากให้ช่วยสร้าง Level Assignment Table สำหรับ service ที่ทีมกำลังทำอยู่ไหม บอก component และ layer มาได้เลย
SUT Definition Template
หากต้องการ template เอกสารที่ใช้ หน้าตาจะเป็นประมาณนี้
═══════════════════════════════════════════════════════
SUT: [ชื่อระบบ] Version: [sprint/version]
═══════════════════════════════════════════════════════
ARCHITECTURE STYLE
[ ] Monolith [x] Microservices [ ] Serverless
IN-SCOPE COMPONENTS
Component | Type | Owner
──────────────────────────────────────────────
Order Service | Backend | Order Team
User Service | Backend | Auth Team
API Gateway | Infra/Backend | Platform Team
DEPENDENCY STRATEGY
Dependency | Unit | Integration | E2E
─────────────────────────────────────────────────
PostgreSQL | Mock ORM | Docker | Docker (staging)
Redis | Mock | Docker | Docker (staging)
RabbitMQ | Mock | Docker | Docker (staging)
Stripe | Stub | WireMock | Stripe sandbox
SendGrid | Mock SDK | Mock SDK | Mock SDK
S3 | Mock SDK | LocalStack | Real bucket (staging)
OUT OF SCOPE
- Inventory Service (ทีมอื่นดูแล)
- Analytics Pipeline (ไม่เกี่ยวกับ flow ที่ test)
DOCKER COMPOSE SETUP
test-db: postgres:15-alpine
test-cache: redis:7-alpine
test-queue: bitnami/rabbitmq
wiremock: wiremock/wiremock:latestSUT Definition Template