Code coverage

https://www.reddit.com/r/ProgrammerHumor/comments/5sv04i/for_complete_code_coverage/

จากการที่ได้ทำ project กับทีมนั้นทำให้เกิดการพูดคุยเรื่องที่น่าสนใจต่าง ๆ มากมายไม่ว่าเรื่องนั้นจะเกี่ยวข้องโดยตรงกับ project หรือไม่ หนึ่งในนั้นคือเรื่องของการใช้งาน code coverage เคยคิดมาตลอดว่าการใช้งานมันมีเพียงแค่รูปแบบเดียว แต่เพิ่งรู้ว่ามันสามารถเอาไปประยุกต์ใช้ได้อีกหนึ่งรูปแบบ เลยจดบันทึกไว้ซะหน่อย

Code coverage คืออะไร

เราเคยเขียนบทความเกี่ยวกับ code coverage ไปบ้างแล้ว เลยจะยกเนื้อหาบางส่วนมาปูทางกันก่อน ลองไปดูกันใน Medium เพิ่มเติมนะครับ

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

หนึ่งในเครื่องมือที่สามารถนำมาใช้ได้คือ “code coverage” ซึ่งมันสามารถวัดได้ว่ามี code ส่วนไหนที่ถูก execute โดยชุดการทดสอบของเรา ดังนั้นถ้ามันมี code ที่ไม่ได้ execute เราก็ไปเพิ่มชุดการทดสอบให้มันครอบคลุมซะ

ถึงแม้ปัญหาของ code coverage คือมันแค่ดูว่า code ส่วนไหนที่ถูก execute ไม่ใช่ถูก test แต่ใน project ที่ code ซึ่งตรงนี้ก็ต้องใช้แนวคิด mutation testing ละ

แต่มันไม่ได้หมายความว่าไม่ต้องใช้ code coverage แล้วไปใช้ mutation testing แทนเลยนะครับ เราสามารถใช้ควบคู่ไปด้วยกันได้ หนำซ้ำ mutation testing จะเกิดประโยชน์สูงสุดจาก project ที่มี code coverage สูง ๆ แล้ว ดังนั้น code coverage เหมาะมาก ๆ สำหรับ project ที่ code เริ่มเยอะแต่มีชุดการทดสอบนิดเดียวหรือไม่มีเลยซึ่งส่งผลกระทบต่อความมั่นใจตรง ๆ ซึ่ง code coverage สามารถวัดได้จาก 3 สิ่งคือ

  • Lines: วัดจากจำนวนบรรทัดที่ถูก execute จาก test สามารถผ่านได้ง่ายที่สุด เพราะในหนึ่งบรรทัด จะมีจำนวน statement ของ code (block) กี่อันก็ได้ ขอให้มีอันนึงที่ cover ก็ได้ทั้งบรรทัดละ
  • Blocks: วัดจากจำนวน block ที่ถูก execute จาก test ก็จะยากขึ้นมา
  • Branches: วัดจากจำนวนเงื่อนไขที่เป็นไปได้ทั้งหมด (conditional branches) ที่ถูก execute จาก test เป็นอันที่ผ่านได้ยากที่สุด

ต่อมาคือการเล่นกับค่า code coverage เพื่อนำไปปรับปรุง code ของเรา มีด้วยกัน 2 รูปแบบ

Fixed code coverage

มันก็คือการที่ทีมกำหนดค่า code coverage ขั้นต่ำขึ้นมา 1 ค่า โดยมีเงื่อนไขง่าย ๆ คือ

ถ้า code coverage ที่วัดออกมาได้มีค่าเกินจากที่กำหนดไว้ ก็ถือว่าผ่าน

การใช้งานลักษณะนี้จะเหมาะสำหรับ project ที่มีการเขียนชุดการทดสอบมาบ้างพอสมควรแล้วเพราะจะได้หาตัวเลขที่ตอบความมั่นใจของทีมง่ายขึ้น

Force improvement code coverage

มันก็คือการที่ทีมกำหนดค่า code coverage ขั้นต่ำขึ้นมา 1 ค่า เหมือนกัน โดยมีเงื่อนไขที่ซับซ้อนขึ้นมาคือ

ถ้า code coverage ที่วัดออกมามีค่าสูงกว่า code coverage จากครั้งก่อนหน้าไม่เกินจากที่กำหนดไว้ ก็ถือว่าผ่าน

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

แต่จะดันไม่ผ่านถ้า code coverage ที่เราปรับปรุงสูงกว่าค่าที่กำหนดไว้ เพราะหากเราตั้ง code coverage ไว้สูงเกินไปแบบ 100% ทีมจะต้องออกแรงในการเขียนชุดการทดสอบครอบคลุม code บางส่วนที่ไม่จำเป็น เช่น getter/setter method ซึ่งอาจจะไม่คุ้มแรงเมื่อเทียบกับความมั่นใจที่ได้รับกลับมา

สรุป

จะเห็นว่าเราสามารถเล่นกับ code coverage ตามสถานการณ์ใน project ได้ หาก project มีความ mature ในระดับนึงเราก็จัด fixed เลย แต่ถ้ายังเราก็เริ่มที่ force improvement ก่อน พอใช้ไปสักพักก็จะถึงจุดที่ mature ขึ้น code coverage มันนิ่งหรือต่ำลงไปบ้างทำให้ code coverage เราไม่ผ่านบ่อยขึ้น เราก็เปลี่ยนมาใช้เป็น fixed แทน สำคัญคือทีมพัฒนาควรจะเข้าใจจุดประสงค์ของ code coverage ในการใช้เป็นเครื่องมือในการปรับปรุงคุณภาพของ code มากกว่าจะเอามาเปรียบเทียบกับทีมเพื่อนข้าง ๆ เพราะเขาก็มี context และสภาพทีมต่างจากเรานั่นเอง