มาทำความรู้จัก Dependency Injection และ Mocking กัน

Dependency Injection (DI) และ Mocking สองคำนี้หลายๆ ท่านอาจจะเคยได้ยินผ่านหูกันมาบ้างแล้ว แต่ก็อาจสงสัยว่ามันคืออะไรกันแน่ แล้วทำไมเมื่อมี Dependency Injection แล้วก็ต้องมีคำว่า Mocking ตามมา สองคำนี้มีดีอย่างไร? ลองมาดูกันครับ 😉

Dependency

คำว่า Dependency นั้นตามความหมายก็คือ “การพึ่งพา” ในทางโปรแกรมมิ่งนั้นก็คือการที่ Class หนึ่งต้องอาศัย Class อื่นในการทำงาน

// PizzaShop (v.1)

จากโค้ดด้านบนจะเห็นว่าร้านพิซซ่า ( PizzaShop ) เมื่อได้รับ Order มาก็ต้องพึ่งพา (Dependent) ตัวเรียกเก็บเงิน ( PaypalBiller) พ่อครัว ( PizzaChef ) และเด็กส่งพิซซ่า ( PizzaBoy )  และนั่นก็หมายความว่า Dependency ของ PizzaShop  ในที่นี้นั่นก็คือ PaypalBiller  PizzaChef  และ PizzaBoy  นั่นเอง

และเมื่อเราต้องการเรียกใช้ Class นี้ก็สามารถทำได้โดย

Dependency Injection

ทีนี้เมื่อเรารู้จักคำว่า Dependency กันแล้ว มาต่อกันที่คำว่า Injection กันครับ Injection นั้นก็หมายถึง “การฉีด” ดังนั้น

Dependency + Injection = การฉีด Dependency นั่นเอง

จากคลาส PizzaShop เราใช้การสร้าง Dependency โดยการใช้ new ภายใน Class แต่หากเราให้เป็นไปตามวิธีการของ Dependency Injection เราก็ต้องหาทางฉีด (Inject) Dependency นั้นเข้าไป ซึ่งวิธีที่นิยมกันก็จะมี 2 วิธีด้วยกันนั่นก็คือ

  1. ผ่าน Constructor
  2. ผ่าน Setter Method

โดยตัวอย่างด้านล่างจะเป็นการ Inject ผ่าน Constructor นะครับ

// PizzaShop (v.2)

และเรียกใช้ได้โดย

ทำไมต้อง Dependency Injection?

เหตุผลหลักๆ ที่เราทำให้คลาสนั้นเป็นไปตามหลักการ Dependency Injection นั่นก็คือ ความง่ายในการเขียน Unit Test ให้กับคลาสนั้นๆ ซึ่งตามจุดประสงค์ของการทำ Unit Test คือการทดสอบตัวโปรแกรมเป็นส่วนๆ  (Unit) โดยทั่วไปก็คือ Method นั่นเอง

ทีนี้เราลองกลับมาดู PizzaShop (v.1) ซึ่งไม่ได้ทำตามหลัก Dependency Injection

จากโค้ดด้านบนเราสามารถเขียน Test เพื่อทดสอบ Method takeOrder  ได้ดังนี้

จาก Test ด้านบนจุดประสงค์เราก็คือการทดสอบ Method takeOrder เพียงอย่างเดียวเท่านั้น แต่ในความเป็นจริงแล้ว เมื่อเราสร้าง (Instantiate) Object PizzaShop  ขึ้นมา สิ่งที่ถูกสร้างตามขึ้นมาด้วยก็คือ Dependency ทั้งหลายนั่นคือ PaypalBiller , PizzaChef  และ PizzaBoy  นั่นแปลว่าเมื่อเรารัน Test สิ่งที่เราทดสอบนั้นไม่ใช่แค่ Method takeOrder อีกต่อไปแต่กลายเป็นการทดสอบทุกๆ Class ซึ่งการทำเช่นนี้นั้นก็จะไม่ใช่ Unit Test อีกต่อไป แต่จะกลายเป็น Integration Test แทน

อีกหนึ่งปัญหาคือ… ถ้าหากเราต้องการทดสอบว่า Method takeOrder  นั้น throw Exception จริง ถ้าหากการชำระเงินไม่สำเร็จ เราจะเขียน Test อย่างไรดี?

หรือว่าเราจะไปแก้ Method chargeCustomer  ให้ส่งค่ากลับมาเป็น false เพื่อให้เราทดสอบได้?

ถ้าเป็นอย่างงั้นคงไม่ดีแน่ถ้าเราต้องไปแก้ Class เพื่อสำหรับการทำ Unit Test

ทางออกของเรานั้นก็คือ …

Dependency Injection + Mocking Framework

Dependency Injection + Mocking Framework

จะเห็นได้ว่า PizzaShop (v.1) นั้นเราไม่สามารถเขียน Unit Test ให้ครอบคลุมทุกเคสได้ เพราะเราไม่สามารถควบคุม Dependency รวมถึงค่าที่ Method จะ Return ได้

เราลองกลับมาดูที่ PizzaShop (v.2) กันอีกทีนะครับ

จาก PizzaShop (v.2) เราสามารถส่ง Dependency ทั้งหมดผ่าน Constructor ได้ เพราะฉะนั้นสิ่งที่เราทำได้นั้นก็คือ สร้าง Object ที่ Class นั้นต้องการและ Inject เข้าไป

แต่…. คงจะไม่ดีถ้าเรายังคงสร้าง Object จริงๆ เช่น new PaypalBiller()  และส่งเข้าไป เพราะการทำเช่นนั้นก็เหมือนกับการสร้าง Dependency ตัวจริงและเป็นการ Test ฟังก์ชันอื่นๆ ที่เราไม่ต้องการ

สิ่งหนึ่งที่เราทำได้นั้นก็คือ การสร้าง Dependency เสมือนหรือการปลอม Object ขึ้นมา ที่เราเรียกกันทั่วไปว่า การทำ Mocking นั้นเอง

Mocking ~~

Mocking ~ ม๊อคกิ้ง คืออะไร? Mock นั้นแปลว่า การปลอม ซึ่งในทางโปรแกรมมิ่งเราใช้เทคนิค Mocking สำหรับการปลอม Object ขึ้นมาโดยที่ไม่จำเป็นต้องสร้าง Object ตัวจริง รวมถึงการควบคุมค่าที่ Method จะ Return กลับมาอีกด้วย

ตัวอย่างด้านล่างผมจะใช้ Mockery  ซึ่งเป็น Mocking Framework ของ PHP (สำหรับภาษาอื่นๆ นั้นก็จะมี Mocking Framework เฉพาะไป เช่น Mockito ของ Java)

จะเห็นว่าเมื่อเราใช้เทคนิค Mocking แล้วเราไม่จำเป็นที่จะต้องสร้าง Object จริงขึ้นมา ทำให้เราสามารถทดสอบเฉพาะ Method ที่เราต้องเพียงลำพังได้

และจากปัญหาที่เราเจอว่า “หากเราต้องการทดสอบว่า Exception นั้นถูก Throw จริงๆ จาก Method takeOrder  เมื่อการชำระเงินไม่สำเร็จ” เราควรทำอย่างไร?

หากเราใช้เทคนิค Mocking แล้วการควบคุมค่าที่ Return จาก Method ของ Mocked Object สามารถทำได้โดยง่าย โดยการแก้ไขบรรทัดที่ 12 ให้เป็น

หลังจากนั้น Test Case ใหม่ของเราก็จะเป็นไปตามนี้

บทส่งท้าย

จะเห็นได้ว่าการทำตามหลักการ Dependency Injection ควบคู่กับการทำ Mocking นั้นส่งผลให้การเขียน Test ของเรานั้นเป็นไปได้ง่าย และที่สำคัญก็คือ Test Case ของเรานั้นจะมีหน้าที่ในการทดสอบเฉพาะส่วนที่มันควรจะทำ และยังคงเป็นอิสระ (Isolated) จากคลาส (Dependency) อื่นๆ โดยสิ้นเชิง

อ้างอิง

  1. https://en.wikipedia.org/wiki/Dependency_injection

หากบทความมีข้อผิดพลาดประการใดสามารถแนะนำ ติชม ได้เลยนะครับ

prapat