การทดสอบแบบสุ่ม (Random Testing)
สรุปใจความสำคัญ
- การทดสอบแบบสุ่มเป็นเทคนิค Black-box testing ที่ใช้ข้อมูลนำเข้าแบบสุ่มเพื่อค้นหา Bug
- ช่วยลดความลำเอียง (Bias) ของผู้ทดสอบที่เป็นมนุษย์
- ต้องการจำนวนการทดสอบจำนวนมากเพื่อให้ได้ระดับความเชื่อมั่นทางสถิติที่สูง
- มีเครื่องมือยอดนิยม เช่น QuickCheck และ Randoop ที่ช่วยสร้างกรณีทดสอบอัตโนมัติ
การทดสอบแบบสุ่ม (Random Testing) คือเทคนิคการทดสอบซอฟต์แวร์แบบกล่องดำ (Black-box testing) ซึ่งใช้วิธีการสร้างข้อมูลนำเข้า (Input) แบบสุ่มและเป็นอิสระต่อกัน เพื่อนำมาทดสอบการทำงานของโปรแกรม จากนั้นจะนำผลลัพธ์ที่ได้ไปเปรียบเทียบกับข้อกำหนดของซอฟต์แวร์ (Software Specifications) เพื่อตรวจสอบว่าผลลัพธ์นั้นถูกต้อง (Pass) หรือผิดพลาด (Fail)
ในกรณีที่ซอฟต์แวร์ไม่มีข้อกำหนดที่ชัดเจน ผู้ทดสอบมักจะใช้การดักจับข้อผิดพลาดของภาษาโปรแกรม (Language Exceptions) แทน ซึ่งหมายความว่าหากเกิด Exception ขึ้นในระหว่างการรันการทดสอบ จะถือว่าโปรแกรมมีข้อผิดพลาด (Fault) นอกจากนี้ การทดสอบแบบสุ่มยังถูกนำมาใช้เพื่อหลีกเลี่ยงความลำเอียง (Bias) ที่อาจเกิดขึ้นจากการเลือกข้อมูลทดสอบโดยมนุษย์
ประวัติของการทดสอบแบบสุ่ม
การทดสอบแบบสุ่มถูกนำมาใช้ครั้งแรกในด้านฮาร์ดแวร์ โดย Melvin Breuer ได้ทำการศึกษาในปี 1971 ต่อมาในปี 1975 Pratima และ Vishwani Agrawal ได้ประเมินประสิทธิภาพของการทดสอบรูปแบบนี้ ส่วนในด้านซอฟต์แวร์ Duran และ Ntafos ได้เริ่มศึกษาการทดสอบแบบสุ่มในปี 1984
Howden ได้นำเสนอการใช้การทดสอบสมมติฐาน (Hypothesis Testing) มาเป็นฐานทางทฤษฎีสำหรับการทดสอบแบบสุ่มในหนังสือ Functional Testing and Analysis โดยได้พัฒนาสูตรคำนวณจำนวนการทดสอบ (n) ที่จำเป็นเพื่อให้มีความเชื่อมั่น (Confidence) อย่างน้อย 1-1/n ในอัตราความล้มเหลวที่ไม่เกิน 1/n ซึ่งสูตรขอบเขตล่าง n log n ชี้ให้เห็นว่าต้องมีการทดสอบที่ปราศจากความล้มเหลวเป็นจำนวนมากเพื่อให้ได้ระดับความเชื่อมั่นที่น่าพอใจ
หลักการทำงานและตัวอย่าง
ในการทดสอบแบบสุ่ม ข้อมูลนำเข้าจะถูกสร้างขึ้นโดยไม่มีรูปแบบที่ตายตัว ตัวอย่างเช่น หากมีฟังก์ชันในภาษา C++ ที่มีข้อผิดพลาดแฝงอยู่ การสุ่มค่าตัวเลข เช่น {123, 36, -35, 48, 0} อาจมีเพียงค่า '-35' เท่านั้นที่ทำให้เกิดข้อผิดพลาด (Bug) ซึ่งหากไม่มีการเปรียบเทียบกับผลลัพธ์อ้างอิง (Reference Implementation) ข้อผิดพลาดนี้อาจไม่ถูกตรวจพบ
การใช้ผลลัพธ์อ้างอิงมักเกิดขึ้นเมื่อมีการเขียนอัลกอริทึมที่ซับซ้อนเพื่อเพิ่มประสิทธิภาพ (Performance) โดยนำผลลัพธ์ไปเปรียบเทียบกับอัลกอริทึมมาตรฐานที่เรียบง่ายกว่าแต่ทำงานช้ากว่า เพื่อยืนยันความถูกต้องของผลลัพธ์
การทดสอบในภาษาเชิงวัตถุ (Object-Oriented Languages)
สำหรับภาษาโปรแกรมเชิงวัตถุ เครื่องมือทดสอบมักจะสำรวจโครงสร้างของโปรแกรมเพื่อหาตัวสร้างวัตถุ (Constructors) หรือเมธอดที่ส่งคืนวัตถุ จากนั้นจะเรียกใช้เมธอดเหล่านั้นด้วยข้อมูลนำเข้าแบบสุ่ม โดยมีการจัดการกลุ่มวัตถุที่สร้างขึ้น (Pool of objects) และใช้หลักความน่าจะเป็นในการเลือกว่าจะสร้างวัตถุใหม่หรือนำวัตถุเดิมที่มีอยู่แล้วกลับมาใช้ซ้ำ
จุดแข็งและจุดอ่อน
จุดแข็ง
- ต้นทุนต่ำ: ไม่จำเป็นต้องมีความรู้เชิงลึกเกี่ยวกับโครงสร้างภายในของโปรแกรมที่ถูกทดสอบ
- ลดความลำเอียง: ต่างจากการทดสอบโดยมนุษย์ที่อาจมองข้ามจุดบกพร่องเนื่องจากความเชื่อมั่นในโค้ดบางส่วน
- ค้นหาข้อผิดพลาดเบื้องต้นได้รวดเร็ว: สามารถรันเซสชันการทดสอบจำนวนมากได้ในเวลาอันสั้น
- มีประสิทธิภาพเมื่อมีข้อกำหนดชัดเจน: หากซอฟต์แวร์มีข้อกำหนดที่แม่นยำ การทดสอบแบบสุ่มจะสามารถระบุข้อผิดพลาดที่เกิดขึ้นจริงได้อย่างมีประสิทธิภาพ
จุดอ่อน
- ตรวจพบเฉพาะข้อผิดพลาดพื้นฐาน: มักพบข้อผิดพลาดประเภท Null Pointer Dereferencing แต่ยากที่จะพบข้อผิดพลาดที่ซับซ้อน
- ขึ้นอยู่กับความแม่นยำของข้อกำหนด: หากข้อกำหนดของซอฟต์แวร์ไม่ชัดเจน การตัดสินว่าผลลัพธ์ถูกต้องหรือไม่ย่อมทำได้ยาก
- ประสิทธิภาพต่ำกว่าเทคนิคอื่น: เมื่อเทียบกับการวิเคราะห์โปรแกรมแบบสถิต (Static Program Analysis) การทดสอบแบบสุ่มอาจให้ผลลัพธ์ที่ด้อยกว่าในบางกรณี
- ปัญหาในการทำ Continuous Integration (CI): หากการสุ่มข้อมูลนำเข้าเปลี่ยนไปในทุกครั้งที่รัน อาจทำให้การทดสอบผ่านหรือตกแบบสุ่ม (Flaky Tests) ซึ่งสร้างความสับสนในกระบวนการ CI
- ต้องการจำนวนการทดสอบมหาศาล: เพื่อให้ได้ระดับความเชื่อมั่นทางสถิติที่สูง จำเป็นต้องรันการทดสอบจำนวนมาก (เช่น ต้องรัน 459 ครั้งโดยไม่พบข้อผิดพลาด เพื่อให้มั่นใจ 99% ว่าโอกาสเกิดความล้มเหลวต่ำกว่า 1/100)
ประเภทของการทดสอบแบบสุ่ม
แบ่งตามข้อมูลนำเข้า
- การสร้างลำดับข้อมูลนำเข้าแบบสุ่ม (Random Input Sequence Generation): เช่น การสุ่มลำดับการเรียกใช้เมธอด
- การสุ่มลำดับข้อมูล (Random Sequence of Data Inputs): หรือที่เรียกว่า Stochastic Testing
- การเลือกข้อมูลแบบสุ่มจากฐานข้อมูล (Random Data Selection): การดึงข้อมูลที่มีอยู่จริงในฐานข้อมูลมาใช้ทดสอบ
แบ่งตามการนำทาง (Guidance)
- การทดสอบแบบไม่นำทาง (Undirected Random Testing): การสุ่มโดยไม่มีการใช้ฮิวริสติก (Heuristics) เพื่อนำทาง
- การทดสอบแบบนำทาง (Directed Random Testing): การใช้ข้อมูลป้อนกลับ (Feedback) หรือการปรับตัว (Adaptive) เพื่อนำทางให้การสุ่มเข้าถึงส่วนของโปรแกรมที่ยังไม่ถูกทดสอบ
เครื่องมือที่ใช้ในการทดสอบแบบสุ่ม
| เครื่องมือ | รายละเอียด |
|---|---|
| QuickCheck | เครื่องมือยอดนิยมดั้งเดิมจากภาษา Haskell ถูกพอร์ตไปยังหลายภาษา ใช้การสร้างลำดับการเรียก API แบบสุ่มตามแบบจำลองเพื่อตรวจสอบคุณสมบัติของระบบ |
| Randoop | สร้างลำดับการเรียกเมธอดและตัวสร้างวัตถุสำหรับคลาสที่ทดสอบ และสร้างเป็น JUnit tests |
| Simulant | เครื่องมือสำหรับภาษา Clojure ที่จำลองพฤติกรรมของผู้ใช้ตามแบบจำลองทางสถิติ |
| AutoTest | เครื่องมือใน EiffelStudio ที่ทดสอบโค้ด Eiffel โดยอัตโนมัติโดยใช้สัญญา (Contracts) |
| YETI | เครื่องมือที่ไม่ยึดติดกับภาษา (Language Agnostic) รองรับ Java, .NET, C และอื่นๆ |
| GramTest | เครื่องมือทดสอบแบบสุ่มที่ใช้ไวยากรณ์ (Grammar-based) เขียนด้วย Java โดยใช้รูปแบบ BNF |
ข้อวิจารณ์และข้อจำกัด
ในทางปฏิบัติ การทดสอบแบบสุ่มมีบทบาทในวงจำกัด เนื่องจากปัญหาหลักคือการขาด Test Oracle (เครื่องมือหรือวิธีการที่ใช้ตรวจสอบว่าผลลัพธ์ที่ได้ถูกต้องตามข้อกำหนดหรือไม่) นอกจากนี้ยังมีปัญหาเรื่อง Operational Profile หรือความรู้เกี่ยวกับรูปแบบการใช้งานจริงของผู้ใช้ ซึ่งทำให้การสุ่มข้อมูลนำเข้าแบบสุ่มทั่วไปอาจไม่สะท้อนถึงการใช้งานจริงในโลกภายนอก
คำถามที่พบบ่อย
การทดสอบแบบสุ่มแตกต่างจากการทดสอบแบบปกติอย่างไร?
การทดสอบแบบปกติมักใช้กรณีทดสอบ (Test Case) ที่มนุษย์ออกแบบมาโดยเฉพาะ แต่การทดสอบแบบสุ่มจะสร้างข้อมูลนำเข้าแบบสุ่มโดยไม่มีรูปแบบ เพื่อค้นหาข้อผิดพลาดที่ผู้พัฒนาอาจคาดไม่ถึง
Test Oracle คืออะไรในบริบทของการทดสอบแบบสุ่ม?
Test Oracle คือกลไกที่ใช้ตัดสินว่าผลลัพธ์ที่ได้จากการรันการทดสอบแบบสุ่มนั้น 'ถูกต้อง' หรือ 'ผิดพลาด' เช่น การเปรียบเทียบกับผลลัพธ์จากโปรแกรมอ้างอิง หรือการตรวจสอบว่าโปรแกรมเกิด Exception หรือไม่
ทำไมการทดสอบแบบสุ่มถึงอาจเป็นปัญหาสำหรับ Continuous Integration (CI)?
เพราะหากข้อมูลนำเข้าถูกสุ่มใหม่ทุกครั้งที่รัน การทดสอบที่เคยผ่านในครั้งหนึ่งอาจจะตกในครั้งถัดไป (Flaky Tests) แม้ว่าโค้ดจะไม่มีการเปลี่ยนแปลง ซึ่งทำให้การติดตามข้อผิดพลาดทำได้ยากขึ้น
เมื่อใดควรใช้การทดสอบแบบสุ่ม?
ควรใช้เมื่อต้องการค้นหา Bug พื้นฐานอย่างรวดเร็ว หรือเมื่อต้องการให้แน่ใจว่าโปรแกรมจะไม่แครช (Crash) เมื่อได้รับข้อมูลนำเข้าที่แปลกประหลาด ซึ่งผู้ทดสอบอาจนึกไม่ถึงในการออกแบบ Test Case แบบปกติ