เรียนรู้พรอมต์สำหรับสร้างเทสต์ด้วย Claude Code ที่ผลิตชุดทดสอบสัญญาณสูงโดยมุ่งที่ขอบเขต อินแวเรียนต์ และโหมดความล้มเหลว แทนการทดสอบแค่เส้นทางปกติ

reserveInventory(itemId, qty):\n\nสัญญาอาจระบุว่า qty ต้องเป็นจำนวนเต็มบวก ฟังก์ชันต้องทำงานเป็นอะตอม และจะไม่สร้างสต็อกติดลบ นั่นจะนำไปสู่เทสต์สัญญาณสูงทันที: qty = 0, qty = 1, qty มากกว่าคงเหลือ, การเรียกพร้อมกัน, และการบังคับให้ฐานข้อมูลเกิดข้อผิดพลาดกลางทาง\n\nถ้าคุณใช้เครื่องมือแบบ vibe-coding เช่น Koder.ai กระบวนการเดียวกันนี้นำไปใช้ได้: เขียนสัญญาในแชทก่อน แล้วสร้างเทสต์ที่โจมตีขอบเขต โหมดความล้มเหลว และรายการ “ห้ามเกิด” โดยตรง\n\n## รูปแบบพรอมต์: แผนแม่แบบเทสต์ที่มีสัญญาณสูง\n\nใช้พรอมต์สร้างเทสต์ Claude Code นี้เมื่อคุณต้องการเทสต์น้อยลง แต่แต่ละชิ้นต้องมีน้ำหนัก การเคลื่อนไหวสำคัญคือบังคับให้มีแผนการทดสอบก่อน แล้วจึงสร้างโค้ดทดสอบหลังจากคุณอนุมัติแผน\n\ntext\nYou are helping me write HIGH-SIGNAL unit tests.\n\nContext\n- Language/framework: <fill in>\n- Function/module under test: <name + short description>\n- Inputs: <types, ranges, constraints>\n- Outputs: <types + meaning>\n- Side effects/external calls: <db, network, clock, randomness>\n\nContract (keep it small)\n1) Preconditions: <what must be true>\n2) Postconditions: <what must be true after>\n3) Error behavior: <how failures are surfaced>\n\nTask\nPHASE 1 (plan only, no code):\nA) Propose 6-10 tests max. Do not include “happy path” unless it protects an invariant.\nB) For each test, state: intent, setup, input, expected result, and WHY it is high-signal.\nC) Invariants: list 3-5 invariants and how each will be asserted.\nD) Boundary matrix: propose a small matrix of boundary values (min/max/empty/null/off-by-one/too-long/invalid enum).\nE) Failure modes: list negative tests that prove safe behavior (no crash, no partial write, clear error).\nStop after PHASE 1 and ask for approval.\n\nPHASE 2 (after approval):\nGenerate the actual test code with clear names and minimal mocks.\n\n\nทริคปฏิบัติคือขอให้ boundary matrix เป็นตารางกะทัดรัด เพื่อให้เห็นช่องว่างชัดเจน:\n\n| Dimension | Valid edge | Just outside | “Weird” value | Expected behavior |\n|---|---|---|---|---|\n| length | 0 | -1 | 10,000 | error vs clamp vs accept |\n\nถ้า Claude เสนอ 20 เทสต์ ให้บีบให้เหลือ ถามให้รวมเคสที่คล้ายกันและเก็บเฉพาะที่จับบั๊กจริงได้ (off-by-one, ประเภทข้อผิดพลาดผิด, สูญหายข้อมูลเงียบ, invariant แตก)\n\n## ขั้นตอนทีละขั้น: รันพรอมต์แล้วแปลงผลเป็นเทสต์\n\nเริ่มด้วยสัญญาเล็กๆ ที่เป็นรูปธรรมสำหรับพฤติกรรมที่ต้องการ วาง signature ของฟังก์ชัน คำอธิบายสั้นๆ ของอินพุตและเอาต์พุต และเทสต์ที่มีอยู่ (แม้จะเป็นเพียง happy-path) วิธีนี้ช่วยให้โมเดลยึดกับสิ่งที่โค้ดทำจริง ไม่ใช่สิ่งที่มันเดา\n\nถัดมา ให้ขอตารางความเสี่ยงก่อนขอโค้ดทดสอบ ใส่สามคอลัมน์: ขอบเขต (edges of valid input), โหมดความล้มเหลว (bad input, missing data, timeouts), และ invariants (กฎที่ต้องเป็นจริง) เติมหนึ่งประโยคต่อแถวว่า “ทำไมสิ่งนี้ถึงพังได้” ตารางเรียบง่ายเผยช่องว่างได้เร็วกว่ากองไฟล์เทสต์\n\nจากนั้นเลือกชุดเทสต์ที่เล็กที่สุดที่แต่ละอันมีจุดประสงค์จับบั๊กแตกต่างกัน ถ้าสองเทสต์ล้มเพราะสาเหตุเดียวกัน ให้เก็บอันที่แข็งแรงกว่า\n\nกฎการเลือกปฏิบัติ:\n\n- เก็บเทสต์ที่โดนขอบเขตต่างกัน (min, max, empty, off-by-one)\n- เก็บเทสต์ที่พิสูจน์การทำงานอย่างปลอดภัยเมื่อเกิดความล้มเหลว (error ชัดเจน, ไม่มีการเขียนบางส่วน, ไม่มีแครช)\n- เก็บเทสต์ที่ยืนยัน invariant (ลำดับ, ยอดรวม, idempotency, ไม่มีการซ้ำ)\n- ตัดเทสต์ที่แค่ซ้ำว่า “ทำงานกับอินพุตปกติ”\n\nสุดท้าย ให้คำอธิบายสั้นๆ ต่อเทสต์ว่า: ถ้าเทสต์นี้ล้ม มันจะจับบั๊กรูปแบบไหน ถ้าประโยคนั้นคลุมเครือ ("ยืนยันพฤติกรรม") เทสต์นั้นมักเป็น low-signal\n\n## วิธีเข้ารหัส invariant ลงใน assertion\n\nInvariant คือกฎที่ต้องเป็นจริงไม่ว่าอินพุตที่ถูกต้องใดจะถูกส่งเข้าไป สำหรับการทดสอบเชิง invariant ให้เขียนกฎเป็นภาษาธรรมดา แล้วแปลงเป็น assertion ที่ล้มดังและชัดเจนได้\n\nเลือก 1–2 invariants ที่จริงๆ แล้วปกป้องคุณจากบั๊กจริง Invariant ที่ดีมักเกี่ยวกับความปลอดภัย (ไม่สูญหายข้อมูล), ความสอดคล้อง (ผลเดียวกันกับอินพุตเดียวกัน), หรือขีดจำกัด (ไม่เกินเพดาน)\n\n### แปลง invariant ให้เป็นเช็คที่พิสูจน์ได้\n\nเขียน invariant เป็นประโยคสั้นๆ แล้วตัดสินใจว่าหลักฐานที่จะสังเกตได้คืออะไร: ค่าที่คืน ข้อมูลที่เก็บ เหตุการณ์ที่ปล่อย หรือการเรียกไปยัง dependency การอ้างสิทธิ์ที่แข็งแรงเช็คทั้งผลลัพธ์และ side effects เพราะบั๊กหลายตัวซ่อนอยู่ที่ "คืน OK แต่เขียนผิด"\n\nตัวอย่าง เช่น ฟังก์ชันที่ใช้คูปองกับคำสั่งซื้อ:\n\n- Invariant: ยอดท้ายสุดไม่เป็นลบ\n- Invariant: การใช้คูปองเดียวกันสองครั้งไม่ลดสองครั้ง\n\nตอนนี้เข้ารหัสพวกนั้นเป็น assertion ที่วัดได้:\n\nts\nexpect(result.total).toBeGreaterThanOrEqual(0)\nexpect(db.getOrder(orderId).discountCents).toBe(originalDiscountCents)\n\n\nหลีกเลี่ยง assert คลุมเครืออย่าง “คืนค่าตามที่คาด” ให้ assert กฎเฉพาะ (ไม่ติดลบ) และ side effect เฉพาะ (ส่วนลดเก็บครั้งเดียว)\n\n### เพิ่มบันทึกตัวอย่างที่ทำให้เกิดความผิดพลาดเพื่อให้เทสต์ยังคมอยู่\n\nสำหรับแต่ละ invariant ให้เพิ่มบันทึกสั้นๆ ในเทสต์ว่า ข้อมูลแบบไหนที่จะผิดเงื่อนไขนี้ วิธีนี้ช่วยให้เทสต์ไม่กลายเป็นการตรวจ happy-path ในภายหลัง\n\nรูปแบบง่ายๆ ที่ทนทานเมื่อเวลาผ่านไป:\n\n- ใส่ invariant ในชื่อเทสต์\n- ตรวจ invariant บนผลลัพธ์\n- ตรวจ side effect สำคัญ (หรือการไม่มี side effect)\n- เพิ่มคอมเมนต์หนึ่งบรรทัดเกี่ยวกับกรณีที่จะละเมิด (เช่น คูปองมูลค่าสูงมากหรือการใช้ซ้ำ)\n\n## โหมดความล้มเหลว: เขียนเทสต์ที่พิสูจน์การล้มอย่างปลอดภัย\n\nเทสต์สัญญาณสูงมักเป็นเทสต์ที่ยืนยันว่าโค้ดล้มอย่างปลอดภัย หากโมเดลเขียนแค่เทสต์แบบ happy-path คุณจะไม่รู้อะไรเลยเกี่ยวกับพฤติกรรมเมื่้ออินพุตหรือ dependency สกปรก\n\nเริ่มด้วยการตัดสินใจว่า “ปลอดภัย” หมายความว่าอย่างไรสำหรับฟีเจอร์นี้ คืนข้อผิดพลาดเป็นชนิดที่พิมพ์ได้หรือ fallback ไปค่าเริ่มต้น หรือ retry หนึ่งครั้งแล้วหยุด? เขียนพฤติกรรมที่คาดไว้ในหนึ่งประโยคแล้วให้เทสต์พิสูจน์มัน\n\nเมื่อขอ Claude Code สำหรับเทสต์โหมดความล้มเหลว ให้เข้มงวด: ครอบคลุมวิธีที่ระบบจะพัง และ assert ตอบสนองที่คุณต้องการ บรรทัดที่มีประโยชน์คือ: “ชอบเทสต์น้อยชิ้นที่มี assertion แข็งแรง มากกว่าเทสต์หลายอันที่ตื้น”\n\nหมวดความล้มเหลวที่มักให้เทสต์ดีที่สุด:\n\n- อินพุตไม่ถูกต้อง: ฟอร์แมตไม่ถูกต้อง ฟิลด์จำเป็นหายไป ค่านั้นอยู่นอกช่วง\n- ความล้มเหลวของ dependency: timeout, 500, ตอบกลับว่าง, payload เสียหาย\n- ปัญหาลำดับ: เหตุการณ์มาถึงไม่เรียง ซ้ำ การเขียนบางส่วน\n- ความขนาน: อัปเดตแข่งกัน, การตรวจ idempotent\n- พฤติกรรมกู้คืน: คืน error หรือ fallback หรือ retry\n\nตัวอย่าง: มี endpoint สร้างผู้ใช้และเรียกบริการอีเมลส่งข้อความต้อนรับ เทสต์แบบ low-value ตรวจแค่ว่า “คืน 201.” แต่เทสต์โหมดความล้มเหลวที่มีสัญญาณจะตรวจว่า หากบริการอีเมล timeout คุณจะ (a) ยังคงสร้างผู้ใช้และคืน 201 พร้อม flag “email_pending” หรือ (b) คืน 503 ชัดเจนและไม่สร้างผู้ใช้ เลือกพฤติกรรมอย่างใดอย่างหนึ่ง แล้ว assert ทั้ง response และ side effects\n\nยังต้องทดสอบว่าคุณไม่รั่วไหลอะไร ถ้าการตรวจสอบไม่ผ่าน ให้แน่ใจว่าไม่มีอะไรถูกเขียนลง DB ถ้า dependency คืน payload เสียหาย ให้แน่ใจว่าไม่โยน exception ไม่ระบาย stack trace ดิบๆ\n\n## กับดักทั่วไปที่สร้างเทสต์ไร้ค่า\n\nชุดเทสต์ที่มีค่าน้อยมักเกิดเมื่อโมเดลถูกรางวัลด้วยปริมาณ ถ้าพรอมต์ Claude Code ของคุณขอ “20 unit tests” มักได้การเปลี่ยนแปลงเล็กๆ น้อยๆ ที่ดูเหมือนครอบคลุมแต่จับอะไรไม่ได้\n\nกับดักทั่วไป:\n\n- เทสต์หน้าตาเหมือนกัน: เทสต์อินพุตที่ถูกต้องซ้ำๆ กับสตริงหรือตัวเลขต่างกัน\n- เทสต์ที่สะท้อนโค้ด: อ้างถึงขั้นตอนภายในหรือ helper แทนพฤติกรรมที่สังเกตได้\n- ม็อกทุกอย่าง: แทน DB, นาฬิกา, เครือข่าย, config ทั้งหมด\n- Assertion อ่อน: แค่เช็ค “ไม่มีข้อผิดพลาด”, “ไม่เป็น null”, หรือ “สถานะ 200”\n- สถานะที่สกปรกร่วมกัน: ทิ้งข้อมูล seeded, global เปลี่ยนแปลง, หรือ cache\n\nตัวอย่าง: ฟังก์ชัน “create user” สิบเทสต์แบบ happy-path อาจเปลี่ยนอีเมลเป็นหลายรูปแบบแต่พลาดสิ่งสำคัญ: ปฏิเสธอีเมลซ้ำ, รหัสผ่านว่าง, และยืนยันว่า ID ที่คืนไม่มีซ้ำและคงที่\n\nเส้นทางช่วยในการตรวจสอบ:\n\n- ขอบเขต: ให้แต่ละเทสต์ชื่อความเสี่ยงที่มันครอบคลุม (boundary, failure mode, หรือ invariant)\n- หลีกเลี่ยงการตรวจเฉพาะการทำงานภายใน ยกเว้นมันเปลี่ยนพฤติกรรมที่สังเกตได้\n- ม็อกให้น้อยที่สุด และอนุญาตให้มีเทสต์บางชิ้นที่ทดสอบ integration จริงเมื่อเป็นไปได้\n- บังคับ assertion แข็งแรง: ผลลัพธ์ที่แน่นอน, การเปลี่ยนสถานะ, และประเภท/ข้อความของข้อผิดพลาด\n- เพิ่มกฎ cleanup เพื่อไม่ให้เทสต์พึ่งพาลำดับการรัน\n\n## ตัวอย่าง: แปลงฟีเจอร์หนึ่งเป็นชุดเทสต์เล็กแต่แข็งแรง\n\nสมมติฟีเจอร์หนึ่ง: ใช้คูปองที่เช็คเอาต์\n\nสัญญา (เล็กและทดสอบได้): ให้ subtotal ของตะกร้าเป็นเซ็นต์และคูปองเป็นออปชัน คืนยอดสุดท้ายเป็นเซ็นต์ กฎ: คูปองเปอร์เซ็นต์ปัดเศษลงเป็นเซ็นต์ที่ใกล้เคียงที่สุด คูปองคงที่หักเป็นจำนวนคงที่ และยอดไม่ควรติดลบ คูปองอาจไม่ถูกต้อง หมดอายุ หรือถูกใช้แล้ว\n\nอย่าขอแค่ว่า “เทสต์ applyCoupon()” แต่ขอการทดสอบกรณีขอบเขต โหมดความล้มเหลว และ invariants ผูกกับสัญญานี้\n\n### ขอบเขตที่บังคับพฤติกรรมขอบ\n\nเลือกอินพุตที่มักทำให้การคำนวณหรือการตรวจสอบพัง: รหัสคูปองว่าง, subtotal = 0, subtotal ใกล้ต่ำสุดและสูงสุด, ส่วนลดคงที่มากกว่ายอดสั่งซื้อ, และเปอร์เซ็นต์เช่น 33% ที่ทำให้เกิดการปัดเศษ\n\n### โหมดความล้มเหลวเพื่อพิสูจน์การล้มอย่างปลอดภัย\n\nสมมติว่าการค้นหาคูปองอาจพังและสถานะอาจผิด: บริการคูปองล่ม คูปองหมดอายุ หรือคูปองถูกใช้แล้วโดยผู้ใช้คนนี้ เทสต์ควรพิสูจน์ผลลัพธ์ถัดไป (ปฏิเสธคูปองด้วยข้อผิดพลาดชัดเจน ยอดไม่เปลี่ยน)\n\nชุดเทสต์สัญญาณสูงขั้นต่ำ (5 เทสต์) และสิ่งที่แต่ละอันจับได้:\n\n- ปฏิเสธโค้ดว่างหรือช่องว่าง: จับบั๊กที่รับช่องว่างเป็นค่าถูกต้องและการ trim ผิดพลาด\n- ปัดเศษคูปองเปอร์เซ็นต์ (subtotal 101, 33%): จับข้อผิดพลาดการปัดเศษและ off-by-one เซนต์\n- ส่วนลดคงที่มากกว่ายอด (subtotal 500, discount 1000): พิสูจน์ invariant ว่ายอดไม่ควรติดลบ\n- ขอบขั้นต่ำในการซื้อ (subtotal 999 vs 1000): จับตรรกะการเปรียบเทียบผิด (< กับ <=)\n- การค้นหาคูปองล้มหรือ timeout: พิสูจน์ fallback ที่ปลอดภัย (ไม่ใช้ส่วนลด) และการจัดการข้อผิดพลาดที่เสถียร\n\nถ้าเทสต์พวกนี้ผ่าน คุณได้ครอบคลุมจุดแตกหักทั่วไปโดยไม่เติมชุดด้วยเทสต์ happy-path ซ้ำๆ\n\n## เช็คลิสต์ด่วนสำหรับเทสต์ที่ AI สร้างแล้วมีสัญญาณสูง\n\nก่อนยอมรับผลที่โมเดลสร้าง ให้ตรวจสอบอย่างรวดเร็ว เป้าหมายคือเทสต์แต่ละอันต้องปกป้องคุณจากบั๊กที่เฉพาะและน่าจะเกิดจริง\n\nใช้เช็คลิสต์นี้เป็นเกต:\n\n- ขอบเขตต่ออินพุต: สำหรับแต่ละฟิลด์อินพุต (สตริง, ID, timestamp, flag) ให้รวมอย่างน้อยหนึ่งกรณีขอบ (ว่าง vs ช่องว่าง, ความยาวสูงสุด, ศูนย์ vs ลบ, ฟิลด์ออปชันหายไป, หนึ่งเกินขีดจำกัด)\n- ความล้มเหลวของ dependency: รวมอย่างน้อยหนึ่งเทสต์ที่ dependency ทำงานผิดพลาด (timeout ฐานข้อมูล, API ภายนอก 500, โทเค็นหมดอายุ) พิสูจน์พฤติกรรมที่ปลอดภัย (error ชัดเจน, ไม่มีการเขียนบางส่วน)\n- Invariants พร้อม assertion แข็งแรง: เลือก 1–3 กฎที่ต้องเป็นจริงและ assert โดยตรง หลีกเลี่ยง assert คลุมเครืออย่าง “response ok”\n- หนึ่งบั๊กเฉพาะต่อเทสต์: อ่านชื่อเทสต์แต่ละอันแล้วถามว่า “เทสต์นี้จะจับบั๊กอะไร?” ถ้าสองเทสต์ตอบคำถามเดียวกัน ให้รวมเข้าด้วยกัน\n- เทสต์ลบ: ลองลบเทสต์ ถ้าไม่มีอะไรสำคัญหายไป (ไม่มี boundary, ไม่มี failure mode, ไม่มี invariant) เทสต์นั้นไม่ได้สมควรอยู่\n\nทริคปฏิบัติหลังการสร้าง: เปลี่ยนชื่อเทสต์เป็น “should <พฤติกรรม> when <เงื่อนไขขอบ>” และ “should not <ผลร้าย> when <ความล้มเหลว>” ถ้าชื่อไม่เปลี่ยนง่าย เทสต์นั้นมักไม่เฉพาะเจาะจงพอ\n\nถ้าคุณสร้างแอปผ่านแชท ให้รันวงจรนี้ใน Koder.ai (koder.ai) เพื่อให้สัญญา แผน และเทสต์ที่สร้างอยู่ในที่เดียว เมื่อรีแฟกเตอร์เปลี่ยนพฤติกรรมโดยไม่คาดคิด สแนปช็อตและการย้อนคืนจะช่วยให้เปรียบเทียบและปรับจนชุดสัญญาณสูงคงที่\n\nค่าเริ่มต้น: ตั้งเป้าเป็นชุดเล็กที่สามารถจับบั๊กจริงได้\n\nเกณฑ์ที่ใช้งานได้ดีคือ 6–10 เทสต์ต่อหน่วย (ฟังก์ชัน/โมดูล). ถ้าต้องการมากกว่านี้ มักหมายความว่าหน่วยนั้นทำงานมากเกินไปหรือสัญญา (contract) ยังไม่ชัดเจน
เทสต์แบบ happy-path ส่วนใหญ่แค่ยืนยันว่า ตัวอย่างยังทำงานได้ พวกมันมักพลาดสิ่งที่จะพังในสภาพแวดล้อมจริง\n\nเทสต์ที่มีสัญญาณสูงจะมุ่งเป้าไปที่:\n\n- ขอบเขต (Boundaries) (0/1/max, ว่าง/ null, off-by-one)\n- โหมดความล้มเหลว (Failure modes) (timeout, input ไม่ถูกต้อง, dependency error)\n- Invariants (กฎที่ต้องเป็นจริงเสมอ เช่น “ไม่เขียนข้อมูลบางส่วนเมื่อเกิดข้อผิดพลาด”)
เริ่มจาก สัญญาเล็กๆ ที่อ่านได้ในครั้งเดียว:\n\n- Inputs: ชนิดข้อมูล ช่วงที่อนุญาต และอะไรถือว่าเป็นค่าว่าง/ขาดหาย\n- Outputs: รูปร่างของผลสำเร็จและข้อผิดพลาด\n- Side effects: สิ่งที่จะถูกเขียน/เปลี่ยน (DB, ไฟล์, เครือข่าย)\n- “ห้ามเกิด”: การแครช สูญหายข้อมูลโดยเงียบ เก็บเงินซ้ำ การเขียนแบบไม่สมบูรณ์\n\nแล้วจึงสร้างเทสต์จากสัญญานี้ ไม่ใช่จากตัวอย่างอย่างเดียว
ทดสอบกรณีเหล่านี้ก่อน:\n\n- ค่าสูงสุด/ต่ำสุด (0, 1, max, มากกว่า max)\n- ว่าง vs มีค่า ("", [], null)\n- off-by-one (n-1, n, n+1)\n- ขอบด้านฟอร์แมต (สตริงมีแค่ช่องว่าง, เลขนำ, ฯลฯ)\n- ขอบเวลา (ก่อน/หลังวันหมดอายุ)\n\nเลือก 1–2 กรณีต่อมิติของอินพุต เพื่อให้แต่ละเทสต์ครอบคลุมความเสี่ยงที่ต่างกัน
เทสต์โหมดความล้มเหลวที่ดีต้องพิสูจน์สองอย่าง:\n\n1) ฟังก์ชันคืน ข้อผิดพลาดที่ชัดเจนและคาดไว้ (ชนิด/ข้อความ/สถานะ)\n2) มันล้มเหลว อย่างปลอดภัย:\n\n- ไม่มีการเปลี่ยนสถานะบางส่วน\n- ไม่มีการเปิดเผยรายละเอียดภายใน\n- ไม่มี retry หรือ side effect ที่ไม่ตั้งใจ\n\nถ้ามีการเขียนฐานข้อมูล ให้เสมอตรวจก่อนและหลังเหตุการณ์ผิดพลาดว่ามีอะไรเกิดขึ้นใน storage
แนวทางปกติ: เปลี่ยน invariant เป็นการอ้างสิทธิ์บนผลสังเกตได้ (observable outcome).\n\nตัวอย่าง:\n\n- “ยอดรวมท้ายที่สุดต้องไม่ติดลบ” → expect(total).toBeGreaterThanOrEqual(0)\n- “เมื่อเกิดข้อผิดพลาด ห้ามเปลี่ยนสถานะ” → ตรวจสอบ ไม่มีแถวใหม่ / ไม่มี flag ถูกพลิก\n- “Idempotent” → เรียกสองครั้งแล้วยืนยันว่าการเรียกครั้งที่สองไม่เปลี่ยนสถานะ\n\nควรตรวจทั้ง ค่าที่ส่งกลับ และ side effects เพราะบั๊กหลายตัวซ่อนอยู่ที่ “คืนค่า OK แต่เขียนผิด”
คุ้มค่าที่จะเก็บเทสต์แบบ happy-path เมื่อมันปกป้อง invariant หรือการรวมที่สำคัญ\n\nเหตุผลที่ดีในการเก็บ:\n\n- ยืนยัน invariant สำคัญสำหรับอินพุตปกติ (เช่น กฎการปัดเศษ)\n- ล็อกสัญญาของ API ที่ผู้เรียกพึ่งพา\n- ป้องกันการ regressions จากเหตุการณ์ในอดีต\n\nถ้าไม่ใช่ ให้แลกเป็นเทสต์ขอบเขต/ความล้มเหลวที่จับบั๊กได้มากกว่า
บังคับเอา PHASE 1: แผนเท่านั้น มาก่อน\n\nให้โมเดลส่ง:\n\n- ข้อเสนอ 6–10 เทสต์สูงสุด\n- สำหรับแต่ละอัน: เจตนา, การเตรียม, อินพุต, ผลที่คาด, ทำไมถึงมีสัญญาณสูง\n- ตาราง boundary ขนาดเล็ก\n- รายการ failure-mode\n- 3–5 invariants และวิธีตรวจสอบ\n\nก็ต่อเมื่อคุณอนุมัติแผนแล้วจึงสั่งให้สร้างโค้ด (PHASE 2). วิธีนี้ป้องกันการได้ผลเป็น 20 เทสต์ลอกกันไปมา
แนวทางเริ่มต้น: mock เฉพาะขอบเขตที่คุณไม่ควบคุม (DB/เครือข่าย/นาฬิกา) และเก็บที่เหลือให้เป็นของจริง\n\nเพื่อลดการม็อกเกินไป:\n\n- อย่า mock helper ภายในเพียงเพราะต้องการสะท้อน implementation\n- ใช้ in-memory หรือ fake เล็กๆ ที่มีพฤติกรรมชัดเจนเมื่อทำได้\n- mock นาฬิกา/randomness เมื่อมันส่งผลต่อ assertion เท่านั้น\n\nถ้าเทสต์พังเพราะรีแฟกเตอร์แต่พฤติกรรมไม่เปลี่ยน มักแปลว่า mock เยอะหรือผูกกับ implementation มากเกินไป
ใช้การลบทดสอบแบบง่าย:\n\n- ถ้าคุณลบเทสต์แล้ว ไม่เสียขอบเขต, ไม่เสีย failure mode, และ ไม่เสีย invariant เทสต์นั้นไม่ได้สมควรอยู่\n\nสแกนหาเทสต์ซ้ำ:\n\n- ถ้าสองเทสต์จะแพ้เพราะบั๊กเดียวกัน ให้เก็บอันที่มี assertion แข็งแรงกว่า\n- ถ้า assertion แค่ “not null” หรือ “status 200” ให้เสริมความเข้มหรือถอดทิ้ง