آزمون عکسک (snapshot testing) روشی است برای بررسی خودکار رابط کاربر، که در آن وضعیت فعلی صفحه نمایش با یک تصویر مرجع (snapshot) که در اجرای قبلی آزمون ذخیره شده مقایسه میشود. هر تفاوت دیداری به عنوان تغییری ثبت میشود که نیازمند تایید توسط توسعهدهنده است. بر خلاف آزمونهای UI که وجود عناصر را بررسی میکنند، آزمونهای عکسک تغییرات پیکسلی را ثبت میکنند — جابجاییها، انحرافات رنگی و اختلالات چینش. به گزارش Android Developers, 2024، آزمون عکسک تا 30% از بازگشتهای دیداری را که توسط آزمونهای سنتی UI از قلم افتاده، کشف میکند و آن را به ابزاری ضروری برای حفظ رابط پیوسته تبدیل میکند.
نکات کلیدی
آزمون عکسک (snapshot testing) روشی است که در آن آزمون یک کامپوننت را رندر کرده، تصویر حاصل را به عنوان مرجع ذخیره کرده و در اجراهای بعدی، رندر فعلی را با این مرجع مقایسه میکند. اگر تصاویر مطابقت داشته باشند — آزمون قبول میشود. اگر تفاوتهایی پیدا شود — آزمون مردود شده و توسعهدهنده تصویر diff با پیکسلهای تغییرکرده را دریافت میکند. این روش از توسعه وب (Jest snapshots) قرض گرفته شده و برای پلتفرمهای موبایل سازگار شده است.
ارزش اصلی آزمونهای عکسک — کشف خودکار تغییرات دیداری غیرمنتظره. توسعهدهنده میتواند شمای رنگی را در ظاهر جهانی تغییر داده و به صورت تصادفی بر دهها صفحه تأثیر بگذارد. آزمونهای UI که وجود دکمهها و متون را بررسی میکنند، این را نمیبینند. آزمون عکسک تغییر هر پیکسل را در هر صفحه تأثیرپذیر ثبت کرده و تصویر کاملی از تأثیر تغییر ارائه میدهد.
به گزارش نظرسنجی Mobile DevOps Summit 2023، تیمهایی که از آزمون عکسک در کنار آزمونهای کلاسیک UI استفاده میکنند، تعداد نقایص دیداری در انتشارات را 40% کاهش میدهند. این روش بهویژه در پروژههای با سیستمهای طراحی و رویکردهای کامپوننتی مؤثر است، چرا که تغییر یک کامپوننت اصلی میتواند بر دهها صفحه تأثیر بگذارد.
تفاوت اصلی در شیئی است که بررسی میشود. آزمونهای UI وجود، وضعیت و رفتار عناصر رابط را بررسی میکنند: «دکمه قابل مشاهده است»، «متن شامل پیام خطا است»، «پس از کلیک صفحه جدید باز میشود». آزمونهای عکسک ظاهر را به صورت کلی بررسی میکنند: جایگزینی عناصر، فاصلهها، رنگها، فونتها، سایهها و گردها. آزمون عکسک به سوال «آیا صفحه مطابق انتظار به نظر میرسد؟» پاسخ میدهد، در حالی که آزمون UI به سوال «آیا صفحه مطابق انتظار کار میکند؟»
سرعت اجرا نیز متفاوت است. آزمونهای UI روی شبیهساز یا دستگاه واقعی اجرا میشوند، نیازمند بارگیری کامل برنامه هستند و برای یک سناریو از 10 ثانیه تا یک دقیقه زمان میبرند. آزمونهای عکسک بر پایه کتابخانههایی مانند Paparazzi کامپوننت را در محیط مجازی بدون اجرای شبیهساز رندر میکنند و زمان آزمون را به 100-500 میلیثانیه کاهش میدهند. یک مجموعه کامل آزمونهای عکسک (50-100 صفحه) در 2-5 دقیقه اجرا میشود، در حالی که همین تعداد آزمون UI 30-60 دقیقه زمان نیاز دارد.
اما آزمونهای عکسک جایگزین آزمونهای UI نیستند. راهبرد بهینه ترکیبی از هر دو است: آزمونهای عکسک بازگشت دیداری را پوشش میدهند (رندر هر صفحه در وضعیتهای پایه)، و آزمونهای UI بازگشت رفتاری (سناریوهای کلیک، اعتبارسنجی ورودی، ناوبری). چنین ترکیبی 90% اطمینان از صحت رابط را با حداقل زمان اجرای CI فراهم میکند.
در Android ابزارهای اصلی Paparazzi و Shot هستند. Paparazzi از Cash App کامپوننتها را در محیط آزمون روی JVM بدون شبیهساز با استفاده از چینش گرانیتشی Layoutlib رندر میکند. Shot از Karumi عکسهای Instrumentation را روی دستگاه واقعی یا شبیهساز اجرا کرده و آنها را با استفاده از کتابخانه AShot با در نظر گرفتن تفاوتهای وضوح و تراکم پیکسل با مرجعها مقایسه میکند.
Paparazzi نیازی به اجرای شبیهساز ندارد — رندر از طریق Layoutlib روی JVM اجرا میشود که سرعتی مشابه با آزمونهای واحد دارد. کتابخانه هم سیستم View و هم Jetpack Compose را پشتیبانی میکند. برای Compose از اصلاحگر paparazzi.snapshot { MyComposable() } استفاده میشود. مرجعها در src/test/snapshots ذخیره میشوند و در هر اجرا به طور خودکار مقایسه میشوند. حداکثر درصد تفاوت از طریق maxPercentDifference تنظیم میشود.
SnapshotTesting از Point-Free نه تنها UIImage، بلکه String، 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 از طریق goldens toolkit آزمون golden استفاده میشود.
به آزمونهای عکسک برای 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))
}
}
یک جریان کار طیفی شامل چهار مرحله است. اولین اجرا (record mode): همه آزمونهای عکسک در حالت ضبط اجرا میشوند — تصاویر مرجع ایجاد و در مخزن ذخیره میشوند. این مرحله در راهاندازی ابتدایی آزمونها یا پس از تغییر آگاهانه رابط انجام میشود. پس از ضبط، مرجعها همراه با کد کامیت میشوند — آنها بخشی از پروژه میشوند.
در اجراهای بعدی، آزمونها در حالت مقایسه کار میکنند: هر رندر جدید با مرجع مقایسه میشود. اگر تفاوتهایی پیدا شود، تصویر diff تولید میشود: پیکسلهای مطابق با مرجع به رنگ سبز و متفاوت به رنگ قرمز هایلایت داده میشوند. توسعهدهنده diff را بررسی کرده و تصمیم میگیرد: اگر تغییر منتظره است (تغییر آگاهانه طراحی)، مرجع با دستور record بهروز میشود؛ اگر غیرمنتظره است — باگ برطرف میشود. بهروزرسانی مرجعها با یک دستور یکباره انجام میشود: برای Paparazzi `./gradlew recordPaparazzi`، برای SnapshotTesting — `assertSnapshot(record: true)`.
به گزارش Spotify Engineering Blog (2022)، تیمهایی که از جریان کار توصیفشده استفاده میکنند، برای تجزیه و تحلیل تصاویر diff به طور متوسط 2 دقیقه برای هر آزمون زمان میگذارند. در یک مجموعه 50 تایی آزمونهای عکسک، چرخه کامل بهروزرسانی مرجعها 15-20 دقیقه طول میکشد که از بررسی دستی تغییرات دیداری بر 50 صفحه به مراتب سریعتر است.
آزمونهای عکسک محدودیتهای اصلی دارند. حساسیت به محیط: یک کامپوننت واحد میتواند در نسخههای مختلف سیستم عامل، تراکم صفحه و پیکربندیهای فونت به شکل متفاوتی رندر شود. مرجعهایی که روی یک کامپیوتر ایجاد شدهاند ممکن است از رندر روی سرویس CI تفاوت داشته باشند. راه حل — استفاده از پارامترهای ثابت محیط: نسخه مشخص Layoutlib برای Paparazzi یا مدل دقیق دستگاه برای SnapshotTesting.
الگوی نامناسب شماره 1: اسنپشوتهای غول — آزمون عکسکی که کل صفحه را گرفته، با هر تغییر کوچک در هر کامپوننتی مردود میشود. رویکرد صحیح — آزمایش کامپوننتهای جداگانه (دکمه، کارت، فیلد ورودی) به صورت جداگانه. هر کامپوننت به طور مستقل آزمایش میشود که این امر منبع تغییر را به دقت مشخص میکند. الگوی نامناسب شماره 2: نادیده گرفتن diffها — بهروزرسانی خودکار مرجعها بدون تجزیه و تحلیل تصاویر diff، ارزش آزمونهای عکسک را به صفر میرساند. هر diff نیازمند تصمیم آگاهانه توسعهدهنده است.
به گزارش Better Engineering Blog (2023)، آزمونهای عکسک در پوشش کامپوننتهای سیستم طراحی و صفحات کلیدی در وضعیتهای پایه — خالی، پر، خطا و مرزی — بیشترین سود را دارند. پوشش انیمیشنها و وضعیتهای دینامیکی از طریق آزمونهای عکسک به دلیل غیرقابلپیشبینی بودن زمانهای رندر نامؤثر است — برای چنین سناریوهایی، ضبط ویدیو یا بررسی دستی QA مناسبتر است.
سوالات متداول
خیر، آزمونهای عکسک ظاهر را بررسی میکنند، در حالی که آزمونهای UI رفتار رابط را. راهبرد بهینه ترکیب هر دو روش است: اسنپشوتها برای بازگشت دیداری، آزمونهای UI برای بررسی سناریوها و ناوبری. اسنپشوتها به سوال «آیا صحیح به نظر میرسد؟» و آزمونهای UI به سوال «آیا صحیح کار میکند؟» پاسخ میدهند.
مرجعها با هر تغییر آگاهانه در طراحی بهروز میشوند: رنگ جدید طرح، فاصلههای تغییریافته، اضافه یا حذف عناصر. بهروزرسانی از طریق حالت record انجام میشود، سپس تصاویر diff در code review بررسی میشوند تا اطمینان حاصل شود که تغییرات مطابق انتظارات طراح هستند.
ابتدا کامپوننتهای سیستم طراحی — دکمهها، کارتها، فیلدهای ورودی، پنجرههای مودال. سپس صفحات کلیدی در وضعیتهای پایه. با اسنپشوتها آزمایش نکنید انیمیشنها، WebView، نقشهها و صفحات با محتوای دینامیکی — برای آنها، اسنپشوتها به دلیل غیرقابلپیشبینی، خطای کذبی ایجاد میکنند.
از همان سطح API برای حالتهای record و test استفاده کنید. برای Paparazzi نسخه مشخص Layoutlib را در تنظیمات مشخص کنید. برای SnapshotTesting مدل دستگاه را ثابت کنید. مرجعهایی که روی Android 14 ایجاد شدهاند ممکن است از رندر روی Android 12 به دلیل تغییرات در فونتهای سیستم و مضمون Material تفاوت داشته باشند.
در CI، آزمونهای عکسک در حالت بررسی (verify) اجرا میشوند. اگر آزمون مردود شود، CI تصویر diff را در مصنوعات ساخت نشان میدهد. حالت record (بهروزرسانی مرجعها) به طور محلی توسط توسعهدهنده یا در یک کار CI جداگانه با تحریک دستی انجام میشود. تصاویر مرجع حتماً در مخزن کامیت میشوند.
نتایج
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید