วันนี้ได้พูดคุย และ อธิบายเกี่ยวกับ Test Double ไปนิดหน่อย
จึงนำมาอธิบายเพิ่มเติม พร้อมยกตัวอย่าง
เพื่อทำให้เห็นภาพว่า Test Double แต่ละตัวนั้น
เป็นอย่างไร ใช้งานอย่างไร และ แตกต่างกันอย่างไร
โดยจำอธิบายเฉพาะ Mock, Stub และ Dummy เท่านั้น
เนื่องจากเป็นสิ่งที่ใช้งานบ่อยสุด ๆ แล้ว
มาเริ่มกันเลย
เนื้อหาต่าง ๆ ใน blog นี้อ้างอิงข้อมูลจากบทความเหล่านี้
- Mock Aren’t Stub เขียนโดยคุณ Martin Fowler
- The Little Mocker เขียนโดยคุณ Robert C. Martin
- Mock
- Stub
- Spy
- Dummy
- Fake
1. Mock
เป็นคำที่ถูกใช้ และ เรียกใช้บ่อยมาก ๆ และมักจะเรียกแทนตัวอื่น ๆ ใน Test Double กันไปเลย ดังนั้นมาเข้าใจกับ Mock กันหน่อยMock เป็นการตรวจสอบพฤติกรรมการทำงาน ( Behavior verification ) ของ class ที่เราทำงานด้วยว่า มีการทำงานตามที่เราต้องการ หรือ คาดหวังไว้หรือไม่ตัวอย่างเช่น ใน Service ของเราต้องเรียกใช้ DAO เพื่อทำการบันทึกข้อมูล สิ่งที่เราต้องการ คือ DAO ทำการบันทึกหรือไม่ ? นั่นหมายถึง method save() ของ DAO ถูกเรียกหรือไม่นั่นเอง คำถามคือ แล้วจะจะทดสอบได้อย่างไรล่ะ ? มาดูตัวอย่าง code กัน เริ่มที่ class UserService กันก่อน ซึ่งเราต้องการที่จะทดสอบ [gist id="ec087ea9702345027fbf" file="UserService.java"] คำอธิบาย จะเห็นได้ว่า class UserService นั้นมี class ที่ทำงานด้วย 2 class คือ UserDAO และ User แต่ class ที่เราสนใจพฤติกรรมการทำงานก็คือ UserDAO ซึ่งเราต้องการรู้ว่า พฤติกรรมการทำงานมันถูกต้องหรือไม่ ? เมื่อ method createUser() จาก class UserService ถูกเรียกใช้งานแล้ว method save() ของ class UserDAO ต้องถูกเรียกใช้งานด้วย เราสามารถเขียนการทดสอบได้ดังนี้ [gist id="ec087ea9702345027fbf" file="UserServiceTest.java"] คำอธิบาย ในการทดสอบจะเห็นได้ว่า ในส่วนของ Test Fixtures หรือ ข้อมูลสำหรับการทดสอบนั้น เราจะทำการ mock class UserDAO ( ตัวอย่างนี้ใช้ mock library ชื่อว่า Mockito ) จากนั้นในการทดสอบ จะทำการตรวจสอบพฤติกรรมการทำงานของ class UserDAO ว่า มีการเรียกใช้ method save() หรือไม่ ปล. ถ้าไม่ต้องการใช้ mock library สามารถเขียนเองได้นะครับ วิธีที่ง่ายที่สุดคือ การ extends จาก class UserDAO นั่นเอง
2. Stub
บางคนจะเรียกว่า Stub out หรือเป็นการกำหนดสถานะของสิ่งนั้น ๆ ตามต้องการ ว่าต้องการให้ object ที่ทำงานด้วยนั้นมีสถานะอย่างไร ? จะมีสถานะถูก หรือ ผิด ตามที่ต้องการทดสอบ ดังนั้นจึงเรียกว่า State verification นั่นแสดงว่า เราต้องการตรวจสอบสถานะของ object ว่าถูกต้องตามที่เราคาดหวังหรือไม่ นั่นเอง ตัวอย่างเช่น ใน class UserService นั้น จำเป็นต้องทำการตรวจสอบข้อมูลของผู้ใช้งาน ผ่าน class UserValidationService ถ้าผ่านจะทำการกำหนดสถานะของผู้ใช้งานให้เป็น TRUE แต่ถ้าไม่คือ FALSE มาดูตัวอย่าง code กันดีกว่า [gist id="ec087ea9702345027fbf" file="UserService2.java"] ถ้าเราต้องการทดสอบ โดยให้บันทึกข้อมูลสถานะของผู้ใช้งานเป็น TRUE ล่ะ เราจะต้องทำอย่างไร ? สิ่งที่ทำได้ตอนนี้คือ Stub การทำงานของ class UserValidationService มันไปเลย ดังนั้นถ้าเรียกใช้งานผ่าน method isValid() แล้ว จะต้อง return ค่า TRUE ออกมาเสมอ เขียน code ของการทดสอบได้ดังนี้ [gist id="ec087ea9702345027fbf" file="UserServiceTest2.java"] คำอธิบาย จะเห็นได้ว่าได้ทำการสร้าง Stub ด้วยการ extends มาจาก class จริง ๆ โดย stub นี้จะส่งค่า TRUE กลับมาเสมอเมื่อเรียกใช้งาน method isValid() ทำให้เราสามารถควบคุมสิ่งต่าง ๆ ได้ นั่นส่งผลให้เราสามารถทดสอบการทำงานได้ซ้ำแล้วซ้ำเล่า นี่คือการตรวจสอบสถานะของการทำงานด้วย Stubถ้าถามว่าระหว่าง Mock กับ Stub ใช้อะไรเยอะกว่า ผมตอบได้เลยว่า ใช้ Stub เยอะกว่ามาก
3. Dummy
ชื่อมันตรงตามตัวเลย คือ ให้เราสร้าง class หน้าโง่ ๆ ขึ้นมา มันอาจจะไม่มีการ implement อะไรเลย มีเพียงเพื่อให้ code ของเรา compile ผ่านเท่านั้นเอง ส่ง dummy เข้าไป เพื่อให้มันครบตามที่ต้องการเท่านั้นเอง โดย dummy เหล่านี้ไม่สามารถนำไปใช้งานจริง ๆ ได้นะ !! ตัวอย่างเช่น จากการทดสอบในตัวอย่างของ Stub นั้น จะพบว่ามี class หนึ่งที่ไมีถูกใช้งานอะไรเลย นั่นก็คือ class UserDAO ดังนั้น ถ้าเราต้องการสร้าง Dummy ของ class UserDAO ขึ้นมาล่ะ จะต้องทำอย่างไรดี ? โดยถ้าใครมาเรียกใช้ method ต่าง ๆ ของ class Dummy จะต้องโยน exception ออกมาเสมอ เพราะว่า มันคือ dummy ไงล่ะ เอาไปใช้งานจริง ๆ ไม่ได้หรอกนะ มาดู code กันดีกว่า [gist id="ec087ea9702345027fbf" file="UserServiceTest3.java"] ปล. พวก inner class ต่าง ๆ เหล่านี้ ถ้าต้องการใช้ซ้ำ ๆ จากการทดสอบอื่น ๆ แล้ว แนะนำให้แยกออกไปสร้าง class ใหม่เลยนะมาถึงตรงนี้แล้ว เป็นยังไงกันบ้าง ?
น่าจะพอทำให้เห็นภาพของ Mock, Stub และ Dummy ได้ชัดเจนมากยิ่งขึ้น ส่วนของ Spy กับ Fake ขออธิบายสั้น ๆ ก็แล้วกัน ไม่เช่นนั้นจะงงกันมากไปกว่าเดิม4. Spy
ถูกใช้เมื่อเราต้องการทำให้มั่นใจว่า พฤติกรรมการทำงานของสิ่งที่เราสนใจ มันทำงานได้ถูกต้องจริง ๆ นะ ( Exclusive behavior verification ) เช่น ต้องเรียกมากกว่า 1 ครั้ง เป็นต้น แต่ข้อเสียของ Spy ที่เห็นได้ชัด คือ code ของการทดสอบจะผูกติดกับ code จริง ๆ มากจนเกินไป บางครั้งมันคือการ lock spec ของ code เลย ดังนั้น เมื่อมีการแก้ไข code จะกระทบต่อส่วนการทดสอบอย่างมาก5. Fake
เป็นการ implement ที่เหมือนจริงมาก ๆ บางครั้งเกือบแยกไม่ออกกับของจริงเลยด้วยซ้ำ ทำให้เป็นข้อเสียหนึ่งของ Fake แต่มันเหมาะสมกับการจัดการกับบางสิ่งบางอย่าง ที่ไม่สามารถทดสอบบน production หรือ server จริง ๆ ได้ ตัวอย่างของ Fake ที่ชัดเจนมาก ๆ คือ Memory databaseโดยสรุปแล้ว
Test Double ใช้สำหรับการทำงานร่วมกันของ class ต่าง ๆ ในส่วนของการทดสอบ หรือ unit test นั่นเอง โดย Test Double ที่ใช้มาก ๆ คือ Mock, Stub และ Dummy ดังนั้น developer ทั้งหลายจะต้องศึกษา เรียนรู้ และ ทำความเข้าใจ ว่าแต่ละตัวมันคืออะไร ว่าแต่ละตัวใช้งานอย่างไร ว่าแต่ละตัวแตกต่างกันอย่างไร สุดท้ายจริง ๆ ถ้าต้องการให้การทดสอบมันทำได้ง่าย ๆ แต่ละส่วนการทำงาน หรือ แต่ละ class ต้องไม่ผูกมัดกันแน่น (Loose coupling) นะ ต้องแยกเป็นอิสระต่อกัน (Isolation)เชื่อเถอะว่า ถ้า code มันทดสอบได้ยาก แสดงว่า code ที่เขียนขึ้นมามันก็ยาก แถมผูกมัดกันแบบแน่น ๆ ( ลองหา new ใน code ดูสิ !! ) ซึ่งเป็นที่มาของ spaghetti code นั่นเอง