testing
วันนี้ได้พูดคุย และ อธิบายเกี่ยวกับ Test Double ไปนิดหน่อย
จึงนำมาอธิบายเพิ่มเติม พร้อมยกตัวอย่าง
เพื่อทำให้เห็นภาพว่า Test Double แต่ละตัวนั้น
เป็นอย่างไร ใช้งานอย่างไร และ แตกต่างกันอย่างไร

โดยจำอธิบายเฉพาะ Mock, Stub และ Dummy เท่านั้น
เนื่องจากเป็นสิ่งที่ใช้งานบ่อยสุด ๆ แล้ว
มาเริ่มกันเลย

เนื้อหาต่าง ๆ ใน blog นี้อ้างอิงข้อมูลจากบทความเหล่านี้

เนื่องจาก class ส่วนใหญ่ มักจะต้องทำงานร่วมกับ class อื่น ๆ เสมอ
ดังนั้น เมื่อเราเขียน unit test เพื่อทดสอบการทำงาน
เราจำเป็นต้องหลีกเลี่ยงการเรียกใช้งาน class จริง ๆ
ของสิ่งที่ต้องทำงานร่วมกัน
เพื่อให้เราสามารถทดสอบการทำงานได้ตามที่ต้องการ
ซึ่งเทคนิคที่ใช้งานกันคือ Test Double ซึ่งประกอบไปด้วย 5 ชนิด คือ

  1. Mock
  2. Stub
  3. Spy
  4. Dummy
  5. Fake

แต่ขออธิบายพร้อมยกตัวอย่างเฉพาะ Mock, Stub และ Dummy เท่านั้น
เนื่องจากใช้งานบ่อยนั่นเอง

1. Mock

เป็นคำที่ถูกใช้ และ เรียกใช้บ่อยมาก ๆ
และมักจะเรียกแทนตัวอื่น ๆ ใน Test Double กันไปเลย
ดังนั้นมาเข้าใจกับ Mock กันหน่อย

Mock เป็นการตรวจสอบพฤติกรรมการทำงาน ( Behavior verification )
ของ class ที่เราทำงานด้วยว่า

มีการทำงานตามที่เราต้องการ หรือ คาดหวังไว้หรือไม่

ตัวอย่างเช่น
ใน Service ของเราต้องเรียกใช้ DAO เพื่อทำการบันทึกข้อมูล
สิ่งที่เราต้องการ คือ DAO ทำการบันทึกหรือไม่ ?
นั่นหมายถึง method save() ของ DAO ถูกเรียกหรือไม่นั่นเอง
คำถามคือ แล้วจะจะทดสอบได้อย่างไรล่ะ ?

มาดูตัวอย่าง code กัน
เริ่มที่ class UserService กันก่อน ซึ่งเราต้องการที่จะทดสอบ

คำอธิบาย
จะเห็นได้ว่า class UserService นั้นมี class ที่ทำงานด้วย 2 class คือ
UserDAO และ User

แต่ class ที่เราสนใจพฤติกรรมการทำงานก็คือ UserDAO
ซึ่งเราต้องการรู้ว่า พฤติกรรมการทำงานมันถูกต้องหรือไม่ ?
เมื่อ method createUser() จาก class UserService ถูกเรียกใช้งานแล้ว
method save() ของ class UserDAO ต้องถูกเรียกใช้งานด้วย

เราสามารถเขียนการทดสอบได้ดังนี้

คำอธิบาย
ในการทดสอบจะเห็นได้ว่า
ในส่วนของ 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 กันดีกว่า

ถ้าเราต้องการทดสอบ
โดยให้บันทึกข้อมูลสถานะของผู้ใช้งานเป็น TRUE ล่ะ
เราจะต้องทำอย่างไร ?
สิ่งที่ทำได้ตอนนี้คือ Stub การทำงานของ class UserValidationService มันไปเลย
ดังนั้นถ้าเรียกใช้งานผ่าน method isValid() แล้ว
จะต้อง return ค่า TRUE ออกมาเสมอ
เขียน code ของการทดสอบได้ดังนี้

คำอธิบาย
จะเห็นได้ว่าได้ทำการสร้าง 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 กันดีกว่า

ปล. พวก 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 นั่นเอง