ระหว่างที่ทำ pair programming กับเพื่อน developer อยู่ มี discussion ที่น่าสนใจอยู่เกี่ยวกับการเขียนชุดการทดสอบ ก็คือมี code ที่ต้องการจะ implement คร่าวๆ ประมาณนี้

เนื่องจากมันมี dependencies ติดกับ class นึง คำถามคือ หน้าตาของชุดการทดสอบจะออกมาประมาณไหน จากการพูดคุยกันพบว่าจะได้ 2 รูปแบบ

  • สาย Classicist หลีกเลี่ยงการ mock dependency ใดๆ
  • สาย Mockist ตรงกันข้าม mock dependency เท่าที่จะเป็นไปได้

แล้วทั้ง 2 รูปแบบมันต่างกันอย่างไรล่ะ

Classicist

การเขียนชุดการทดสอบของ object ใดๆ ในส่วนของ dependency ของ object นั้น เราจะต้อง hard code ผลลัพธ์ที่ควรจะเป็นลงไปใน implementation code ของ dependency ก่อน เพื่อให้ชุดการทดสอบของ object ผ่าน จากนั้นตอนเราเขียนชุดการทดสอบของ dependency นั้นๆ เราค่อยแทน hard code ด้วย implementation code จริง ทำการ run ชุดการทดสอบใหม่ ทำวนลงไปตาม layer เรื่อยๆ

Mockist

การเขียนชุดการทดสอบของ object ใดๆ ในส่วนของ dependency ของ object นั้น เราก็จะทำการ mock ผลลัพธ์ที่ควรจะเป็นลงไปในชุดการทดสอบ เพื่อให้ชุดการทดสอบของ object ผ่าน จากนั้นเราเขียนชุดการทดสอบของ dependency นั้นๆ ทำลงไปตาม layer เรื่อยๆ

จะ Classicist หรือ Mockist ดีล่ะ

จากข้อดี-ข้อเสียของทั้ง 2 รูปแบบ เราควรพิจารณาการใช้งานตามนี้

การ Setup

classicist จะต้องเตรียมระบบที่ต้องการทดสอบ และ dependency เหล่านั้น และ data ที่ใช้สำหรับการทดสอบเอง ปัญหาจะเบาบางลงไปบ้างถ้าเรา reuse การ setup เหล่านั้น แต่ก็มีข้อจำกัดเรื่องการอ่านทำความเข้าใจ code ที่ต้องกระโดดไปมาระหว่าง test suite และ test data บ่อยกว่า

mockist จะใช้เครื่องมือช่วยเตรียมระบบที่ต้องการทดสอบ และ dependency เหล่านั้นให้ แต่ในส่วนของ data ที่ใช้สำหรับการทดสอบก็ยังต้องเตรียมเองอยู่ มีข้อจำกัดที่ทุกๆ ชุดการทดสอบจะต้องมี statement ในการเตรียม mock เสมอ

ความเป็นอิสระต่อกัน

สำหรับ classicist การแก้ไข implementation code ไม่ว่าจะ object ไหนก็ตามย่อมมีผลทำให้ชุดการทดสอบของเราพังหมด ในอีกแง่มันก็เป็นข้อดีเพราะจะทำให้เรารู้ว่าการแก้ไขมันมีผลกระทบต่อ object ไหนอื่นๆ บ้าง

กลับกันในส่วนของ mockist การแก้ไข object อื่นๆ ก็ไม่มีผลต่อ object ที่เราทดสอบอยู่ แต่เวลามีการแก้ไขแล้วก็มีโอกาสที่เราจะหาข้อผิดพลาดได้ยากและนานกว่า นอกจากนั้นชุดการทดสอบก็มีโอกาสที่จะพังถ้ามีการ refactor เพราะ mockist จะสนใจด้วยว่ามีการเรียกใช้ dependency ด้วย method และ argument ที่ถูกต้อง แต่ classicist สนใจแต่ state สุดท้าย

จะสังเกตได้ว่าการเลือกใช้ classicist หรือ mockist มันขึ้นอยู่กับบริบทของ object ที่ต้องการจะทดสอบล้วนๆ ส่วนตัวแล้วเราจะเลือกไปทาง classicist ให้ได้มากที่สุด จนกว่าเราจะพบว่า object ของเรามี dependency ที่ยากต่อการ setup เช่น การเชื่อมต่อกับ database ถึงจะเลือกใช้เป็น mockist

Further reading

Mocks Aren’t Stubs by Martin Fowler