in Technology

รูปแบบการทดสอบของ Unit Test จะเป็น Solitary หรือ Sociable ดี

จากที่ได้ทำงานร่วมกับลูกค้า และพบเองในกลุ่ม Coach ตอนทำงานกันเอง คือการพูดถึงเรื่อง Unit Test ที่เขียนอย่างไรถึงจะถูกต้อง? เช่น ขนาดของ Unit Test ต้องเล็กขนาดไหน?, ทดสอบ Method ที่เรียกหา Method อื่นๆ ตัวเดียวได้ไหม? เป็นต้น

โพสต์นี้ผมจะอ้างอิงถึงลุง Martin Fowler เยอะหน่อย เพราะไปอ่านที่แกอธิบายไว้เรื่อง UnitTest และ Mocks Aren’t Stubs ได้น่าสนใจมาก

ถ้าถามว่า Unit Test คืออะไร ต้องทดสอบในหน่วยย่อยขนาดไหน ไม่มีใครสามารถตอบได้ แม้แต่ลุง Martin Fowler และ Kent Beck เพราะนิยามของแกก็เปลี่ยนไปตามงานที่ทำ แต่ไม่ว่าจะนิยามอย่างไร มันจะมีลักษณ์เหมือนกัน 3 ข้อ คือ

  1. มุ่งทดสอบในหน่วยที่เล็กที่สุดของระบบ – นั่นสิ แล้วระบบเรามีหน่วยย่อยสุดแค่ไหน ต้องกลับไปดูว่าเราออกแบบไว้อย่างไร
  2. เขียนโดยโปรแกรมเมอร์ ด้วยเครื่องมือที่ถนัด  – ใช่แล้ว เป็นโปรแกรมเมอร์เขียนนะ ไม่ใช่ Tester!, และจะใช้เครื่องมืออะไรก็ตามถนัดเลย จะเขียนเช็เทียบค่าเอง หรือใช้ Unit Test Framework เช่น xUnit ก็ตามสะดวก
  3. มีการทดสอบที่ได้ผลลัพธ์ออกมาเร็วมากๆ – เห็นผลในระดับเสี้ยววินาที (millisecond)

ข้อ 2, 3 ยังพอเข้าใจได้ แต่ข้อ 1 ขอบเขตยังไม่ชัดเจนนัก แต่อย่างที่ผมบอก มันอยู่ที่การออกแบบซอฟต์แวร์เราแต่แรกว่าย่อยได้ขนาดไหนและครบถ้วนหรือไม่ (ดังนั้นจึงเป็นที่มาว่าทำไมซอฟต์แวร์ที่พัฒนาโดยใช้หลักการ Test-Driven Development ถึงสามารถเขียน Unit Test ได้ครบ, ครอบคลุม และมีโครงสร้างระบบที่สามารถทดสอบได้ตั้งแต่ต้น)

การออกแบบซอฟต์แวร์ของเรา หลีกเลี่ยงไม่ได้ที่จะมี Method ที่ทำงาน 2 แบบ คือ ทำงานจบได้ในตัวมันเอง กับ ต้องอาศัย Method อื่นๆ มาประกอบกันเพื่อทำงาน

ซึ่งการทดสอบของ Unit Test ก็เช่นกัน ได้จำแนกออกเป็น 2 แบบตามการทำงานของ Method คือ

Unit Test ที่ทดสอบแบบ Solitary

คือทดสอบการทำงานเพียง​ Method เดียวเท่านั้น เช่น เรามีระบบที่ให้สมาชิกสามารถโอนเงินได้และคิดค่าธรรมเนียม ดังนั้นเราจะทดสอบเฉพาะ Method การคิดค่าธรรมเนียม โดยไม่เอา Method ทำงานเรื่องสมาชิกและการโอนเงินเข้ามาเกี่ยวข้องเลย ดังนั้น จะต้องออกแบบให้แยกจากกันให้ดี และใช้หลักการของ Test Doubles เช่น Mock หรือ Stub เข้ามาจำลองการทำงานอื่นๆ (ส่วนของสมาชิก, โอนเงิน) ที่เราไม่ต้องการทดสอบ

Unit Test ที่ทดสอบแบบ Sociable

คือทดสอบการทำงานของ Method ที่เกี่ยวข้องกับ Method อื่นๆ ด้วย ซึ่งลุง Martin บอกว่าในสมัยยุค 90 ที่ xUnit เพิ่งจะเกิด รูปแบบ Sociality นี้ได้รับความนิยมมาก เพราะมันทำได้สะดวกกว่าแบบ Solitary

การทดสอบแบบ Sociality นี้เอง ค่อนข้างสร้างความสับสนให้หลายคน ว่าเรียกมันเป็น Unit Test ได้หรือไม่ เพราะไม่ได้ทดสอบการทำงานเพียง Method เดียว, ซึ่งลุง Martin คิดว่ามันเป็น Unit Test ครับ เพราะเป็นการทดสอบสิ่งที่เกิดขึ้นของ Method จริงๆ ไม่มีการตัดแต่งเพื่อทดสอบหรือการจำลองใดๆ แม้ว่ามันจะดูมีหลายการทำงาน (หรือหลาย Unit ในความคิดเรา) ก็ตาม

ต่อมาในช่วงปี ค.ศ. 2000 ที่ xUnit เป็นที่นิยมแล้ว การทดสอบแบบ Solitary ก็กลับมาบูมอีกครั้ง เพราะมีเครื่องมือที่ช่วยในการทำ Mock เกิดขึ้นมากมาย จนได้เกิดสำนักที่เขียน Unit Test ขึ้นเป็นสองสำนักใหญ่ๆ (School) คือ

Classical (หรือเรียกว่าชาว Classicist, หรือสำนัก Detroit)

คือใช้รูปแบบการทดสอบแบบ Sociable เน้นทดสอบโดยใช้ Object, Method, Properties จริงๆ มีการอ้างอิง Method ถึงกันก็ปล่อยมันไป เพื่อให้เห็นพฤติกรรมจริงๆของมัน, แต่ทั้งนี้ ก็มีใช้ Mock ด้วย แต่เท่าที่จำเป็นเท่านั้น อย่างการเชื่อมต่อ Database, Network

โดยกลุ่ม Classical จะเป็นพวกเน้นทดสอบสถานะการทำงานของ Method นั้นๆ (States verification) ว่าทำงานแสดงค่าออกมาได้ถูกต้องตามที่คาดหวังหรือไม่ เช่น ส่งข้อมูลเข้าไป Method ผลที่ได้ออกมาต้องเป็น ถูก หรือ ผิด หรือได้ค่าการเปลี่ยนแปลงตามที่หวังไว้ออกมาเหมือนเดิมเสมอๆ

ที่มาของชื่อ Detroit เพราะมันถูกใช้ใน XP Programming ครั้งแรกกับโปรเจ็ค C3 ของ บริษัท Chrysler ในเมือง Detroit

Mockist (หรือเรียกว่าชาว Mockist, หรือสำนัก London)

คือใช้รูปแบบการทดสอบแบบ Solitary ที่ทำงานเดียวเท่านั้น ดังนั้นจะต้องตัดตัวเกี่ยวข้องออก มีการใช้ Mock, Stub เข้ามาสร้างตัวจำลองของ Object, Method, Properties เสมอๆ

โดยกลุ่ม Mockist จะเป็นพวกเน้นใช้ Mock เพื่อทดสอบพฤติกรรมการทำงานของ Method นั้นๆ (Behavior verification) ว่าทำงานได้ตามที่คาดหวังหรือไม่ เช่น ส่งข้อมูลเข้าไป Method เพื่อบันทึกข้อมูล จะเห็นว่ามีการบันทึกข้อมูลเกิดขึ้นเสมอ เป็นต้น

ที่มาของชื่อ London คือ ถูกใช้โดยกลุ่มคนใช้ XP Programming ในยุคเริ่มแรกในเมือง London

ซึ่งการทดสอบสองแบบนี้เรียกว่าเป็น Unit Test ทั้งคู่ และลุง Martin เองก็เคารพในการตัดสินใจของของผู้ที่จะเลือกใช้ ไม่มีใครผิดใครถูก หรืออะไรดีกว่ากัน

และที่สำคัญลุง Martin มองว่า ถ้าต้องไปเชื่อมต่อกับระบบภายนอกเช่น I/O, Database, Network ที่มันมีความเสถียรพอ และมีความเร็วมากพอ ก็สามารถเอามาใช้ใน Unit Test ได้นะ เช่น จำลอง MySQL Database ด้วย In-Memory Database เป็นต้น

แล้วจะเลือกอะไรดีระหว่าง Classicist หรือ Mockist

จากทั้งหมดที่เขียนเล่ามา ไม่ว่าจะความเห็นส่วนตัว หรือความเห็นของลุง Martin เอง ทั้งสองแบบดีหมด ไม่จำเป็นต้องไปเลือกมันหรือต้องไปสนใจว่า Method นี้จะทดสอบด้วยอะไร มันอยู่ที่ว่าเราเข้าใจหรือเปล่าว่า Unit Test คืออะไร และเราจะทดสอบอะไร

ถ้า Method มันทำงานจบในตัวเอง หรือมีการใช้ Method อื่น แบบง่ายๆ สามารถแยกการทำงานได้ แยกการทำสอบได้ เราก็ไม่จำเป็นต้องไปจำลอง Object โดยใช้ Mock, Stub หรืออื่นๆใน Test Doubles เลยก็ได้

แต่ถ้ามันดูยุ่งยาก และจำเป็นต้องเชื่อมต่อกับส่วนอื่น และมันทำให้การทดสอบช้าลง หรือไม่เสถียร เช่น Network, Database แบบนี้เราก็เลี่ยงไม่ได้ที่จะต้องใช้ Test Doubles นะ

สรุป

  • เราจะใช้ Unit Test ที่มีรูปแบบการทดสอบเป็น Classical หรือ Mockist ก็ได้ ไม่มีผิดไม่มีถูก
  • แยกการทำงานของ Method ให้ทำงานทีละอย่าง
  • ถ้า Method ไหนมีแต่ Business Logic – เป็น Classical
  • ถ้า Method ไหนมี Business Logic โดยจำเป็นต้องข้องเกี่ยวกับ Method อื่นที่คุมไม่ได้ หรือระบบภายนอก – เป็น Mockist
  • ถ้า Method ไม่มี Business Logic และไปข้องเกี่ยวกับอื่นที่คุมไม่ได้ หรือระบบภายนอก – เป็น Mockist
  • จะแยกแยะได้ขนาดนี้ แปลว่าต้องทำ High Level Design และ Detail Level Design
  • ถ้าจะทำให้เขียน Unit Test ให้ง่าย ควรเริ่มด้วย Test First และ Test-Driven Development
  • ถ้าอ่านโพสต์นี้แล้วไม่เข้าใจ ต้องไปเรียนรู้ Unit Test และ Test Doubles เพิ่มนะ
  • พอเขียนเสร็จ รู้สึกว่าตัวเองต้องไปเรียนรู้เรื่อง OOP, SOLID ให้มากขึ้น

อ้างอิง

มาคุยกัน

Comment