เพิ่มเติมเรื่องอื่นๆ
แนะนำ Code Coverage
Code coverage คือ ตัวชี้วัดในการทำ software testing เพื่อเป็นการอธิบายว่า source นั ้นได้ถูกทดสอบไปแล้วทั้งหมดเท่าใด และครอบคลุมส่วนของ source code มากน้อยเพียงใดออกมาได้
ซึ่งคำสั่งของภาษา Go ได้เตรียมคำสั่งสำหรับการทำ code coverage ให้เป็นที่เรียบร้อย สามารถดู Code Coverage ได้คือคำสั่งนี้
# คำสั่งนี้สำหรับการ run test coverage ทั่วไป
go test ./... -cover
# คำสั่งนี้สำหรับการ run test coverage เพื่อให้ได้ file สำหรับการ convert มาเป็น html
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
จะได้ html code coverage ทั้งหมด ออกมา และสามารถดูผลลัพธ์ออกมาได้ว่า code ส่วนไหนยังไม่ได้ถูก Test ออกมาได้
เช่นแบบนี้
ข้อดีของการทำคือมีตัวชี้วัดที่ชัดเจนขึ้นว่า ตัว source code ของเรานั้นโดนทดสอบผ่าน code ทั้งหมดที่เราเขียนมาแล้วหรือยัง เพื่อเป็นการรับประกันว่า source code เราได้ถูก test แล้วและไม่มีส่วนของ Source code "dead code" (Source code ที่ไม่ได้ถูกใช้งานจริง) อยู่ด้วยเช่นเดียวกัน
แต่ข้อพิจารณาคือ Code coverage "ไม่ได้เป็นการบอกว่าการ test นี้มีคุณภาพเพียงใด" สิ่งเหล่านี้ยังคงต้องตรวจสอบจาก Test case เหมือนเดิมว่า เรากำลังทดสอบตามโจทย์ของเราอยู่หรือไม่อยู่ดี
เพราะแม้ว่า Code coverage อาจจะได้เกือบ 100% แต่ Test case แทบไม่ได้ตรวจสอบผลลัพธ์ของตัว Unit test = ก็ถือเป็น Unit test ที่ไม่ครอบคลุมในแง่การใช้งานอยู่ดี
guideline สำหรับการทำ unit test
คำแนะนำสำหรับผู้ที่จะเริ่มต้นทำ unit test จะให้ guideline ไว้ตามนี้
-
รู้ว่าต้อง Test อะไร Focus การทดสอบว่า เรากำลังทดสอบ functions นี้หรือ method นี้ไป เพื่อมั่นใจเรื่องอะไร โดยพยายาม focus ไปที่ "จุดประสงค์เดียวของ function" และ ทดสอบ "สิ่งที่ไม่ใช่ตามจุดประสงค์ของ function" เพื่อให้แน่ใจว่าต่อให้ส่งอะไรที่ไม่ถูกต้องนะ เรายังคงตามจุดประสงค์ของ function ได้
-
เขียนชื่อ Test case ให้เคลียร์ เขียนให้ชัดเจนว่าแต่ละ Test case มีจุดประสงค์เพื่อ Test อะไร (การตั้งชื่อมีผลต่อการกลับมาตรวจสอบ Test ค่อนข้างมาก)
-
ทุก unit tests ต้อง independent กัน ผลลัพธ์จากการทดสอบของแต่ละ unit tests ต้องแยกออกจากกันชัดเจน และต้องไม่มีผลลัพธ์อันไหนมาส่งผล ต่อผลลัพธ์ของ unit tests ตัวนี้ได้
- หากมีเคสที่มีผลต่อกัน = จำเป็นต้อง mock การทดสอบ โดยตั้งสมมุติฐานว่า ผลลัพธ์ของ service ที่มาขึ้นต่อกันนั้น "มีผลลัพธ์เป็นแบบไหน" ออกมา
- ทำการ mock เพื่อให้ผลการทดสอบ unit test ทุกรอบออกมาเหมือนเดิม และจะได้แน่ใจว่า การทดสอบนี้เป็นการทดสอบแค่เฉพาะ function ของเราอย่างเดียว
-
1 Test case = Test อย่างเดียวพอ แต่ละ Test case ควร verify เพียงแค่ ส่วนเดียวหรือผลลัพธ์จากการ run function จากข้อมูลชุเดียว เพื่อให้ง่ายต่อการกลับมาตรวจสอบและไม่ใช้ Unit test ในแต่ละ Test case ใหญ่เกินไปเมื่อเกิด Test case ที่ Fail เราจะสามารถตรวจสอบจาก code ได้ไวยิ่งขึ้นว่าเกิดจากอะไรได้ (แทนที่จะให้ 1 Test case ใหญ่และต้องมาไล่ดูภายใน Test case อีกที)
-
ใช้ Mock กับ Stubs ตามข้อที่ 2. เมื่อมีส่วนใดก็ตามที่เกี่ยวข้องกับส่วนอื่น รวมถึง "external system" (เช่น payment gateway, database, web service) ควรใช้ mocks หรือ stubs (เทคนิคเหมือนกันกับที่เราทำกันทั้งหมดใน Hexagonal Architecture ที่มีการ mock ผลลัพธ์ของ service เอาไว้) เพื่อเป็นการจำลอง external system เหล่านั้นออกมาแทน
- เพื่อให้การ run unit test เป็นการทดสอบเฉพาะส่วน code ของเราเท่านั้น และ ลดความซับซ้อนและคาดเดาไม่ได้ จากการ run unit test (เพื่อให้ unit test สามารถ run ได้ทุกรอบโดยที่ไม่ต้องขึ้นอยู่กับ Service ตัวอื่นๆได้)
- (เพิ่มเติม) ใช้งานร่วมกับ CI/CD เพื่อให้การ run test นั้นเกิดขึ้นทุกรอบ เราสามารถเพิ่มเติมเรื่องของ CI/CD ในการ run test ออกมาได้ เพื่อให้ source code ก่อนที่จะขึ้นงาน ผ่านการตรวจสอบด้วย unit test เสมอ
ข้อดีใหญ่ๆ อีกอย่างของการเขียน unit test ที่ดีคือ "การทีี่ unit test สามารถเป็น document ที่ดีได้เช่นเดียวกัน" จะทำให้เรามี Report การทดสอบเพิ่มเติมได้ว่า ระบบเราทดสอบอะไรไปบ้างแล้ว
การทำ Unit test นี้ ไม่ใช่สิ่งที่อยู่ในระดับที่ "ต้องทำ" แต่ เป็นสิ่งที่ "ควรทำ" เพื่อให้ Source code ของเราผ่านการตรวจสอบโดย developer เอง และเพื่อให้สามารถ maintain source code ได้ในอนาคต ในกรณีที่กลับมาแก้ไข ดังนั้น การทำสิ่งนี้จะช่วยทำให้ code quality ของเราดีขึ้นเช่นกันนะครับ
และนี่คือหัวข้อสุดท้ายของงาน development ฝั่ง Go หัวข้อต่อไปเราจะพามาคุยกันเรื่องของการ Deploy งานด้วยภาษา Go บ้างว่ามีวิธีไหนที่เราสามารถ Deploy ได้บ้าง มาคุยกันในหัวข้อต่อไปกันครับ