ทำไม Unit Test ที่ผ่านหมด อาจยังมีบั๊กซ่อนอยู่? รู้จักกับ Mutation Testing

7 นาที 27 views บันทึกเป็น PDF
ทำไม Unit Test ที่ผ่านหมด อาจยังมีบั๊กซ่อนอยู่? รู้จักกับ Mutation Testing

มั่นใจแค่ไหนว่า Unit Test ที่เขียนไว้ครอบคลุมจริง? มาทำความรู้จัก Mutation Testing เทคนิคการจงใจใส่บั๊กเพื่อเช็กว่าบททดสอบของเราตาบอดอยู่หรือไม่

ทดสอบโค้ดผ่านหมดแล้ว มั่นใจได้แค่ไหน

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

ลองนึกภาพว่าเราเขียนคำสั่งให้หุ่นยนต์เดินไปข้างหน้า 5 ก้าว แล้วเราก็เขียนบททดสอบว่า "หุ่นยนต์ต้องเดินได้ 5 ก้าว" ถ้าหุ่นยนต์เดินได้จริง บททดสอบก็ผ่าน แต่ถ้าหุ่นยนต์ตัวนั้นมีบั๊ก (ข้อผิดพลาด) ที่ทำให้มันเดินถอยหลังก่อนจะเดินหน้า 5 ก้าวล่ะ บททดสอบเดิมก็อาจจะยังมองว่าผ่านอยู่ดี

นี่คือเหตุผลว่าทำไมโค้ดที่ผ่านการทดสอบแล้ว ถึงยังอาจมีบั๊กซ่อนอยู่มากมาย การทดสอบแบบเดิมๆ อาจจะไม่ได้ครอบคลุมทุกกรณี หรือบททดสอบนั้นอาจจะ "ตาบอด" ในจุดที่โค้ดมีปัญหาพอดี วันนี้เราจะมาทำความรู้จักกับวิธีที่เรียกว่า Mutation Testing (การทดสอบโดยการจงใจสร้างบั๊ก) เพื่อหาจุดบอดเหล่านี้กัน

Mutation Testing คืออะไรและทำไมต้องใช้

Mutation Testing คือเทคนิคการทดสอบที่เราจะจงใจใส่บั๊กเข้าไปในโค้ดทีละจุด แล้วดูว่าบททดสอบของเรา "จับได้" หรือไม่ ถ้าเราใส่บั๊กไปแล้วบททดสอบยังผ่าน (Green) แสดงว่าชุดการทดสอบของเรามีปัญหา เพราะมันไม่สามารถตรวจจับความผิดพลาดพื้นฐานได้

เปรียบเทียบง่ายๆ เหมือนการตรวจสอบความปลอดภัยของประตูบ้าน ถ้าเราลองแอบเอาไขควงมางัดกลอนประตูแล้วประตูยังล็อคอยู่ แสดงว่าระบบรักษาความปลอดภัยเราใช้ได้ แต่ถ้าเรางัดจนประตูเปิดได้แล้วระบบเตือนภัยเงียบกริบ นั่นแปลว่าระบบตรวจจับของเรามีปัญหา

การทำแบบนี้ช่วยให้เรามั่นใจได้ว่าบททดสอบของเราไม่ได้แค่ "ผ่านไปวันๆ" แต่มันมีความสามารถในการตรวจจับบั๊กจริงๆ มันช่วยเปลี่ยนความรู้สึกมั่นใจจากการเดา ให้กลายเป็นตัวเลขวัดผลที่ชัดเจนว่าโค้ดของเราแข็งแกร่งแค่ไหน

ขั้นตอนการทำ Mutation Testing แบบมือโปร

การทดสอบแบบนี้มีขั้นตอนที่ไม่ซับซ้อนแต่ต้องทำอย่างเป็นระบบ เพื่อให้ได้ผลลัพธ์ที่น่าเชื่อถือที่สุด เริ่มจากตรวจสอบให้แน่ใจว่าชุดทดสอบเดิมของเราไม่ Flaky (การทดสอบที่ผลลัพธ์ไม่แน่นอน เดี๋ยวผ่านเดี๋ยวไม่ผ่าน) เพราะถ้าการทดสอบหลักเชื่อถือไม่ได้ ผลการทดสอบกลายพันธุ์ก็เชื่อถือไม่ได้เช่นกัน

  1. เลือกฟังก์ชันที่ต้องการทดสอบ
  2. สร้าง Mutant (โค้ดที่ถูกจงใจใส่บั๊กเข้าไป) โดยการเปลี่ยนตัวดำเนินการ เช่น เปลี่ยนเครื่องหมายบวกเป็นลบ หรือลบเงื่อนไขบางอย่างออก
  3. รันชุดทดสอบเดิมกับโค้ดที่แกล้งทำบั๊กไว้
  4. ถ้าบททดสอบล้มเหลว (Fail) แสดงว่าเรา "ฆ่า" ตัวกลายพันธุ์ได้สำเร็จ แต่ถ้ายังผ่าน (Pass) แสดงว่าตัวกลายพันธุ์นั้น "รอด" ซึ่งเราต้องกลับไปเพิ่มบททดสอบใหม่

จุดที่มือใหม่มักพลาดคือพยายามทดสอบทุกอย่างในครั้งเดียว ซึ่งจะทำให้งงมากว่าบั๊กมาจากตรงไหน ให้ลองทำทีละนิด เปลี่ยนแค่จุดเดียวแล้วทดสอบทันที จะช่วยให้เราเห็นภาพชัดเจนว่าโค้ดส่วนไหนที่เรายังไม่ได้เขียนบททดสอบไว้

ตัวอย่างการสร้างบั๊กเพื่อทดสอบ (Mutation)

สมมติเรามีคิว (ชุดข้อมูลที่เรียงลำดับ) ที่จำกัดขนาดไว้ เราจะลองจงใจลบคำสั่งที่ใช้จัดการการหมุนวนของข้อมูล เพื่อดูว่าบททดสอบจะจับได้ไหม

// โค้ดเดิมที่ทำงานถูกต้อง
tail_ = (tail_ + 1) % data_.size();

// โค้ดที่จงใจใส่บั๊ก (Mutation)
tail_ = (tail_ + 1); 
// ลบเครื่องหมาย % ออก ทำให้ tail_ เพิ่มขึ้นเรื่อยๆ จนเกินขนาดข้อมูล

ในตัวอย่างนี้ บรรทัดแรกคือการใช้ Modulo (เครื่องหมายหารเอาเศษ) เพื่อให้ค่าวนกลับมาที่จุดเริ่มต้นของคิว ส่วนบรรทัดที่สองเราลบมันออก ถ้าบททดสอบของเราไม่เคยทดสอบกรณีที่คิวเต็มจนต้องวนรอบ มันก็จะมองว่าโค้ดที่บั๊กนี้ "ทำงานผ่าน" ซึ่งถือว่าอันตรายมาก

ผลลัพธ์ที่ควรเห็นคือ ถ้าบททดสอบเราฉลาดพอ มันจะต้องแจ้งเตือนว่าเกิดข้อผิดพลาดในการเข้าถึงหน่วยความจำ (Memory Error) แต่ถ้ามันไม่แจ้งอะไรเลย นั่นคือสัญญาณเตือนว่าเราต้องเขียนบททดสอบเพิ่มตรงกรณีที่คิวเต็มให้มากขึ้น

เมื่อบททดสอบ "ตาบอด" เราต้องทำอย่างไร

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

วิธีแก้คือการเขียน Regression Test (การทดสอบเพื่อป้องกันไม่ให้บั๊กเดิมกลับมาอีก) เพิ่มเข้าไปในกรณีที่ตัวกลายพันธุ์นั้นรอดไปได้ เช่น ถ้าเราลืมทดสอบเรื่องการวนรอบของคิว เราก็ต้องเขียนบททดสอบที่บังคับให้คิวเต็มและทำการวนรอบจริงๆ เพื่อให้แน่ใจว่าโค้ดจัดการได้ถูกต้อง

จำไว้ว่า Sanitizers (เครื่องมือตรวจจับข้อผิดพลาดขณะรันโปรแกรม) อย่าง ASan หรือ UBSan จะทำงานได้ก็ต่อเมื่อบททดสอบวิ่งผ่านโค้ดส่วนนั้น ถ้าเราไม่เขียนบททดสอบให้ครอบคลุม เครื่องมือเหล่านี้ก็ช่วยอะไรไม่ได้เลย เพราะมันไม่ใช่คนเขียนโค้ดแทนเรา

สรุป: เปลี่ยนความมั่นใจให้เป็นตัวเลข

Mutation Testing ไม่ใช่เรื่องที่ต้องทำในทุกบรรทัดของโค้ด เพราะมันมีต้นทุนเรื่องเวลาในการรันที่สูง แต่มันมีประโยชน์มากกับโค้ดส่วนสำคัญ (Core Logic) ที่ถ้าพังแล้วจะส่งผลกระทบต่อระบบใหญ่ทั้งหมด

สรุปสั้นๆ สำหรับคนที่กำลังฝึกเขียนโค้ด:

  1. อย่าเชื่อมั่นในแถบสีเขียว 100%
  2. ลองจงใจแก้โค้ดให้พังดูบ้าง เพื่อดูว่าบททดสอบแจ้งเตือนไหม
  3. ถ้าบททดสอบไม่เตือน นั่นคือจุดที่คุณต้องเพิ่มบททดสอบเข้าไป

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


ที่มา: The Agent's Tests Passed. Mutation Testing Showed 2 of 4 Faults Survived. — DEV Community

แชร์บทความ

Facebook X LINE

บทความที่เกี่ยวข้อง

จัดการเซิร์ฟเวอร์ผ่าน VS Code ให้ง่ายขึ้นด้วย Easy SSH พร้อมฟีเจอร์โหลดไฟล์ผ่านคลิกเดียว

จัดการเซิร์ฟเวอร์ผ่าน VS Code ให้ง่ายขึ้นด้วย Easy SSH พร้อมฟีเจอร์โหลดไฟล์ผ่านคลิกเดียว

เบื่อไหมที่ต้องสลับหน้าจอไปมาเพื่อจัดการเซิร์ฟเวอร์? มาลองใช้ Easy SSH ปลั๊กอิน VS Code ที่ช่วยให้คุณรีโมทผ่าน Terminal ได้สะดวก แถมโหลดไฟล์ได้ง่ายแค่กด Ctrl+click

ที่มา: DEV Community

3 hours ago 11 นาที
3 views
วิธีดึงข้อมูลราคาจาก Google Hotels ด้วย API สำหรับนักพัฒนา

วิธีดึงข้อมูลราคาจาก Google Hotels ด้วย API สำหรับนักพัฒนา

อยากทำแอปท่องเที่ยวแต่ดึงข้อมูลราคาจาก Google Hotels ไม่ได้? มาดูวิธีใช้ Apify Actor ช่วยดึงข้อมูลแบบอัตโนมัติด้วย Python ง่ายๆ ไม่ต้องกลัวเว็บพัง

ที่มา: DEV Community

6 hours ago 8 นาที
4 views
วิธีเช็กความพร้อมโปรเจกต์ก่อนปล่อยงานจริงด้วย ReleaseReady

วิธีเช็กความพร้อมโปรเจกต์ก่อนปล่อยงานจริงด้วย ReleaseReady

เคยไหม? โค้ดรันได้ในเครื่องแต่พอปล่อยจริงกลับพัง! มาดูวิธีตรวจสอบความพร้อมของโปรเจกต์ก่อนอัปขึ้น GitHub ด้วยเครื่องมือ ReleaseReady กัน

ที่มา: DEV Community

10 hours ago 9 นาที
5 views