Design with TDD
TDD สามารถใช้เป็นเครื่องมือออกแบบโค้ดให้รับการเปลี่ยนแปลงได้ดีขึ้น โดยบังคับให้เราคิด contract, dependency และ API จากมุมการใช้งานจริงก่อนลงรายละเอียด implementation
มุมมองสำคัญ
เมื่อเริ่มจาก test เราจะถูกบังคับให้เขียนโค้ดที่เรียกใช้งานง่ายและแยกความรับผิดชอบชัดขึ้นโดยธรรมชาติ
TDD เป็น design tool ไม่ใช่แค่ test tool
ถ้ามอง TDD แค่การเพิ่ม test เราอาจพลาดคุณค่าที่สำคัญกว่า คือมันช่วยให้เราออกแบบโค้ดจากพฤติกรรมที่ต้องการจริง ๆ ก่อนเสมอ การเขียน test ก่อนทำให้เราเห็นว่า API ที่กำลังออกแบบใช้งานง่ายหรือยัง และแต่ละส่วนรับผิดชอบเกินขอบเขตไปหรือไม่
แยก dependency ผ่าน interface หรือ port
ระหว่างเขียน test-first เรามักเจอว่าการพึ่ง dependency ตรง ๆ ทำให้ทดสอบยาก จังหวะนี้คือสัญญาณให้แยก contract ออกมาเป็น interface หรือ port เพื่อให้ use case คุยกับ abstraction แทน implementation
- กำหนด contract ของ dependency ก่อนเลือก adapter จริง
- inject dependency ผ่าน constructor หรือ function parameter
- ทดสอบ business logic ได้โดยไม่ต้องพึ่งระบบภายนอก
ออกแบบ API จากการใช้งานก่อน implementation
test ช่วยบอกว่า API ควรหน้าตาอย่างไร ถ้าเวลาเขียน test แล้วต้องเตรียมข้อมูลซับซ้อนมากเกินไปหรือชื่อ function ไม่สื่อ intent ชัด มักแปลว่า API ยังไม่ดีพอ การปรับตั้งแต่ตอนนี้จะทำให้ทั้ง production code และ test อ่านง่ายขึ้นมาก
Code smell ที่ TDD มักทำให้เห็นเร็ว
ระหว่างรอบ Red-Green-Refactor ถ้า test เขียนยากผิดปกติ มักมี smell ซ่อนอยู่ เช่น class ใหญ่เกินไป, function ทำหลายหน้าที่, coupling สูง หรือ side effect กระจายไม่ชัด
Point 1
ต้อง mock เยอะผิดปกติใน test เดียว
Point 2
setup ยาวกว่าการ assert หลายเท่า
Point 3
เปลี่ยน behavior เล็กน้อยแต่ test พังวงกว้าง
แนวปฏิบัติเมื่อเริ่มใช้ในทีม
เริ่มจาก flow เล็กที่กติกาชัดก่อน แล้วใช้ code review ช่วยดูว่าทีมกำลังทดสอบ behavior จริงหรือผูกกับ implementation detail เกินไป จากนั้นค่อยขยายการใช้ TDD ไปยังส่วนที่ซับซ้อนขึ้นพร้อมรักษารอบการทำงานให้สั้น