🛑 1. The Problem First: "แก้จุดนี้ พังจุดโน้น" (The Regression Nightmare)
ลองนึกถึงความรู้สึกตอนที่คุณแก้โค้ดเปลี่ยนชื่อ Field ใน Database แล้วต้องมานั่งเปิด Postman ไล่ยิงเช็ค 20 API เพื่อดูว่ามีอะไรพังไหม:
// ❌ Naive Approach: ไม่เขียน Test เลย!
// "เมื่อวานยังรันได้อยู่เลยนะพี่..." 🌋 พัง! เมื่อระบบใหญ่ขึ้น การเช็คคนเดียวเป็นเรื่องที่เป็นไปไม่ได้
// และความผิดพลาดเพียงจุดเดียวอาจทำให้ระบบล่มทั้งแอป (Global Failure)
ปัญหา: ยิ่งแอปใหญ่ขึ้น ความสัมพันธ์ระหว่างฟังก์ชันยิ่งซับซ้อน การแก้ไขจุดเล็กๆ (เช่น Middleware) อาจไปกระทบระบบสำคัญอย่างระบบจ่ายเงินโดยที่คุณไม่รู้ตัว นี่คือความเสี่ยงมหาศาลที่ Senior Developer จะไม่ยอมให้เกิดขึ้นครับ การเขียน Test คือการทำ "ประกันภัย" ให้กับโค้ดของคุณนั่นเอง
💡 2. Real-Life Analogy: การตรวจสอบความปลอดภัยล่วงหน้า (Pre-flight Checklist)
- Unit Test: เหมือนการเช็ค "หลอดไฟทีละดวง" ว่าติดไหม (เช็คฟังก์ชันเล็กๆ แยกขาดจากตัวอื่น)
- Integration Test: เหมือนการเช็ค "ระบบไฟทั้งบ้าน". คุณลองเปิดสวิตช์หน้าบ้าน แล้วดูว่าไฟในห้องครัวติดไหม (เช็คว่า Route -> Middleware -> Controller -> Database ทำงานร่วมกันได้จริงไหม)
- Supertest: เหมือน "หุ่นยนต์ทดสอบแรงกด". ที่คอยกดปุ่มรัวๆ แทนคุณ เพื่อดูว่าเมื่อไหร่มันจะพัง
🚀 3. Execution Journey: มหากาพย์การยิง Request จำลอง
เราจะใช้ Jest เป็นคนคุมกฎ และ Supertest เป็นคนลงมือยิง
🛠 Step-by-step:
- The Setup: เตรียม Express App แบบไม่เปิดพอร์ต (
app.listen) เพื่อให้ Test รันได้ไว - The Request: ใช้ Supertest ยิง HTTP Method (GET, POST, etc.) ไปยัง Route ที่ต้องการ
- The Expectation: ตรวจสอบ Status Code และโครงสร้าง JSON ที่ได้
- The Cleanup: ล้างข้อมูลใน Database ทิ้งหลังจบการทดสอบทุกครั้งเพื่อให้ได้ค่าที่ "สะอาด" ใหม่เสมอ
// ✅ Best Practice: การทำ API Integration Test (Supertest)
const request = require("supertest");
const app = require("../app");
describe("POST /api/v1/users", () => {
it("ควรจะสร้าง User ใหม่ได้สำเร็จ", async () => {
const res = await request(app)
.post("/api/v1/users")
.send({ email: "test@example.com", name: "Senior" });
expect(res.status).toBe(201); // 🛠 เช็คความถูกต้องระดับ HTTP
expect(res.body.email).toBe("test@example.com"); // 🛠 เช็คความถูกต้องระดับ Data
});
});
🪤 4. The Junior Trap: ความหายนะของ "Database ตัวจริง"
จูเนียร์มักจะเผลอเขียน Test ที่ไปสร้างข้อมูลทับ Database ของเพื่อนร่วมทีม:
// ❌ Junior Trap: ใช้ DB เดียวกันกับ Development รัน Test
// 🌋 พัง! ข้อมูล Test จะไปปนกับข้อมูลจริง ทำให้ตอนรันแอปมึนงง
// และที่ร้ายที่สุดคือ Test ข้อแรกอาจจะเปลี่ยนข้อมูลที่ Test ข้อสองต้องใช้ ทำให้ผลลัพธ์เพี้ยน (Flaky Tests)
ระวัง: การรัน Test ต้องเป็น "Isolated Environment" เสมอ ✅ การแก้ไข: ใช้ Database แยกเฉพาะสำหรับ Test (เช่น Postgres_Test) หรือใช้เทคนิค Database Transaction ที่จะ Rollback (ดึงข้อมูลกลับ) ทุกครั้งที่ Test จบหนึ่งข้อครับ
⚖️ 5. The Why Matrix: ท่าไหนคุ้มค่าที่สุด?
| หัวข้อ | Unit Test (รายตัว) | Integration Test (ทั้งระบบ) |
|---|---|---|
| ความง่าย | เขียนง่ายและเร็ว | เขียนยากกว่าเล็กน้อย (ต้องต่อ DB) |
| ความอุ่นใจ | ต่ำ (ไม่รู้ว่าต่อกันแล้วพังไหม) | ⚡⚡⚡ สูงมาก (รู้ว่าทั้งด่านผ่านแน่นอน) |
| ความเร็วในการรัน | ⚡⚡⚡ เร็วมาก | ช้ากว่า (เพราะมีการคุยกับ DB จริง) |
| ความพึงพอใจต่อ Cost | เหมาะกับ Logic ซับซ้อน | 🚀 คุ้มค่าที่สุด สำหรับโปรเจกต์ API |
🎓 6. Senior Mindset Summary
การเป็น Senior คือการมองว่า "เราไม่ได้เขียน Test เพื่อหาบั๊ก แต่เขียนเพื่อให้เรากล้าแก้ไขโค้ด (Fearless Refactoring)". เมื่อมี Test ที่แข็งแรงหุ้มแอปไว้ คุณจะกล้าปรับปรุงระบบให้ดีขึ้นโดยไม่ต้องกังวลว่าความพ่ายแพ้จะเกิดขึ้นในวันพรุ่งนี้ครับ!