พี่คริสถามคำถามใน Facebook ว่า “เขียนเทสกันอย่างไร” ผมเลยได้โอกาสอัพเดตคำตอบสำหรับปี 2019 ซักที หลังจากทำงานมาครบ 10 ปีพอดี
การเขียนเทสของผมในปัจจุบันขึ้นอยู่กับ
- โดเมนงานว่าโปรแกรมที่เขียนอยู่เอาไปใช้ทำอะไร tolerate ต่อความผิดพลาดได้แค่ไหน
- เมื่อผิดพลาดแล้วเรา deploy fix ได้ง่ายและเร็วแค่ไหน
- เรามีระบบ monitoring และ alert พร้อมแค่ไหน
หลังๆ มานี้ผมชอบเขียนเทสให้เฉพาะกับ pure function ที่มี logic ซับซ้อน เพราะรู้สึกว่ามัน maintenance cost ต่ำและมีประโยชน์มาก
ผมเขียนเทสให้กับพวกโค้ดที่ทำ component wiring (integration, end-to-end) น้อยลง เพราะมันช้าและ maintenance cost สูง บางทีก็ต้องสร้าง abstraction มาเพื่อเปิดช่องให้เขียนเทสได้โดยเฉพาะ ใช้ monitoring ตรวจจับแล้วแก้ทีหลังเอา (โดเมนงานที่ทำอยู่เอื้อให้ทำได้)
เคยได้คำแนะนำนึงมาว่า ถ้าโค้ดตรงไหนเขียนเทสยาก ก็อาจจะไม่ต้องเขียนแต่ให้พยายามทำให้มันเป็น hot path ให้มันถูกเรียกใช้บ่อยๆ ถ้ามันเกิดพังเราจะได้รู้ได้เร็วๆ ยิ่งถ้า start ระบบไม่ขึ้นเลยยิ่งดี ต้องอย่าเอามันไปซ่อนไว้ใน conditional ลึกๆ เพราะจะทำให้เจอปัญหาช้า ผมคิดว่าเป็นแนวคิดที่น่าสนใจเหมือนกัน
มีตัวอย่างเกี่ยวกับเรื่องการเขียนเทสให้กับ pure function อยู่อันนึง เคยมีคนถามผมว่าถ้าเค้าทำ js form ที่มี input เป็น 100 ฟีลด์และมีเงื่อนไข validation แบบใช้หลายๆ input ร่วมกันตัดสินใจ จะมีวิธีการเขียนเทสอย่างไร ผมให้คำตอบเค้าไปว่า ให้เปลี่ยนมันเป็น pure function ซะ แทนที่เมื่อ validate แล้วจะทำ side-effect ทันที ให้ return ผลลัพธ์การตัดสินใจออกมาแทน แล้วให้ฟังก์ชันด้านนอกเป็นคนทำ side-effect หลังจากนั้นเราก็จะสามารถทำการเขียนเทสให้กับ pure function นั้นได้เต็มที่เลย จะกี่ร้อยเทสเคสก็ว่ากันไป
ทั้งนี้ทั้งนั้นการที่ผมสามารถเขียนเทสน้อยลงได้เยอะ เนื่องมาจากว่าผมมี tool ที่มาแบ่งงานจาก TDD ไป ก็คือ REPL ของภาษา Clojure ที่ผมใช้เขียนอยู่ในปัจจุบันนั่นเอง REPL แบ่งงานเหล่านี้จาก TDD ไปแล้ว
- Fast development feedback loop
- Help stay focus during development
- API design tool
- Run small unit / subset of program
- Increase debuggability of program