การทดสอบสแนปชอตเป็นวิธีการตรวจสอบส่วนติดต่อผู้ใช้แบบอัตโนมัติ โดยสถานะหน้าจอปัจจุบันจะถูกเปรียบเทียบกับรูปภาพอ้างอิง (สแนปชอต) ที่บันทึกไว้ในการรันทดสอบครั้งก่อน ความแตกต่างทางภาพใดๆ จะถูกบันทึกเป็นการเปลี่ยนแปลงที่ต้องการการยืนยันจากนักพัฒนา ต่างจากการทดสอบ UI ที่ตรวจสอบการมีอยู่ขององค์ประกอบ การทดสอบสแนปชอตจะตรวจจับการเปลี่ยนแปลงในระดับพิกเซล — การเลื่อน การเบี่ยงเบนของสี และปัญหาการจัดวาง ตามข้อมูลจาก Android Developers, 2024 การทดสอบสแนปชอต ตรวจพบการถดถอยทางภาพมากถึง 30% ที่การทดสอบ UI แบบดั้งเดิมมองข้าม ทำให้เป็นเครื่องมือที่ขาดไม่ได้สำหรับการรักษาอินเทอร์เฟซที่สอดคล้องกัน
ประเด็นสำคัญ
การทดสอบสแนปชอต เป็นเทคนิคที่การทดสอบเรนเดอร์คอมโพเนนต์ส่วนติดต่อผู้ใช้ บันทึกรูปภาพผลลัพธ์เป็นข้อมูลอ้างอิง และในการรันครั้งต่อไปจะเปรียบเทียบการเรนเดอร์ปัจจุบันกับข้อมูลอ้างอิงนี้ หากรูปภาพตรงกัน — การทดสอบผ่าน หากพบความแตกต่าง — การทดสอบล้มเหลว และนักพัฒนาได้รับรูปภาพ diff ที่เน้นพิกเซลที่เปลี่ยนแปลงไป เทคนิคนี้ยืมมาจากการพัฒนาเว็บ (Jest snapshots) และปรับให้เข้ากับแพลตฟอร์มมือถือ
คุณค่าหลักของการทดสอบสแนปชอตคือ การตรวจจับการเปลี่ยนแปลงทางภาพที่ไม่คาดคิดโดยอัตโนมัติ นักพัฒนาอาจเปลี่ยนโทนสีในธีมส่วนกลางและส่งผลกระทบต่อหน้าจอหลายสิบหน้าโดยไม่ได้ตั้งใจ การทดสอบ UI ที่ตรวจสอบการมีอยู่ของปุ่มและข้อความจะไม่สังเกตเห็นสิ่งนี้ การทดสอบสแนปชอตจะบันทึกการเปลี่ยนแปลงทุกพิกเซลบนทุกหน้าจอที่ได้รับผลกระทบ โดยให้ภาพรวมที่สมบูรณ์ของผลกระทบของการเปลี่ยนแปลง
จากการสำรวจของ Mobile DevOps Summit 2023 ทีมที่ใช้การทดสอบสแนปชอตนอกเหนือจากการทดสอบ UI แบบดั้งเดิมจะลดจำนวนข้อบกพร่องทางภาพในรุ่นที่เผยแพร่ลง 40% วิธีการนี้มีประสิทธิภาพโดยเฉพาะในโครงการที่มีระบบการออกแบบและสถาปัตยกรรมแบบคอมโพเนนต์ ซึ่งการเปลี่ยนแปลงคอมโพเนนต์พื้นฐานหนึ่งตัวอาจส่งผลกระทบต่อหน้าจอแอปพลิเคชันหลายสิบหน้า
ความแตกต่างพื้นฐานอยู่ที่วัตถุของการตรวจสอบ การทดสอบ UI ตรวจสอบการมีอยู่ สถานะ และพฤติกรรมขององค์ประกอบอินเทอร์เฟซ: “ปุ่มมองเห็นได้”, “ข้อความมีข้อความแสดงข้อผิดพลาด”, “เมื่อกดแล้วเปิดหน้าจอใหม่” การทดสอบสแนปชอตตรวจสอบลักษณะทางภาพทั้งหมด: ตำแหน่งองค์ประกอบ ระยะขอบ สี แบบอักษร เงา และมุมมน การทดสอบสแนปชอต ตอบคำถาม “หน้าจอมีลักษณะตามที่คาดหวังหรือไม่?” ในขณะที่การทดสอบ UI ตอบ “หน้าจอทำงานตามที่คาดหวังหรือไม่?”
ความเร็วในการดำเนินการก็แตกต่างกันเช่นกัน การทดสอบ UI ทำงานบนอีมูเลเตอร์หรืออุปกรณ์จริง ต้องการการโหลดแอปพลิเคชันแบบเต็ม และใช้เวลา 10 วินาทีถึงหนึ่งนาทีต่อหนึ่งสถานการณ์ การทดสอบสแนปชอต ที่ใช้ไลบรารีเช่น Paparazzi จะเรนเดอร์คอมโพเนนต์ในสภาพแวดล้อมเสมือนโดยไม่ต้องเปิดอีมูเลเตอร์ ซึ่งช่วยลดเวลาในการทดสอบเหลือ 100–500 มิลลิวินาที ชุดการทดสอบสแนปชอตแบบเต็ม (50–100 หน้าจอ) จะดำเนินการใน 2–5 นาทีแทนที่จะเป็น 30–60 นาทีสำหรับชุดการทดสอบ UI ที่เทียบเคียงได้
อย่างไรก็ตาม การทดสอบสแนปชอตไม่ได้แทนที่การทดสอบ UI กลยุทธ์ที่เหมาะสมที่สุด คือการผสมผสาน: การทดสอบสแนปชอตครอบคลุมการถดถอยทางภาพ (การเรนเดอร์แต่ละหน้าจอในสถานะพื้นฐาน) ในขณะที่การทดสอบ UI ครอบคลุมด้านพฤติกรรม (สถานการณ์การคลิก การตรวจสอบความถูกต้องของอินพุต การนำทาง) การผสมผสานนี้ให้ความมั่นใจ 90% ในความถูกต้องของอินเทอร์เฟซโดยใช้เวลาเรียกใช้ CI น้อยที่สุด
บน Android เครื่องมือหลักคือ Paparazzi และ Shot Paparazzi จาก Cash App เรนเดอร์คอมโพเนนต์ในสภาพแวดล้อมการทดสอบ JVM โดยไม่ต้องใช้อีมูเลเตอร์ ใช้เลย์เอาต์แรงโน้มถ่วง Layoutlib Shot จาก Karumi ถ่ายภาพหน้าจอ Instrumentation บนอุปกรณ์จริงหรืออีมูเลเตอร์และเปรียบเทียบกับข้อมูลอ้างอิงผ่านไลบรารี AShot โดยคำนึงถึงความแตกต่างของความละเอียดและความหนาแน่นของพิกเซล
Paparazzi ไม่ต้องการการเปิดอีมูเลเตอร์ — การเรนเดอร์ดำเนินการบน JVM ผ่าน Layoutlib ให้ความเร็วเทียบเท่ากับการทดสอบหน่วย ไลบรารีรองรับทั้งระบบ View และ Jetpack Compose สำหรับ Compose ให้ใช้มอดิฟายเออร์ paparazzi.snapshot { MyComposable() } ข้อมูลอ้างอิงถูกเก็บไว้ใน src/test/snapshots และเปรียบเทียบโดยอัตโนมัติในทุกการรัน เปอร์เซ็นต์ความแตกต่างสูงสุดสามารถกำหนดค่าได้ผ่าน maxPercentDifference
SnapshotTesting จาก Point-Free รองรับการเปรียบเทียบไม่เพียงแต่ UIImage แต่ยังรวมถึงสตริง JSON Data และที่เก็บ Core Data ทั้งหมด ทำให้เป็นเครื่องมืออเนกประสงค์ที่ไม่เพียงแต่สำหรับสแนปชอต UI แต่ยังสำหรับการตรวจสอบการทำให้เป็นอนุกรมและการถอดรหัสการตอบสนอง JSON สำหรับ SwiftUI ให้ใช้ส่วนขยาย assertSnapshot กับมอดิฟายเออร์ .image(on: .iPhone13) กลยุทธ์ record: true จะสร้างข้อมูลอ้างอิงในการรันครั้งแรก
สำหรับ React Native วิธีแก้ปัญหายอดนิยมคือ react-native-testing-library ร่วมกับ jest-image-snapshot วิธีการทดสอบสแนปชอตแบบเว็บถูกย้ายไปยังสภาพแวดล้อมมือถือโดยการเรนเดอร์คอมโพเนนต์ในสภาพแวดล้อม Node.js ตามด้วยการเปรียบเทียบสแนปชอต JSON ของ DOM เสมือน วิธีการนี้เร็วกว่าแบบเนทีฟแต่แม่นยำน้อยกว่า — ไม่คำนึงถึงลักษณะเฉพาะของการเรนเดอร์แบบอักษรและคุณสมบัติของคอมโพเนนต์ระบบเฉพาะแพลตฟอร์ม สำหรับ Flutter จะใช้การทดสอบ golden ผ่านชุดเครื่องมือ goldens
มาดูการทดสอบสแนปชอตสำหรับ Android (Paparazzi) และ iOS (SnapshotTesting) กัน ตัวอย่างทั้งสองตรวจสอบลักษณะของคอมโพเนนต์ — การ์ดผู้ใช้ที่มีรูปประจำตัว ชื่อ และสถานะ การทดสอบเรนเดอร์คอมโพเนนต์ ด้วยข้อมูลทดสอบและเปรียบเทียบผลลัพธ์กับรูปภาพอ้างอิงที่เก็บไว้ในที่เก็บ
Paparazzi ใช้คำอธิบายประกอบ @Test และเมธอด snapshot() เพื่อบันทึกการเรนเดอร์ ข้อมูลอ้างอิง จะถูกบันทึกในโฟลเดอร์ src/test/snapshots และโหลดโดยอัตโนมัติในการรันครั้งถัดไปเพื่อเปรียบเทียบ
class UserCardSnapshotTest {
@get:Rule
val paparazzi = Paparazzi(
theme = "Theme.MyApp",
maxPercentDifference = 0.1
)
@Test
fun userCard_defaultState() {
val card = UserCard(
name = "Alice Johnson",
status = "Online",
avatarUrl = "https://example.com/avatar.png"
)
paparazzi.snapshot(card)
}
@Test
fun userCard_offlineState() {
val card = UserCard(
name = "Bob Smith",
status = "Offline",
avatarUrl = null
)
paparazzi.snapshot(card, name = "user_card_offline")
}
}
SnapshotTesting ใช้มอดิฟายเออร์ .snapshot() ภายใน assertSnapshot ไลบรารีจะกำหนดรูปแบบโดยอัตโนมัติ — UIImage สำหรับ UIView, String สำหรับข้อความ, Data สำหรับข้อมูลไบนารี
import SnapshotTesting
import XCTest
class UserCardSnapshotTests: XCTestCase {
func testUserCardDefaultState() {
let card = UserCardView(
name: "Alice Johnson",
status: "Online",
avatarURL: URL(string: "https://example.com/avatar.png")
)
let controller = UIHostingController(rootView: card)
assertSnapshot(matching: controller, as: .image(on: .iPhone13))
}
func testUserCardOfflineState() {
let card = UserCardView(
name: "Bob Smith",
status: "Offline",
avatarURL: nil
)
assertSnapshot(matching: card, as: .image(on: .iPhone13))
}
}
ขั้นตอนการทำงานทั่วไปประกอบด้วยสี่ขั้นตอน การรันครั้งแรก (โหมดบันทึก): การทดสอบสแนปชอตทั้งหมดดำเนินการในโหมดบันทึก — รูปภาพอ้างอิงถูกสร้างขึ้นและบันทึกในที่เก็บ ขั้นตอนนี้ดำเนินการระหว่างการตั้งค่าการทดสอบเริ่มต้นหรือหลังจากการเปลี่ยนแปลงอินเทอร์เฟซโดยเจตนา หลังจากบันทึก ข้อมูลอ้างอิงจะถูกคอมมิตพร้อมกับโค้ด — กลายเป็นส่วนหนึ่งของโครงการ
ในการรันครั้งต่อไป การทดสอบจะทำงานใน โหมดเปรียบเทียบ: การเรนเดอร์ใหม่แต่ละครั้งจะถูกเปรียบเทียบกับข้อมูลอ้างอิง หากพบความแตกต่าง รูปภาพ diff จะถูกสร้างขึ้น: พิกเซลที่ตรงกับข้อมูลอ้างอิงจะถูกเน้นด้วยสีเขียว พิกเซลที่แตกต่างกันด้วยสีแดง นักพัฒนาศึกษา diff และตัดสินใจ: หากการเปลี่ยนแปลงเป็นไปตามที่คาดหวัง (การเปลี่ยนแปลงการออกแบบโดยเจตนา) ข้อมูลอ้างอิงจะถูกอัปเดตด้วยคำสั่งบันทึก หากไม่คาดคิด — ข้อบกพร่องจะถูกแก้ไข การอัปเดตข้อมูลอ้างอิง ดำเนินการด้วยคำสั่งเดียว: สำหรับ Paparazzi คือ `./gradlew recordPaparazzi` สำหรับ SnapshotTesting — `assertSnapshot(record: true)`
ตาม Spotify Engineering Blog (2022) ทีมที่ใช้ขั้นตอนการทำงานที่อธิบายไว้ใช้เวลาเฉลี่ย 2 นาทีต่อการทดสอบในการวิเคราะห์รูปภาพ diff ด้วยชุดการทดสอบสแนปชอต 50 รายการ รอบการอัปเดตข้อมูลอ้างอิงแบบเต็มใช้เวลา 15–20 นาที ซึ่งเร็วกว่าการตรวจสอบด้วยตนเองของการเปลี่ยนแปลงทางภาพบน 50 หน้าจออย่างมีนัยสำคัญ
การทดสอบสแนปชอตมีข้อจำกัดพื้นฐาน ความไวต่อสภาพแวดล้อม: คอมโพเนนต์เดียวกันอาจเรนเดอร์แตกต่างกันในเวอร์ชันระบบปฏิบัติการ ความหนาแน่นของหน้าจอ และการกำหนดค่าแบบอักษรที่แตกต่างกัน ข้อมูลอ้างอิงที่สร้างบนเครื่องหนึ่งอาจแตกต่างจากการเรนเดอร์บนเซิร์ฟเวอร์ CI วิธีแก้ไขคือใช้พารามิเตอร์สภาพแวดล้อมคงที่: เวอร์ชัน Layoutlib เฉพาะสำหรับ Paparazzi หรือรุ่นอุปกรณ์ที่แน่นอนสำหรับ SnapshotTesting
รูปแบบที่ไม่พึงประสงค์ #1: สแนปชอตขนาดยักษ์ — การทดสอบสแนปชอตที่บันทึกทั้งหน้าจอล้มเหลวทุกครั้งที่มีการเปลี่ยนแปลงเล็กน้อยในคอมโพเนนต์ใดๆ วิธีการที่ถูกต้องคือการทดสอบคอมโพเนนต์แต่ละรายการ (ปุ่ม การ์ด ฟิลด์ป้อนข้อมูล) แยกกัน คอมโพเนนต์แต่ละตัวได้รับการทดสอบอย่างอิสระ ซึ่งให้การระบุแหล่งที่มาของการเปลี่ยนแปลงที่แม่นยำ รูปแบบที่ไม่พึงประสงค์ #2: การละเลย diff — การอัปเดตข้อมูลอ้างอิงโดยอัตโนมัติโดยไม่วิเคราะห์รูปภาพ diff ทำให้คุณค่าของการทดสอบสแนปชอตลดลงเป็นศูนย์ แต่ละ diff ต้องการการตัดสินใจอย่างมีสติของนักพัฒนา
ตาม Better Engineering Blog (2023) การทดสอบสแนปชอตให้ประโยชน์สูงสุดเมื่อครอบคลุมคอมโพเนนต์ระบบการออกแบบและหน้าจอหลักในสถานะพื้นฐาน — ว่าง เต็ม ข้อผิดพลาด และขอบเขต การครอบคลุมแอนิเมชันและสถานะไดนามิกผ่านการทดสอบสแนปชอตไม่มีประสิทธิภาพเนื่องจากลักษณะที่ไม่แน่นอนของประทับเวลาในการเรนเดอร์ — สำหรับสถานการณ์ดังกล่าว การบันทึกวิดีโอหรือการตรวจสอบ QA ด้วยตนเองเหมาะสมกว่า
คำถามที่พบบ่อย
ไม่ การทดสอบสแนปชอตตรวจสอบลักษณะทางภาพ ในขณะที่การทดสอบ UI ตรวจสอบพฤติกรรมของอินเทอร์เฟซ กลยุทธ์ที่เหมาะสมที่สุด คือการผสมผสานทั้งสองวิธี: สแนปชอตสำหรับการถดถอยทางภาพ การทดสอบ UI สำหรับสถานการณ์และการนำทาง สแนปชอตตอบ “ดูถูกต้องหรือไม่” การทดสอบ UI ตอบ “ทำงานถูกต้องหรือไม่”
ข้อมูลอ้างอิงจะถูกอัปเดตทุกครั้งที่มีการเปลี่ยนแปลงการออกแบบโดยเจตนา: สีธีมใหม่ ระยะขอบที่เปลี่ยนแปลง การเพิ่มหรือลบองค์ประกอบ การอัปเดต ดำเนินการผ่านโหมดบันทึก หลังจากนั้นรูปภาพ diff จะถูกตรวจสอบในการตรวจสอบโค้ดเพื่อให้แน่ใจว่าการเปลี่ยนแปลงตรงตามความคาดหวังของนักออกแบบ
อันดับแรก คอมโพเนนต์ระบบการออกแบบ — ปุ่ม การ์ด ฟิลด์ป้อนข้อมูล หน้าต่างโมดัล จากนั้นหน้าจอหลักในสถานะพื้นฐาน อย่าทดสอบด้วยสแนปชอต แอนิเมชัน WebView แผนที่และหน้าจอที่มีเนื้อหาไดนามิก — สแนปชอตให้ผลลัพธ์ล้มเหลวปลอมเนื่องจากความไม่แน่นอน
ใช้ API Level เดียวกันสำหรับทั้งโหมดบันทึกและโหมดทดสอบ สำหรับ Paparazzi ให้ระบุเวอร์ชัน Layoutlib เฉพาะในการกำหนดค่า สำหรับ SnapshotTesting ให้กำหนดรุ่นอุปกรณ์ ข้อมูลอ้างอิงที่สร้างบน Android 14 อาจแตกต่างจากการเรนเดอร์บน Android 12 เนื่องจากการเปลี่ยนแปลงของแบบอักษรระบบและธีม Material
ใน CI การทดสอบสแนปชอตจะทำงานในโหมดตรวจสอบ (verify) หากการทดสอบล้มเหลว CI จะแสดงรูปภาพ diff ในสิ่งประดิษฐ์ของการ build โหมดบันทึก (การอัปเดตข้อมูลอ้างอิง) ดำเนินการในเครื่องโดยนักพัฒนาหรือในงาน CI แยกต่างหากพร้อมทริกเกอร์ด้วยตนเอง รูปภาพอ้างอิงต้องถูกคอมมิตในที่เก็บ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม