บทความและข่าวสาร | Seven Peaks Insights

เคล็ดลับจัดการ Snackbar ใน Jetpack Compose เพื่อให้ทดสอบ UI อย่างแม่นยำและเชื่อถือได้

SPS- Thought Leadership_Somkiet-01-Herobanner


ในการพัฒนาแอปฯ Android ยุคใหม่ ทีมพัฒนาต่างหันมาพึ่งพาการทดสอบ UI แบบอัตโนมัติมากขึ้นเรื่อยๆ ตามความซับซ้อนของแอปฯ ที่เพิ่มขึ้น อย่างไรก็ตาม มีส่วนหน้าจอผู้ใช้ (UI) บางตัวที่ดูเหมือนจะสร้างง่าย แต่กลับทดสอบได้ยากอย่างน่าประหลาด คุณสมเกียรติ กิจวงศ์วัฒนะ Staff Software Engineer จาก LINE MAN Wongnai ได้มาแบ่งปันข้อมูลเชิงลึกเกี่ยวกับตัวการสำคัญที่มักทำให้การทดสอบรวนอยู่บ่อยครั้ง นั่นก็คือ Snackbar นั่นเอง

ทำไมการทดสอบ Snackbar แบบมาตรฐานถึงมักจะล้มเหลว

วิธีการเขียน Snackbar ทั่วไปใน Jetpack Compose มักจะใช้ Scaffold, SnackbarHost และ SnackbarHostState ซึ่งวิธีนี้ทำงานได้สมบูรณ์แบบสำหรับผู้ใช้งานทั่วไป แต่พอนำไปรันในสภาพแวดล้อมระบบอัตโนมัติอย่าง CI/CD มันมักจะเกิดปัญหาเรื่อง Timing ตามมา

คุณลองนึกถึงภาพขั้นตอนการเก็บคูปองดู

  1. การกระทำ: ผู้ใช้กดปุ่ม "เก็บคูปอง"
  2. Snackbar แรก: แอปฯ แสดงข้อความ "กำลังใช้คูปอง..."
  3. ปัญหาเรื่องจังหวะ (Race): หากเซิร์ฟเวอร์ตอบกลับเร็วมาก แอปฯ จะพยายามแสดง Snackbar ตัวที่สอง ("เก็บคูปองสำเร็จ!") ก่อนที่ตัวแรกจะหายไป

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

การสร้าง Controller และ Container ที่เป็นมิตรต่อการทดสอบ

เพื่อขจัดปัญหาการทดสอบที่รวนจนทำงานต่อได้ลำบาก เราจึงควรเปลี่ยนมาใช้โครงสร้างแบบสองส่วนที่ช่วยให้เราควบคุมพฤติกรรมของ Snackbar ในการทดสอบได้ด้วยตัวเอง นั่นคือการใช้ Controller เพื่อสั่งปิดข้อความ และ Container เพื่อจัดการระยะเวลาการแสดงผล

sps_thought_leadership_somkiet_02-1


1. SnackbarUiTestController

  • วัตถุประสงค์: ทำหน้าที่เป็นตัวลงทะเบียนเพื่อเก็บอินสแตนซ์ของ SnackbarHostState ที่ถูกสร้างขึ้นตอนแอปฯ ทำงาน เพื่อให้เราสามารถสั่งปิดข้อความ (Dismiss) ผ่านโค้ดการทดสอบได้โดยตรง

  • ฟังก์ชันหลัก:

    • registerSnackbarHostState: เพิ่ม State เข้าไปในระบบติดตาม

    • dismissCurrentSnackbar: สั่ง dismiss() Snackbar ที่กำลังแสดงอยู่ด้วยตนเอง

    • clearAllSnackbarHostStates: เคลียร์ข้อมูลหลังจบการทดสอบเพื่อป้องกันปัญหาหน่วยความจำรั่ว

2. SnackbarHostStateProvider

เพื่อให้โค้ดที่ใช้งานจริง (Production code) ยังคงสะอาดอยู่ Provider ตัวนี้จะใช้ Java Reflection เพื่อเช็กว่ามี Test Controller พร้อมใช้งานหรือไม่

  • หากแอปฯ รันอยู่ในสภาพแวดล้อมการทดสอบ มันจะลงทะเบียน SnackbarHostState กับ Controller โดยอัตโนมัติ

  • ใช้ CompositionLocalProvider เพื่อให้ State นี้ถูกเรียกใช้งานได้ง่ายทั่วทั้งโครงสร้าง UI (UI Tree)

3. SnackbarContainer

คอมโพเนนต์ตัวนี้จะแก้ปัญหาเรื่องระยะเวลาการแสดงผล ในการใช้งานจริง Snackbar จะมีระยะเวลาแบบสั้นหรือยาว แต่เมื่อฟังก์ชัน isRunningUiTest() เป็นจริง Container จะเปลี่ยนระยะเวลาให้เป็นแบบ Indefinite (แสดงค้างไว้ตลอด) วิธีนี้ช่วยให้มั่นใจว่า Snackbar จะอยู่บนหน้าจอนานพอที่ตัวทดสอบจะตรวจสอบความถูกต้องได้ และไม่หายไปก่อนเวลาที่มันควรจะอยู่ถึง

การนำขั้นตอนที่เอื้อต่อการทดสอบไปใช้งาน

ด้วยโครงสร้างแบบนี้ การทดสอบ UI ของคุณจะคาดเดาผลได้และอ่านง่ายขึ้นมาก แทนที่จะต้องมานั่งลุ้นเรื่องจังหวะเวลา โค้ดการทดสอบของคุณจะทำงานตามรูปแบบที่ชัดเจนคือ "ตรวจสอบ แล้วจึงสั่งปิด" (Verify-and-dismiss pattern)

Kotlin
@Test

fun couponRedemptionFlow() {

    verifyDefaultCard()

    clickCollectButton()

    

    // Step 2: Verify and then manually dismiss

    verifyApplyingSnackbar()

    SnackbarUiTestController.dismissCurrentSnackbar() 

    

    // Step 3: Now the second snackbar can be verified without conflict

    verifyRedeemedCard()

    verifyRedeemedSnackbar()

    SnackbarUiTestController.dismissCurrentSnackbar()

}

ข้อควรระวังสำหรับการนำไปใช้จริง

แม้ว่าวิธีนี้จะมีประสิทธิภาพมาก แต่มี 2 จุดสำคัญที่ต้องระลึกไว้เสมอ

1. อย่าให้โค้ดทดสอบหลุดไปในงานจริง: ตรวจสอบให้แน่ใจว่า SnackbarUiTestController อยู่ในโฟลเดอร์ androidTest เท่านั้น หากหลุดไปอยู่ใน Production ตัว Snackbar ของคุณอาจจะค้างอยู่บนหน้าจอผู้ใช้จริงตลอดกาลได้

2. ความซับซ้อน: ในแอปฯ ที่ซับซ้อน Snackbar จากคอมโพเนนต์อื่นอาจจะโผล่ขึ้นมาแทรกระหว่างการทดสอบได้ ดังนั้นควรใช้เมธอด @After เพื่อเคลียร์ Host States ทั้งหมดระหว่างจบแต่ละการทดสอบเสมอ

sps_thought_leadership_somkiet_03

เน้นความเสถียร
มากกว่าความเรียบง่าย

การบังคับให้ Snackbar แสดงค้างไว้ (Indefinite duration) ระหว่างการทดสอบ และการใช้ Controller ส่วนกลางเพื่อสั่งปิดข้อความ จะช่วยเปลี่ยนการทดสอบ Snackbar ที่เคยรวนให้กลายเป็นส่วนที่เสถียรที่สุดในชุดทดสอบของคุณ วิธีนี้ก้าวข้ามจากการหวังว่า UI จะอยู่นิ่งๆ ไม่เกิดปัญหา ไปเป็นการมอบเครื่องมือให้นักพัฒนาสั่งการมันได้ดั่งใจ


คุณสมเกียรติ กิจวงศ์วัฒนะ
Staff Software Engineer - Android at LINE MAN Wongnai

คุณสมเกียรติเป็นนักพัฒนา Android ที่มีความหลงใหลในสายงานนี้มานานกว่า 10 ปี มีประสบการณ์ทั้งในด้านการพัฒนาซอฟต์แวร์ระดับองค์กร และเป็นผู้ที่มีส่วนร่วมขับเคลื่อนชุมชน Android ในประเทศไทยอย่างต่อเนื่อง ก่อนหน้านี้เขาเคยทำงานเป็นนักพัฒนาฮาร์ดแวร์ สร้างระบบฝังตัว (Embedded Systems) ที่ทำงานเชื่อมต่อกับอุปกรณ์ Android 

SPS- Thought Leadership_Somkiet-Profile-1

พร้อมจะมาลองดูกันไหมว่า การพัฒนาแบบ AI-native จะช่วยให้โปรเจกต์ถัดไปของคุณสำเร็จได้เร็วขึ้นยังไง? ติดต่อเราได้เลย