จัดการ Flaky Tests: คู่มือปฏิบัติเพื่อการทดสอบที่เสถียร

8 นาที 21 views บันทึกเป็น PDF
จัดการ Flaky Tests: คู่มือปฏิบัติเพื่อการทดสอบที่เสถียร

เรียนรู้วิธีรับมือกับ Flaky Tests ปัญหาการทดสอบอัตโนมัติที่ไม่แน่นอน ด้วยเทคนิค Test Isolation, Selector ที่เหมาะสม และการใช้ Retry อย่างถูกวิธี เพื่อให้ระบบ CI/CD ของคุณน่าเชื่อถือ

ทำความรู้จักกับ Flaky Tests: ปีศาจร้ายในระบบทดสอบอัตโนมัติ

ในฐานะนักพัฒนาซอฟต์แวร์มือใหม่ คุณอาจเคยเจอปัญหาที่ว่า รันโค้ดชุดเดิมเป๊ะๆ แต่ผลลัพธ์กลับไม่เหมือนเดิม บางครั้งรันผ่าน บางครั้งรันไม่ผ่าน ทั้งที่ไม่ได้แก้โค้ดอะไรเลย สถานการณ์นี้เรียกว่า Flaky Tests หรือการทดสอบที่ไม่มีความเสถียร ซึ่งเป็นปัญหาใหญ่ที่บั่นทอนความมั่นใจในระบบ CI/CD (Continuous Integration/Continuous Deployment) หรือกระบวนการส่งมอบซอฟต์แวร์อัตโนมัติของเรา

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

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

หัวใจสำคัญของการเขียน Test ให้เสถียร: เริ่มจากความโดดเดี่ยว

เทคนิคแรกที่ต้องจำให้ขึ้นใจคือ Test Isolation หรือการทำให้การทดสอบแต่ละตัวแยกจากกันโดยเด็ดขาด Stateless (การที่โปรแกรมไม่จดจำสถานะเดิม) คือเป้าหมายสูงสุด เพราะถ้าการทดสอบตัวที่ 1 ไปสร้างไฟล์ทิ้งไว้ แล้วการทดสอบตัวที่ 2 มาอ่านไฟล์นั้นซ้ำ ผลลัพธ์อาจจะเพี้ยนได้ทันทีหากลำดับการรันเปลี่ยนไป

คุณต้องมั่นใจว่าก่อนเริ่มการทดสอบแต่ละครั้ง ระบบจะถูกล้างให้สะอาดเหมือนเพิ่งเปิดเครื่องใหม่ คุณอาจต้องเคลียร์ฐานข้อมูล (Database), ลบไฟล์ที่ดาวน์โหลดมา หรือล้างค่า localStorage (พื้นที่เก็บข้อมูลในเบราว์เซอร์) ออกให้หมด เพื่อให้มั่นใจว่าทุกครั้งที่รัน Automated Tests มันจะเริ่มจากจุดสตาร์ทเดียวกันเสมอ

ตัวอย่างการล้างข้อมูลใน Playwright (เครื่องมือทดสอบเว็บยอดนิยม) เพื่อให้แน่ใจว่าการทดสอบแต่ละครั้งเป็นอิสระต่อกัน:

// ใช้ beforeEach เพื่อล้างค่าก่อนเริ่มทดสอบทุกครั้ง
test.beforeEach(async ({ page }) => {
  // ล้างข้อมูลใน localStorage เพื่อป้องกันผลกระทบจากเทสต์อื่น
  await page.evaluate(() => window.localStorage.clear());
  // ไปยังหน้าเริ่มต้นใหม่เสมอ
  await page.goto('https://myapp.com/login');
});

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

เลือกใช้ Selector ให้ฉลาด: อย่าพึ่งพาความเปราะบาง

บ่อยครั้งที่มือใหม่เลือกใช้ CSS Selector ที่ผูกติดกับโครงสร้างหน้าเว็บ เช่น div > ul > li:nth-child(2) > a ซึ่งถ้าวันหนึ่งดีไซน์เนอร์เปลี่ยนโครงสร้างหน้าเว็บนิดเดียว เทสต์ของคุณก็จะพังทันที นี่คือสาเหตุอันดับต้นๆ ของความไม่เสถียรที่เราเรียกว่า Fragile Selectors

วิธีแก้ที่ดีที่สุดคือการสร้าง Dedicated Test Attributes หรือการสร้างแอตทริบิวต์พิเศษไว้ให้เทสต์โดยเฉพาะ เช่นการเติม data-test-id เข้าไปใน HTML ของเรา วิธีนี้จะแยกส่วนของการแสดงผล (UI) ออกจากการทดสอบ (Testing) ทำให้เมื่อคุณแก้ดีไซน์ เทสต์ก็ยังคงทำงานได้ถูกต้องเหมือนเดิม

เปรียบเทียบระหว่างวิธีที่พังง่าย กับวิธีที่แนะนำให้ใช้:

  1. แบบที่พังง่าย: ใช้ button.btn-primary (ถ้าเปลี่ยนสีปุ่มหรือเปลี่ยนคลาส เทสต์จะพัง)
  2. แบบที่แนะนำ: ใช้ [data-test-id='login-submit-button'] (ไม่ว่าปุ่มจะเปลี่ยนสีหรือย้ายตำแหน่งไปแค่ไหน เทสต์ก็ยังหาปุ่มเจอ)

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

กลยุทธ์การ Retry: เครื่องมือช่วยชีวิตไม่ใช่ทางแก้ปัญหา

การทำ Retry Policy หรือการสั่งให้ระบบลองรันซ้ำเมื่อเทสต์พัง เป็นดาบสองคมที่มือใหม่ต้องระวัง การรันซ้ำ (Retry) มีประโยชน์ในกรณีที่เกิดเหตุการณ์ชั่วคราว เช่น เน็ตหลุดหรือ Server ตอบสนองช้า แต่หากคุณใช้มันเพื่อกลบเกลื่อนโค้ดที่บั๊กจริงๆ มันจะกลายเป็นการซ่อนปัญหาไว้ใต้พรมแทน

กฎเหล็กคือให้ตั้งค่า Auto-retries ไว้ต่ำเพียง 1-2 ครั้งเท่านั้น และต้องมีการเก็บ Log (บันทึกการทำงาน) ทุกครั้งที่มีการ Retry เพื่อให้คุณสามารถย้อนกลับมาตรวจสอบได้ว่า ทำไมเทสต์ตัวนี้ถึงต้องรันซ้ำบ่อยกว่าตัวอื่น ซึ่งบ่อยครั้งมันคือสัญญาณเตือนว่ามีปัญหาเชิงโครงสร้างรอให้คุณเข้าไปแก้

การตั้งค่า Retry ใน playwright.config.ts แบบที่เหมาะสม:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  // รันซ้ำสูงสุดแค่ 2 ครั้งถ้าเทสต์พัง
  retries: 2, 
  // การรันแบบขนาน (Parallelism) เริ่มต้นให้จำกัดไว้ที่ 2 ก่อน
  workers: 2, 
});

การตั้ง workers: 2 คือการจำกัดให้ระบบรันเทสต์พร้อมกันเพียง 2 ตัว เพื่อป้องกันไม่ให้คอมพิวเตอร์หรือ Server ของคุณทำงานหนักจนเกินไป (Resource Exhaustion) ซึ่งเป็นสาเหตุหนึ่งที่ทำให้เทสต์พังเพราะเครื่องรันไม่ไหวครับ

การวัดผลและเกณฑ์ความเสถียร: รู้ได้อย่างไรว่าเทสต์เรา "ป่วย"

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

นี่คือตารางการประเมินสถานะที่คุณนำไปใช้ได้จริง:

  • Acceptable (< 1%): ระบบปกติ เทสต์มีความน่าเชื่อถือสูง
  • Warning (1% – 5%): เริ่มมีความไม่เสถียร ต้องรีบเข้าไปตรวจสอบหาสาเหตุ
  • Critical (> 5%): ระบบพังหนัก ต้องหยุดการ Deploy ทันทีจนกว่าจะแก้เทสต์ให้เสถียร

การมี Dashboard หรือรายงานสรุปผล (เช่น Allure Report) ที่แสดงจำนวนครั้งที่รันผ่านและจำนวนครั้งที่ต้อง Retry จะช่วยให้คุณเห็นแนวโน้ม (Trends) ว่าเทสต์ตัวไหนกำลังจะกลายเป็นปัญหาในอนาคตก่อนที่มันจะสายเกินไป

สรุป: เปลี่ยนจากมือใหม่ที่ "ลุ้น" เป็นมือโปรที่ "มั่นใจ"

การจัดการกับ Flaky Tests คือการฝึกนิสัยให้เป็นโปรแกรมเมอร์ที่ละเอียดรอบคอบ คุณไม่ได้แค่เขียนโค้ดให้ทำงานได้ แต่คุณกำลังสร้าง ระบบนิเวศของการพัฒนา ที่ทุกคนในทีมสามารถวางใจได้ว่า ถ้าผลทดสอบเขียว (ผ่าน) ก็คือผ่านจริงๆ ไม่ใช่แค่ความบังเอิญ

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

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


ที่มา: Taming Test Flakiness: A Practical Guide to Retries, Parallelism, and Stability Thresholds — DEV Community

แชร์บทความ

Facebook X LINE

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

เทคนิคเขียนแอป Flutter สำหรับ Meta Smart Glasses ให้ลื่นไหลและมีประสิทธิภาพ

เทคนิคเขียนแอป Flutter สำหรับ Meta Smart Glasses ให้ลื่นไหลและมีประสิทธิภาพ

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

ที่มา: DEV Community

3 hours ago 10 นาที
5 views
เปรียบเทียบ WebSocket, SSE และ Polling เลือกวิธีทำระบบ Real-Time ให้เหมาะกับงาน

เปรียบเทียบ WebSocket, SSE และ Polling เลือกวิธีทำระบบ Real-Time ให้เหมาะกับงาน

อยากทำระบบ Real-Time แต่ไม่รู้จะเลือกใช้ Polling, SSE หรือ WebSocket ดี? มาดูวิธีเลือกใช้ให้เหมาะกับงาน เพื่อให้แอปของคุณทำงานลื่นไหลและประหยัดทรัพยากรเซิร์ฟเวอร์

ที่มา: DEV Community

6 hours ago 10 นาที
4 views