หลังจากที่เราเขียน code และ เขียน test สำหรับการจัดการสกุลเงินแบบง่ายๆ
ผ่านไปแล้วใน Programmers Love Writing Tests ด้วย jUnit ตอนที่ 1
ต่อไป เรามาทำอะไรที่มันท้าทายหรือยากขึ้นมาหน่อย
นั่นก็คือ การจัดการกับสกุลเงินต่างๆ มากกว่า สกุลเดียวกัน
มาเริ่มกันเลยดีกว่า

ปัญหาเรื่อง Multiple Currency

มีเงินหลากหลายสกุลในกระเป๋า เราจะจัดการอย่างไรดีล่ะ ?

เริ่มต้นด้วยการสร้าง class MoneyBag สำหรับจัดเก็บเงินในแต่ละสกุล
ซึ่งสามารถใส่เงินได้ครั้งละ 2 สกุล หรือ ใส่มาเป็น list ได้เลย
โดยใน class MoneyBag ทำการเก็บข้อมูลของเงินในแต่ละสกุลไว้ใน List ดังนี้

ให้สังเกตว่า
มี method appendMoney() ไว้เป็น utility method หรือ helper method ภายใน
สำหรับการเพิ่มเงินในแต่ละสกุลไว้ใน List ของเงินในกระเป๋า

คำถาม
Test case แรกที่เราจะเขียนคืออะไร ?
คำตอบ
ทำการทดสอบ equals ของ MoneyBag ก่อนไหม ?

ดังนี้

สิ่งที่เราต้องทำต่อไปคือ การสร้าง method equals() ใน class MoneyBag ดังนี้

อย่าลืมว่า
เราจะทำการเดิน หรือ พัฒนา code ของเราไปแบบ small step นะครับ
นั่นคือ Code a little, Test a little
ห้ามลืมเด็ดขาด !!!
โดยในการ test เราจะใช้ jUnit นะ
เพื่อใช้ตรวจสอบในสิ่งที่เรากำลังพัฒนากันอยู่ ว่าถูกหรือไม่ …

คำถาม
ต่อไปเราจะทดสอบอะไรล่ะ ?
ต่อไปเราจะทำอะไรล่ะ ?
คำตอบ
สิ่งที่ต้องทำต่อไปคือ การรวมเงินสกุลต่างๆ เข้าด้วยกัน
ลองไปดู method add() ใน class Money สิ

ถ้าเรามีสกุลเงินที่แตกต่างกันจะทำอย่างไรล่ะ ?
จะเปลี่ยน code ใน method add() เป็นแบบนี้ก็ไม่ได้
เพราะว่า มัน compile ไม่ผ่าน !!
เพราะว่า สิ่งที่ return กลับมามันต่างชนิดกัน

คำถาม
แล้วเราจะแก้ไขปัญหานี้ได้อย่างไรล่ะ ?
คำตอบ
ในตอนนี้เห็นไหมว่า เรามี class ที่เป็นตัวแทนของเงิน 2 class ก็คือ Money และ MoneyBag
โดยที่เราต้องการซ่อนการทำงานไว้ ไม่ให้ผู้ใช้งานรู้

ดังนั้น
น่าจะได้เวลาต้องสร้าง interface Money ขึ้นมา จะดีไหม ?

แสดงด้วย class diagram ดังรูป
Screen Shot 2558-02-26 at 11.47.15 AM

ขั้นตอนการสร้างและใช้งาน interface เป็นดังนี้

จากนั้นทำการแก้ไข class ที่เราสร้างมานิดหน่อยคือ
เปลี่ยนชื่อจาก class Money ไป SimpleMoney
class SimpleMoney และ MoneyBag ทำการ implement จาก interface Money

สิ่งที่ต้องจำให้ขึ้นใจก็คือ ทุกๆ test case จะต้องผ่านทั้งหมดเสมอนะครับ
Screen Shot 2558-02-26 at 1.12.32 PM

คำถาม
ต่อไปจะทดสอบ test case อะไรดีล่ะ ?
คำตอบ
ตอนนี้เราจะใช้ MoneyBag เป็นตัวแทนสำหรับการรวบรวมเงินจากแต่ละสกุลนะครับ
ดังนั้น ก่อนที่จะเขียน code อะไรเพิ่ม
เราต้องทดสอบสร้าง MoneyBag จาก list ของ SimpleMoney ก่อนน่าจะดีกว่านะ !!

คำถาม
ต่อจากนี้จะทดสอบอะไรอีกล่ะ ?
คำตอบ
ถ้าต้องการเพิ่ม MoneyBag เข้าไปยัง SimpleMoney ล่ะ ?
ถ้าต้องการเพิ่ม SimpleMoney เข้าไปยัง MoneyBag ล่ะ ?
ถ้าต้องการเพิ่ม MoneyBag เข้าไปยัง MoneyBag ล่ะ ?

จากแต่ละ test case เหล่านี้
จะส่งผลให้เกิด code ของ SimpleMoney และ MoneyBag อยู่ในรูปแบบนี้

คำอธิบาย
SimpleMoney ก็สามารถเพิ่ม Simple Money และ MoneyBag
MoneyBag ก็สามารถเพิ่ม Simple Money และ MoneyBag ได้เช่นกัน

ดังนั้นสิ่งที่เราต้องแก้ไขเพิ่มเติมก็คือ
เพิ่ม method addSimpleMoney() และ addMoneyBag() เข้าไปที่ interface Money ซะเลย
โดย interface Money จะมีหน้าตาดังนี้

จากนั้นก็ทำการแก้ไขทั้ง class SimpleMoney และ MoneyBag เสียด้วยนะ
เพื่อให้สามารถทำงานได้ตามที่ต้องการ
ด้วย code ดังนี้

และ

และอย่าลืมว่าทุก test case ต้องผ่านทั้งหมดนะครับ

คำถาม
ต่อไปจะทำการทดสอบด้วย test case อะไรครับ ?
ได้ยินคำถามนี้มาจนน่าจะชินแล้วนะ …

คำตอบ
เรายังเหลือ test case ที่สำคัญอีกนะ คือ
ในตอนนี้เราเพิ่มเงินในสกุลต่างๆ เข้าไปใน MoneyBag หรือกระเป๋าเงินได้แล้ว
แต่เรายังไม่เคยเอามารวมหรือคำนวณอะไรเลยนะ
ดังนั้น มาทดสอบการคำนวณในเบื้องต้นก่อนดีไหม ?

ตัวอย่างเช่น
ใน MoneyBag ประกอบไปด้วย 5THB กับ 3USD
ถ้าเราต้องการลบด้วย -3USD ล่ะ
ผลลัพธ์ที่เราต้องการก็คือ 5THB ใช่ไหม ?

เรามาเขียน test case กันเลยสิ จะช้าอยู่ทำไมล่ะ….

มาถึงตรงนี้ คุณเห็นอะไรบ้างครับ ?

เห็นไหมว่า การแก้ไขปัญหาแบบ step-by-step
เห็นไหมว่า การแก้ไขปัญหาแบบเล็กๆ ไปเรื่อยๆ
ค่อยๆ ทำไปในทีละส่วน
ค่อยๆ ทำไปทีละ operation
แล้วคุณจะเห็นเองว่าความเร็วในการพัฒนามันมีเหตุมีผลอย่างไร
เห็นไหมว่า เมื่อเรามีชุดของการทดสอบแล้ว จะไม่กลัวการแก้ไข code !!

ดังนั้น ลองลงมือ code ต่อไปให้จนครบทุกๆ operation นะครับ เช่นการบวก ลบ คูณ และ หาร เป็นต้น

โดยสรุปแล้ว คุณต้องฝึก และ ฝึก และ ฝึก ในการพัฒนารูปแบบนี้ครับ

คุณ Martin Fowler กล่าวไว้เกี่ยวกับการเขียน test ไว้ว่า

เมื่อใดก็ตามที่คุณพยายามที่จะดูข้อมูลจากตัวแปร หรือ method ต่างๆ เช่น
ทำการ print ค่าออกมาทาง console
ทำการ debug code
นั่นหมายความว่า คุณต้องเริ่มเขียน test ได้แล้วนะ !!

แน่นอนว่าในช่วงเริ่มต้นมันจะทำให้คุณช้ามากๆ
เนื่องจากจะต้องกำหนดค่าเริ่มต้นต่างๆ มากมาย เช่น test fixture เป็นต้น
และจะต้องฝึกกระบวนการคิดเช่นกัน
ดังที่ผมจะถามเสมอว่า test case ต่อไปคืออะไรนั่นเอง
แต่เมื่อคุณทำไปเรื่อยๆ มันจะทำให้คุณดีขึ้นเรื่อยๆ

ข้อแนะนำอย่างหนึ่งสำหรับการเขียน test

โดยปกติเรามักจะเชื่อว่า ต้องเขียน test ให้ได้มากที่สุด
แต่อย่างไรก็ตาม เราสามารถเลือกเขียน test ในส่วนที่จำเป็นและสำคัญก็ได้นะครับ
ซึ่งมันน่าจะมีประโยชน์กว่าหรือไม่ ?
เนื่องจากทุกๆ test case มันมี cost ของมันอยู่เสมอ
ดังนั้น จงเขียน test ที่คุณคิดว่า มันคุ้มค่าต่อการลงทุนลงแรงไป

มี 2 ช่วงเวลาที่เหมาะสมสำหรับการลงทุนเขียน test คือ

  • ช่วงของการพัฒนา คือ เมื่อคุณต้องการเพิ่ม feature ใหม่ ให้เริ่มต้นด้วยการเขียน test ก่อนเสมอ แล้วทำการเขียน code เพื่อให้ test ผ่านไปแบบ step-by-step
  • ช่วงของการ debug คือ เมื่อใครก็ตามพบข้อผิดพลาดต่างๆ แล้วส่งกลับมาให้เรา สิ่งแรกที่ควรทำก็คือ การเขียน test เพื่อทำให้เกิดข้อผิดพลาดนั้นๆ จากนั้นทำการแก้ไข จนกว่า test นั้นจะผ่าน หรือข้อผิดพลาดนั้นถูกแก้ไขแล้ว

และสุดท้ายอย่าลืม run ชุดของ test อยู่อย่างเสมอนะครับ

ที่สำคัญต้อง run ให้ครบทุก test ด้วยนะ
แต่ถ้าเวลาในการ run test ทั้งหมดมันใช้เวลานานมากๆ
แนะนำให้เริ่มทำการปรับปรุงได้แล้ว เช่น
ให้ test ทำงานแบบขนาน
แบ่ง test ออกเป็นชุดๆ

มีคนเคยบอกไว้ว่า การจะเดินหรือวิ่งไปได้เร็วๆ คือ การเดินหรือวิ่งแบบดีๆ นะครับ