การทดสอบ: ตาข่ายนิรภัยของคุณ
การทดสอบ (test) คือโค้ดที่ตรวจสอบว่าโค้ดอื่นทำงานตามที่คุณคาดหวังหรือไม่ เมื่อเขียนไว้แล้ว มันจะรันภายในไม่กี่วินาที และบอกคุณทันทีถ้ามีอะไรถดถอย (regress)
ชุดทดสอบที่ดีจะมีลักษณะเหมือนพีระมิด: ฐานเป็นการทดสอบระดับหน่วย (unit test) เล็ก ๆ เร็ว ๆ จำนวนมาก ตรงกลางเป็นการทดสอบเชิงบูรณาการ (integration test) จำนวนน้อยลง และบนยอดเป็นการทดสอบแบบครบวงจร (end-to-end test) ที่ช้าและมีจำนวนน้อยที่สุด ความปลอดภัยส่วนใหญ่ของคุณมาจากฐานที่ราคาถูก:
╱╲
╱ ╲ E2E few · slow · whole app
╱────╲ (end-to-end)
╱ ╲
╱ INTEG ╲ INTEGRATION some · medium
╱──────────╲ (parts together)
╱ ╲
╱ UNIT ╲ UNIT many · fast · one function
╱────────────────╲
cheap base = most of your tests
คุณไม่จำเป็นต้องมี coverage 100% เริ่มจากการทดสอบสิ่งที่จะสร้างความเสียหายมากที่สุดถ้ามันพัง: การล็อกอิน การชำระเงิน ข้อมูลที่สร้างขึ้นใหม่ไม่ได้ และตรรกะธุรกิจหลัก AI เก่งมากในการเขียนเทสต์ — บางทีเก่งกว่าการเขียนฟีเจอร์ด้วยซ้ำ เพราะพฤติกรรมที่คาดหวังนั้นชัดเจนเป็นรูปธรรม
นี่คือพรอมป์ตัวอย่างที่ใช้ได้ผลดี:
Write tests for the calculateDiscount function in src/pricing.js.
Cover: a normal case, the zero/empty case, the maximum-discount
boundary, and one invalid input. Use the existing test setup in
this repo (check how other *.test.js files are structured).
Run the tests and show me they pass before you finish.
บรรทัดสุดท้ายนั้นสำคัญ ให้ AI รัน เทสต์เสมอ ไม่ใช่แค่เขียนมันขึ้นมา เทสต์ที่ไม่เคยถูกรันคือการเดา และก็คุ้มค่าที่จะระบุให้ชัดเจนว่ากรณีไหนที่ต้องครอบคลุม เพราะถ้าปล่อยให้ AI ตัดสินใจเอง มันมักจะเขียนสามแบบของเส้นทางการใช้งานปกติแล้วเรียกว่าครอบคลุมแล้ว กรณีที่จับบั๊กได้จริงคือกรณีน่าเบื่อ ๆ: อินพุตว่างเปล่า ค่าขอบเขต สิ่งที่ควรโยนข้อผิดพลาด (throw)
ตัวอย่างง่าย ๆ ของสิ่งที่มันควรผลิตออกมา:
import { calculateDiscount } from "./pricing.js";
import { describe, it, expect } from "vitest";
describe("calculateDiscount", () => {
it("applies a normal percentage discount", () => {
expect(calculateDiscount(100, 0.2)).toBe(80);
});
it("returns the full price when discount is zero", () => {
expect(calculateDiscount(100, 0)).toBe(100);
});
it("never discounts below zero", () => {
expect(calculateDiscount(100, 1.5)).toBe(0);
});
it("rejects a negative price", () => {
expect(() => calculateDiscount(-10, 0.2)).toThrow();
});
});
เมื่อคุณเปลี่ยนฟีเจอร์ในภายหลัง ให้รันเทสต์ก่อน ถ้ามันขึ้นสีแดง แปลว่าคุณเจอจุดพังก่อนที่ผู้ใช้ของคุณจะเจอ เมื่อ AI แก้บั๊ก ให้ขอให้มันเพิ่มเทสต์ที่จะล้มเหลวกับพฤติกรรมเดิม — เพื่อให้บั๊กนั้นไม่สามารถแอบกลับมาได้อีก นิสัยนี้มีชื่อเรียกว่า regression test (เทสต์กันบั๊กย้อนกลับ) บั๊กทุกตัวที่คุณเจอคือบทเรียนที่โค้ดเบสได้จ่ายเงินซื้อมาแล้ว การมี regression test คือวิธีที่คุณเก็บรักษาบทเรียนนั้นไว้
ข้อควรระวังหนึ่งข้อ: เทสต์เองก็ผิดได้เหมือนกัน ถ้า AI เขียนเทสต์ที่ยืนยันว่าพฤติกรรม ที่มีบั๊ก นั้นถูกต้อง มันจะผ่านตลอดไปในขณะที่แอปของคุณยังพังอยู่ ดังนั้นให้อ่านเงื่อนไขการยืนยัน (assertion) ไม่ใช่แค่เครื่องหมายถูกสีเขียว เทสต์ที่ผ่านหมายความแค่ว่า "โค้ดทำในสิ่งที่เทสต์บอก" — คุณยังต้องยืนยันว่าเทสต์นั้นบอกในสิ่งที่ถูกต้อง