Flaky Test คืออะไร?
Flaky Test คือการทดสอบที่ได้ผลการทดสอบไม่สม่ำเสมอ(flakiness) บางครั้ง Pass บางครั้ง Fail ส่วนใหญ่จะเกิดจากการใช้คำสั่ง Waiting หรือ Delay ไม่ถูกต้อง, ขนาดของ Test Code ที่ใหญ่เกินไปและอื่นๆอีกหลายสาเหตุ
สาเหตุของ Flaky Test
ก่อนจะแก้ไขปัญกาเราต้องเข้าใจว่า Flaky Test เกิดจากอะไร โดยสาเหตุที่จะทำให้เกิด Falky Test นั้นอาจมาจาก
-
Timing Issues: การทดสอบนั้นอาจมี Latency บน Network ซึ่งจะทำให้ไม่สามารถ Load UI element ขึ้นมาได้ทัน ซึ่งจะทำให้บางครั้งผ่าน บางครั้งไม่ผ่าน
-
Concurrency และ Parallelism: การทดสอบอาจมี Dependency ซึ่งจะทำให้กดารทดสอบที่ทำงานแบบ Parallel ได้ผลลัพธ์ออกมาไม่แน่นอน
-
External Dependencies: การทดสอบที่เรียกใช้งาน Services หรือ Resources ต่างๆภายนอก เช่น Database หรือ API ซึ่งถ้า Database หรือ API เหล่านั้นหยุดทำงานหรือทำงานช้าลงก็จะทำให้การทดสอบนั้นได้ผลลัพธ์ที่ไม่แน่นอนตามไปด้วย ยกตัวอย่างเช่น การทดสอบ Web application ในช่วงเวลาทำงานอาจเกิด Timeout เนื่องจาก Database ทำงานช้าลง ในขณะที่การทดสอบช่วงเย็นหลังเลิกงานเป็นไปอย่างราบรื่น
-
Race Conditions: การทดสอบแบบขนานที่มีการแตก Threads หรือ Processes ออกมาแล้วมีการเข้าถึง Resources ตัวเดียวกัน จะเกิดปัญหา Race Condition เกิดขึ้น ส่วนใหญ่จะเกิดจากการ Run test พร้อมๆกันหลายตัว ซึ่งผลลัพธ์อาจทำให้ได้ผลลัพธ์ของการทดสอบไม่สม่ำเสมอหรือถึงขั้นเกิด Deadlocks และหยุดทำงานไปเลยก็ได้
-
Data Fluctuations: การทดสอบที่มีการ Generate ข้อมูลขึ้นมาแบบ Dynamic เช่นการใช้งาน Timestamp, การ Random ตัวเลข อาจทำให้ผลลัพธ์ออกมาแตกต่างกันได้
-
Infrastructure: การทดสอบใน Environment หรือ Infrastructure ที่แตกต่างกันก็จะให้ผลของการทดสอบที่แตกต่างกันตามไปด้วย ยกตัวอย่างเช่น การทดสอบ Mobile Application บน Android และ iOS หรือการทดสอบ Web Application บน Chrome หรือ Firefox ไม่เว้นแม้กระทั่งเป็น Environment เดียวกันแต่คนละ Version ก็จะมีผลเช่นเดียวกัน ยกตัวอย่างเช่น การทดสอบ Mobile Application บน iOS version 17 และ version 18
ผลเสียของ Flaky Test
ในเมื่อปัญหาของ Flaky Test คือบางครั้งรันได้ บางครั้งรันไม่ได้ แล้วมันจะส่งผลเสียยังไง ในเมื่อแค่รันใหม่เดี๋ยวมันก็ผ่าน ซึ่งสิ่งที่เกิดผลกระทบจากการเกิด Flaky Test มีดังนี้
- Build Pipeline เราไม่สามารถนำ Test Code ที่มี Flaky Test ไปใส่ไว้ใน CI/CD Pipeline ได้เนื่องจากจะทำให้ Build ผ่านบ้าง ไม่ผ่านบ้าง และถ้าเรารัน CI/CD แบบ On-Cloud การที่มี Flaky Test จะทำให้สิ้นเปลือง Compute Minutes ซึ่งจะทำให้เราเสียค่าใช้จ่ายมากขึ้นโดยไม่จำเป็น
- Tester ถ้าเรารันโปรแกรมแล้วรันได้บ้างไม่ได้บ้าง เราก็คงไม่อยากจะใช้โปรแกรมตัวนั้น เช่นเดียวกับการทดสอบถ้าเรารัน Test แล้วผ่านบ้างไม่ผ่านบ้าง เราก็คงจะไม่อยากรัน Test Code นั้นอีกแล้ว และแน่นอนว่าคงจะไม่มีใครอยากจะเข้าไปแก้ไข Test Code นี้แน่นอน
วิธีการแก้ไข Flaky Test
การแก้ไข Flaky Test นั้นจำเป็นต้องอาศัยความเข้าใจ เราต้องหาว่า Flaky Test ที่เกิดขึ้นนี้มีสาเหตุ(Root cause) มาจากอะไร แล้วทำการแก้ไข ซึ่งถ้ายังไม่รู้ว่าปัญหาคืออะไรให้ลอง Run test หลายๆรอบและหาความแตกต่างของผลลัพธ์ในแต่ละรอบ เช่นเวลาที่ใช้, Request และ Response ที่เกิดขึ้นในแต่ละขั้นตอนรวมทั้ง Resources(CPUs, Memory) ที่ใช้ระหว่างการทดสอบ
ในส่วนของ Test Code เราสามารถแก้ไข Falky Test ได้ด้วยวิธีต่างๆ ดังนี้
-
Waiting ใช้การ Delay เข้าช่วย ซึ่งต้องระวังไม่ให้การทดสอบมีการ Delay มากเกินไป เพราะจะยิ่งใช้เวลาในการทดสอบนานขึ้น
-
Locators การใช้งาน Locators ที่ระบุ UI Element ที่แน่นอน เช่นอาจระบุถึง UI element โดยใช้ ID หรือใช้ CSS Selector ที่ชัดเจนมากขึ้นด้วยการระบุ CSS Class หรือ Attribute Name ที่ชัดเจนลงไป ไม่ควรใช้ลำดับของ UI Elements เพียงอย่างเดียว
-
Minimize Test Code ในหลายกรณี Flaky Test อาจเกิดจากขนาดของ Test ที่ใหญ่เกินไปโดยเฉพาะการทำ E2E Test ซึ่งเราควรจะลดขนาดของ Test ลงด้วยการย้าย Test cases ต่างๆไปไว้ในการทดสอบใน Layer ด้านล่าง เช่น Integration Test หรือ Unit Test แทน
-
Retry mechanisms เพิ่มการ Retry หรือ Auto-wait เข้าไปเพื่อให้ Test Code มีความถูกต้องมากขึ้น ซึ่งในกรณีนี้เราอาจเลือก Test Framework ที่เข้ามาช่วยแก้ปัญหานี้ได้ เช่น เลือกใช้ Cypress หรือ Playwright ที่มี Auto-wait จะช่วยลดปัญหาต่างๆเหล่านี้ลงได้ และถ้าเครื่องมือตัวไหนที่สามารถกำหนดให้ Auto retry ได้ก็ให้เปิดการใช้ Auto retry ทันที
ถ้าเครื่องมือที่นำมาใช้ไม่มี Automatic Retry การเลือกใช้ Explicit Wait จะดีกว่าการใช้ Implicit Wait
ในบาง Testing Framework เราจำเป็นต้องเขียน Code สำหรับการ Retry เองยกตัวอย่างเช่น ใน JUnit เราต้องเขียน Code เพื่อสร้าง Retry เองด้วยการใช้
@RepeatedfTest
แบบนี้public class MyRepeatedTest { @RepeatedTest(3) // Run test method นี้ซ้ำกัน 3 ครั้ง void repeatedTest() { // Test Code } }
ตัวอย่าง Flaky Test และการแก้ไข
ตัวอย่าง Flaky Test บน Cypress
ตัวอย่างนี้เป็น Flaky Test ที่เกิดจากการเขียน Test Code ที่ไม่ถูกต้อง ซึ่งถ้าเราใช้ cy.wait
เราสามารถเลือกใส่ parameters ได้ 2 แบบคือ times และ alias
ในกรณีนี้ถ้าเราเลือกใช้การ delay ด้วยการระบุเวลาอาจทำให้เกิด Flaky Test ได้ เนื่องจากเวลาที่ใช้ Load UI Elements อาจเกิน 500 มิลลิวินาที ซึ่งบางครั้ง Load ทันบางครั้ง Load ไม่ทัน
// Delay 500 millisec
cy.wait(500)
วิธีการแก้ไข Flaky Test บน Cypress
เราสามารถแก้ไขปัญหานี้ได้ด้วยการเปลี่ยนจากการ Delay ด้วยเวลา มาเป็นการ Delay ด้วยการรอให้ได้ผลลัพธ์ครบถ้วนก่อน แล้วค่อยทำงานในบรรทัดต่อไป ซึ่งเราจะเปลี่ยนมาใช้ Test Code แบบนี้แทน
// รอให้ 'getAccount' to respond
cy.intercept('/accounts/*').as('getAccount')
cy.visit('/accounts/123')
cy.wait('@getAccount').then((interception) => {
// เราสามารถเข้าถึง request body, response body, http status
// และรายละเอียดอื่นๆของ request และ response ได้
})
ตัวอย่าง Flaky Test บน Selenium
อีกตัวอย่างนึงสำหรับ Selenium ถ้าเราเลือกใช้งาน Implicit Waits ด้วย Code แบบนี้
# Delay 2 Seconds
driver.implicitly_wait(2)
ในกรณีนี้ก็จะเกิดปัญหาแบบเดียวกับ Cypress คือถ้าเกิน 2 วินาทีไปแล้ว Selenium ก็จะไปทำงานบรรทัดต่อไปทันทีไม่ว่า UI Elements จะพร้อมใช้งานหรือไม่ก็ตาม ซึ่งค่า default ของ implicitly_wait จะเป็น 0 นั่นหมายความว่า Selenium จะเข้าไปค้นหา Web elements ด้วยคำสั่ง find_element ทันที ซึ่งจะทำให้เกิด error เพราะ Web elements นั้นจะ load ไม่ทัน ไม่เหมือนกับใน Cypress ที่จะมี Auto-wait
วิธีการแก้ไข Flaky Test บน Selenium
เราสามารถแก้ไขปัญหานี้ได้ด้วยการใช้งาน Explicit Wait ซึ่ง Selenium จะทำการ delay การทำงานพร้อมกับการตรวจสอบ element ที่เราค้นหา โดยจะวน loop ไปเรื่อยๆจนกว่า element ที่เราต้องการจะถูก load ขึ้นมา ตัวอย่างของ Explicit Wait จะเป็นดังนี้
|
|
ในตัวอย่างนี้ Code หลังจากบรรทัด wait.until() จะถูก run ก็ต่อเมื่อปุ่มที่มี ID เป็น btnSubmit ถูก load ขึ้นมา
หวังว่าบทความนี้จะทำให้ผู้อ่านเข้าใจ Flaky Test มากขึ้นซึ่งปัญหาส่วนใหญ่เกิดจากการที่เรายังไม่เข้าใจปัญหา ดังนั้นวิธีการแก้ไขคือการเรียนรู้และทำความเข้าใจการทำงานของเครื่องมือแต่ละตัวอย่างลึกซึ้ง
ตัวอย่างของ Flaky Test จากลำดับของ Test
ในบางกรณี Flaky Test อาจเกิดจากการเขียน Test Code ที่ไม่เป็นอิสระ(การทดสอบที่ขึ้นกับ Test Case อื่นๆ) ยกตัวอย่างเช่น
|
|
ในกรณีนี้เราจะไม่สามารถสลับลำดับของ Test case 2 กรณีนี้ได้เนื่องจากถ้าเราจะต้องกดปุ่ม Unsubscribe ในบรรทัดที่ 16 เราจะต้องกดปุ่ม Subscribe ในบรรทัดที่ 7 ก่อน นั่นจึงทำให้เราไม่สามารถสลับลำดับของ Test case 2 ข้อนี้ได้ ถ้าเรานำ Test Code ไปรันแบบ Parallel เราจะไม่สามารถกำหนดลำดับของการทดสอบที่แน่นอนได้ นั่นจึงทำให้เกิด Error ขึ้นในบางครั้ง(Flakiness) ที่รัน Test case ที่ 2 ก่อน Test Case แรก