atomic figure 1 450

แปลมาจากบทความเรื่อง The Little Mocker ของ Robert C. Martin
เป็นบทความที่ถามตอบเกี่ยวกับเรื่อง Test Double ได้อย่างสนุกสนานมาก
เหมือนเป็นการเล่าถึงความเป็นมาของ Test Double
ทำให้เข้าใจเรื่อง Dummy, Mock, Stup, Spy และ Fake มากขึ้น
ดังนั้นมาเริ่มกันเลยดีกว่า

ผมแปลตามความเข้าใจของผมเองนะครับ

คำถาม
code ข้างล่างนี้คืออะไร

interface Authorizer {
   public Boolean authorize(String username, String password);
   }

คำตอบ
interface ไงล่ะ ถามได้ แสดดด

คำถาม
แล้ว code ข้างล่างนี้ล่ะ คืออะไร

public class DummyAuthorizer implements Authorizer {
   public Boolean authorize(String username, String password) {
      return null;
   }
}

คำตอบ
ก็เขียนไว้อยู่ว่า dummy ไง

คำถาม
แล้วคุณรู้ไหมว่า dummy เอาไว้ทำอะไร ?

คำตอบ
คุณสามารถทำการส่ง dummy ไปในสักที่
โดยไม่สนใจว่ามันจะถูกใช้ยังไง

คำถาม
แล้วมีตัวอย่างไรไหมล่ะ ?

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

ขอดู code ตัวอย่างหน่อยสิ อธิบายแบบนี้ไม่เห็นภาพเลย
ได้เลยนะ ตามนี้เลย

public class System {
   public System(Authorizer authorizer) {
      this.authorizer = authorizer;
   }

   public int loginCount() {
      //returns number of logged in users.
   }
}

สามารถเขียน unit test เพื่อทดสอบได้ดังนี้

@Test
public void newlyCreatedSystem_hasNoLoggedInUsers() {
   System system = new System(new DummyAuthorizer());
   assertThat(system.loginCount(), is(0));
}

คุณเห็นไหมว่า constructor ของคลาส System นั้นมี argument 1 ตัวนั่นคือ interface Authorize
แต่ใน test นั้น เราทำการสร้าง DummyAuthorize เพื่อส่งเข้าไปยัง constructor
และจะเห็นได้ว่าเราไม่ได้นำมันไปใช้งานอะไรต่อเลย

คำถาม
สังเกตไหมว่า ใน method authorise() ของคลาส DummyAuthorize มัน return null นะ
แต่ไม่เห็น error อะไรเลย ?

คำตอบ
ใช่แล้วนะ ไม่เกิด error อะไร ซึ่ง dummy สามารถ return ค่าอะไรออกมาก็ได้

คำถาม
แล้วทำไมต้อง return ค่าออกมาด้วยล่ะ

คำตอบ
เพราะว่าถ้าใครก็ตามดันมาทะลึ่งใช้ DummyAuthorize ตัวนี้แล้ว จะเจอ NullPointerException อย่างแน่นอน

ดังนั้นใน code production จริงๆ แล้ว DummyAuthorize นี้ก็จะไม่ถูกนำไปใช้งานนะ
มาถึงตรงนี้ น่าจะทำให้เข้าใจเกี่ยวกับ Dummy แล้วนะ

คำถาม
แล้ว dummy มันคือ Mock ใช่ไหมล่ะ
เนื่องจากที่ผ่านมา test object มักจะถูกเรียกว่า Mock มาตลอด

คำตอบ
ใช่แล้วนะ แต่มันเป็นภาษาพูดนะ
เนื่องจากมักจะเรียกว่าการ mock  สำหรับ object ที่กำลังถูกทดสอบ

คำถาม
แล้วชื่อที่เป็นทางการล่ะ เขาเรียกว่าอะไรกัน ?

คำตอบ
เข้าเรียกกันว่า Test Double นะ

คำถาม
แล้วทำไมต้องมีทั้งสองคำด้วยล่ะ แล้วทำไมไม่ใช้ Test Double แทน Mock ล่ะ

คำตอบ
มันมีที่ไปที่มาของมันอยู่นะ ยาวด้วย
อย่าไปใส่ใจมันมากนักเลย

แต่ถ้าอยากรู้จริงๆ ว่ามันเป็นมาเป็นไปอย่างไร อ่านจากเอกสารนี้ได้ MockObject Paper
ซึ่งได้ทำการประกาศคำว่า Mock object ขึ้นมา
คนที่มาอ่านเอกสารนี้ก็นำเอาไปใช้งาน
แต่คนที่ใช้งานต่อๆ ไป ส่วนใหญ่ไม่ได้อ่านเอกสารข้างต้น
เริ่มแปลงคำเหลือแค่ Mocking ซึ่งมันง่ายต่อการพูดนะ

คำถาม
แล้ว Mock คืออะไรล่ะ

คำตอบ
ก่อนที่จะรู้จักกับ Mock เราไปทำความรู้จักกับ Test Double ตัวอื่นก่อนนะ ซึ่งนั่นก็คือ Stub

คำถาม
Stub มันคืออะไรอีกล่ะ

คำตอบ
มันคือ Stub ไงล่ะ … ต่อยกันดีกว่านะ
มาดูตัวอย่างกันก่อนนะ

public class AcceptingAuthorizerStub implements Authorizer {
   public Boolean authorize(String username, String password) {
      return true;
   }
}

โดย method authorise() จะ return ค่า true ออกมา

คำถาม
แล้วทำไมต้อง return ค่า true ออกมาจาก method ล่ะ

คำตอบ
ก็เราต้องการทดสอบระบบว่า เราทำการ login เข้าระบบมาแล้วไงล่ะ

เห็นไหมว่า คุณสามารถส่ง AcceptingAuthorizerStub  มายัง constructor ของคลาส System ได้
และสามารถ login เข้าระบบได้เลย
โดยไม่ต้องสนใจว่าข้างใน production code มันทำอะไรเลย
แต่คุณจะเสียเวลาในการสร้างคลาสขึ้นมาเท่านั้นเอง

คำถาม
ถ้าต้องการไม่ให้ authorize ได้ล่ะ จะต้องทำอย่างไร

คำตอบ
ก็สร้างอีก class มาแล้วให้ method authorise() ทำการ return ค่า false กลับมา
ง่ายไหมล่ะ

เข้าใจตรงกันนะ นี่คือ Stub

คำถาม
ต่อไปใสดู code ข้างนี้กันมันคืออะไร

public class AcceptingAuthorizerSpy implements Authorizer {
   public boolean authorizeWasCalled = false;

   public Boolean authorize(String username, String password) {
      authorizeWasCalled = true;
      return true;
   }
}

คำตอบ
มันคือ Spy ไงล่ะ

คำถาม
แล้วทำไมเราต้องใช้ Spy ล่ะ ?

คำตอบ
เราใช้ Spy เมื่อต้องการทดสอบว่า method authorze() ถูกเรียกจริงๆ หรือไม่

คุณเห็นไหมว่าถ้าคุณเขียน unit test เพื่อทดสอบ จะส่งค่าเข้าไปเหมือน Stub เลยนะ
แต่ unit test จะทำการตรวจสอบเพียงว่า ค่าของ authorizeWasCalled มีค่าเป็น true หรือ false
ตัวอย่างเช่น

@Test
public void callAuthorize() {
   System system = new System(new AcceptingAuthorizerSpy ());
   assertTrue(system.authorizeWasCalled );
}

หรือเราสามารถนับจำนวนครั้งการเข้าทำงานในแต่ละ method ได้อีกด้วย
ดังนั้น คุณสามารถใช้ Spy สำหรับการตรวจสอบว่าภายในระบบงานของเราเป็นไปตามที่ต้องการหรือไม่

คำถาม
ถ้าใช้ Spy มากๆ มันจะผูกมัดกับ code จนเกินไปหรือเปล่า ?

คำตอบ
ใช่แล้ว  การใช้ Spy ควรระวังเช่นกัน
ถ้ามีการใช้งานเยอะเกินไป จะทำให้ส่วนการทดสอบผูกติดกับระบบมากจนเกินไป
และนำไปสู่การทดสอบที่มันแตกหัก หรือ พังได้ง่าย
เคยไหม ที่แก้ไข code เพียงนิดหน่อย แต่ส่วนการทดสอบแก้ไขอย่างมาก
นั่นแหละเรียกว่า การทดสอบที่พังง่าย หรือเรียกว่า Fragile test

คำถาม
ถ้าทำการแก้ไข production code แล้วบางการทดสอบทำงานผิดพลาดหรือไม่ถูกต้อง ล่ะ มันถูกต้องหรือเปล่า

คำตอบ
มันถูกต้องนะ แต่ถ้าเราทำการออกแบบ  production code มาดี
จะทำให้การทดสอบเราทำงานผิดพลาดน้อยมาก

ตอนนี้เรารู้จักกับ Test Double มา 2 ตัวล่ะ คือ Dummy และ Spy แล้วนะ

คำถาม
มาดู code นี้อีกสักหน่อย คุณคือว่าคืออะไร

public class AcceptingAuthorizerVerificationMock implements Authorizer {
   public boolean authorizeWasCalled = false;

   public Boolean authorize(String username, String password) {
      authorizeWasCalled = true;
      return true;
   }

   public boolean verify() {
      return authorizedWasCalled;
   }
}

คำตอบ
มันต้องเป็น Mock อย่างแน่นอน …

คำถาม
จาก code ของ Mock นั้นมันแค่เปลี่ยนจาก Spy แค่เพิ่ม method verify() เข้ามานะ
แล้ว return ค่าของ authorizeWasCalled  ออกไปเท่านั้นเองนะ

คำตอบ
ใช่แล้ว เนื่องจาก Mock จะรู้ว่าเราจะทดสอบอะไรบ้าง

คำถาม
มันเพียงแค่นำเอาการทดสอบไปอยู๋ใน Mock แค่นั้นเองหรอ ?

คำตอบ
ไม่ใช่เพียงแค่นั้นนะ เนื่องจาก Mock มันใช้สำหรับทดสอบพฤติกรรมของระบบงานของเรา

คำถาม
แล้วพฤติกรรมคืออะไร

คำตอบ
Mock นั้นไม่ได้สนใจว่าระบบงานจะ return อะไรกลับมา
แต่ให้ความสนใจว่า method หรือส่วนการทำงานต่างๆ
ถูกเรียกใช้งานตามที่คาดหวังหรือไม่ ด้วย argument ต่างๆ
และนับจำนวนการเรียกใช้งานกี่ครั้ง

คำถาม
เฮ้ย … Mock มันคือ Spy นี่ ?

คำตอบ
คุณเข้าใจถูกแล้ววววว
Mock มันคือการ spy หรือส่งสายลับ เพื่อเฝ้าดูพฤติกรรมของระบบที่ต้องการทดสอบ
และ Mock จะรู้เสมอว่าอะไรคือพฤติกรรมที่คาดหวัง

คำถาม
Mock มันคือเอาสิ่งที่คาดหวังจะทดสอบเข้ามาเลยแบบนี้ ทำให้ผูกมัดเกินไปไหม ?

คำตอบ
ใช่ ดังนั้นมันจึงเกิดพวก Mocking tool มาไงล่ะ
เช่น jMock, EasyMock, Mockito เป็นต้น
โดยเครื่องมือเหล่านี้ จะสร้าง mock object ขึ้นมาให้อย่างอัตโนมัติ

คำถาม
ดูเหมือนว่า มันจะเริ่มซับซ้อนแล้วนะ

คำตอบ
ไม่หรอกนะ
เพื่อความเข้าใจมากขึ้น ลองอ่านเพิ่มเติมในเรื่อง Stub และ Mock ของ Martin Folwer ดูนะ
หรืออ่านหนังสือ  Growing Object Oriented Software, Guided by Tests เพิ่มเติม

มาถึงตรงนี้ เรายังขาดไปอีกหนึ่งตัวใน Test Double นะ นั่นคือ Fake
ตัวอย่าง code เป็นดังนี้

public class AcceptingAuthorizerStub implements Authorizer {
   public Boolean authorize(String username, String password) {
       return username.equals("Bob");
   }
}

คำถาม
เห็นอะไรแปลกๆ ใน code ไหมว่า ทุกๆ คนที่ทำการ authorize จะชื่อ Bob ทั้งหมดเลย

คำตอบ
ถูกต้องแล้ว
เนื่องจาก Fake จะต้องมีพฤติกรรมการทำงานต่างๆ อยู่แล้ว
เราสามารถเปลี่ยน Fake ไปตามพฤติกรรมต่างๆ ด้วยข้อมูลรูปแบบต่างๆ

คำถาม
มันเหมือนการ Simulator เลยนะ

คำตอบ
เข้าใจถูกต้องแล้วนะ

คำถาม
แล้ว Fake มันต่างกับ Stub ยังไงล่ะ

คำตอบ
Fake มันจะใช้พฤติกรรมที่แท้จริงของระบบ แต่ stub จะจำลองพฤติกรรมตามที่ต้องการขึ้นมาแทน
และจะสังเกตว่า Test Double ตัวอื่นๆ จะไม่ได้ใช้พฤติกรรมที่แท้จริงของระบบเลย

ดังนั้น Fake มันจึงแตกต่างกับ Test Double ตัวอื่นๆ อย่างมาก

เราสามารถพูดได้ว่า Mock คือประเภทหนึ่งของ Spy
เราสามารถพูดได้ว่า Spy คือประเภทหนึ่งของ Stub
เราสามารถพูดได้ว่า Stub คือประเภทหนึ่งของ Dummy
แต่ไม่สามารถใช้กับ Fake ได้เลย

คำถาม
ดังนั้น Fake มันน่าจะมีความซับซ้อนมากนะ

คำตอบ
ใช่แล้วมีความซับซ้อนมากสุดๆ เลยล่ะ
จนต้องมี unit test สำหรับ Fake เอง
และยิ่งไปกว่านั้น Fake อาจจะกลายไปเป็นส่วนหนึ่งของ product code เลยก็ได้

ดังนั้น การใช้งาน Fake จะน้อยมากๆ ถึงไม่ใช้เลย
โดย UncleBob บอกว่าใน 30 ปีที่ผ่านมาใช้เพียงครั้งเดียว

คำถาม
แล้วในการเขียน unit test ใช้อะไรใน Test Double บ้างล่ะ

คำตอบ
ใช้บ่อยที่สุดคือ Stub และ Spy และเขียนขึ้นมาเอง
ใช้ Mocking tool น้อยมากๆ
ส่วน Dummy ใช้น้อยมากเช่นกัน

คำถาม
แล้ว Mock ล่ะ

คำตอบ
จะใช้เมื่อใช้ Mocking tool นะ

คำถาม
แล้วทำไมไม่ใช้ Mock ล่ะ

คำตอบ
เพราะว่า Stub และ Spy มันง่ายมากๆ ต่อการสร้างขึ้นมาเอง
เนื่องจาก IDE จะสร้างส่วนต่างๆ ขึ้นมาให้ ซึ่งเรียกว่า Dummy
ต่อจากนั้นทำการแก้ไขเล็กน้อย ก็จะได้ Stub และ Spy มาแล้ว
ดังนั้นแทบไม่จำเป้นต้องใช้ Mocking tool เลย

ความจริงก็คือ UncleBob ไม่ชอบ syntax ของ Mocking tool
ซึ่งมันมีความซับซ้อน ทั้งการ setup และอื่นๆ อีกมากมาย
ดังนั้น จึงเขียนเองดีกว่า มันง่ายดี

โดยสรุป

จากบทความนี้น่าจะทำให้เราเข้าใจกับคำว่า Test Double
ซึ่งประกอบไปด้วย 5 อย่าง คือ Dummy, Stub, Spy, Mock และ Fake
และเห็นภาพว่าแต่ละตัวนั้นเป็นอย่างไร ใช้งานอย่างไร
รวมทั้งสามารถแยกแยะความแตกต่างของมันได้

สำหรับผม มันทำให้ผมมีความกระจ่างในเรื่อง Test Double มากขึ้นมากเลย