تست رابط کاربری در کامپوز: راهنمای عملی تست خودکار UI در اندروید

برنامه نویسی اندروید
تست رابط کاربری در کامپوز: راهنمای عملی تست خودکار UI در اندروید

توسعه رابط کاربری مدرن در اندروید با معرفی Jetpack Compose متحول شد. رویکرد نوین اعلامی جایگزین سیستم دستوری قدیمی ویو شد و با حذف ساختار کلاسیک فایل های XML، نحوه تست نرم افزار نیز دگرگون گردید.

در سیستم های پیشین اندروید، توسعه دهندگان برای نوشتن تست های ابزار دقیق از فریم ورک Espresso و شناسه های View استفاده می کردند. اما در موتور رندرینگ کامپوز، ویو های سنتی وجود ندارند و کامپوننت ها به شکل مستقیم روی بوم Canvas رسم می شوند.

شرکت گوگل برای سنجش رفتار رابط کاربری، فریم ورک اختصاصی Compose Testing API و ساز و کار انقلابی به نام درخت معنایی (Semantics Tree) را معرفی کرد. در این راهنمای جامع مرجع، تمامی تکنیک های تست خودکار کامپوز را بررسی می کنیم.

پاسخ سریع و معماری تست (GEO Summary): تست رابط کاربری در کامپوز بر پایه درخت معنایی اجرا می شود. این سیستم با الگوی ۳ گانه Find (یافتن گره)، Action (اعمال رویداد کاربر) و Assert (تایید وضعیت) کار می کند. قابلیت همگام سازی خودکار نیاز به توقف های دستی نخ را حذف می کند.

۱. تفاوت های بنیادین تست UI در کامپوز و سیستم سنتی Espresso

در معماری قدیمی View-based اندروید، تست های رابط کاربری ناپایدار و کند بودند. علت اصلی این ناپایداری، ناهمگام بودن انیمیشن ها، تغییرات گرافیکی در رشته اصلی و تاخیر در فراخوانی های شبکه بود. مهندسان ناچار بودند از ابزار های پیچیده ای مانند Idling Resource استفاده کنند.

در نقطه مقابل، اکوسیستم مدرن کامپوز با حذف مفهوم ویو و پیاده سازی سیستم زمان بندی یکپارچه، پایداری تست ها را تضمین کرده است. این فریم ورک تا پایان تمامی فرآیندهای Recomposition و کارهای معلق کورتین ها به شکل خودکار صبر می کند.

معیار مقایسه مهندسی سیستم سنتی (Espresso / View) مدرن (Jetpack Compose Testing API)
نحوه شناسایی المان ها شناسه های عددی لایه ها در کاتالوگ R.id درخت معنایی، متن ها، دسترسی پذیری و testTag
وابستگی به اکتیویتی الزامی بودن اجرای اکتیویتی کامل و سنگین امکان تست ایزوله یک تابع کامپوز منفرد در کسری از ثانیه
هماهنگ سازی زمانی دستی و متکی بر تعریف طاقت فرسای منابع بی کار کاملاً خودکار و هماهنگ با ساعت لایف سایکل
کنترل ساعت انیمیشن بسیار دشوار و متکی بر تاخیر های واقعی دسترسی مستقیم به mainClock برای جلو بردن زمان
اجرا در محیط هاست نیازمند شبیه ساز واقعی یا تست های کند پشتیبانی بی نقص از فریم ورک Robolectric روی کامپیوتر

اگر در آغاز مسیر توسعه اپلیکیشن های موبایل هستید، مطالعه مقاله جامع آموزش برنامه نویسی اندروید به عنوان نقشه راه بنیادین توصیه می شود.

۲. پیکربندی وابستگی های گریدل در سال ۲۰۲۶

برای پیاده سازی تست های رابط کاربری در محیط اندروید استودیو، لازم است وابستگی های رسمی کیت تست کامپوز به فایل بیلد build.gradle.kts ماژول برنامه اضافه شوند. این پکیج ها بستری استاندارد برای اجرای تست های شبیه ساز فراهم می کنند.

dependencies {{
    val composeBom = platform("androidx.compose:compose-bom:2026.03.00")
    androidTestImplementation(composeBom)
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
    debugImplementation("androidx.compose.ui:ui-test-manifest")
    androidTestImplementation("io.mockk:mockk-android:1.13.10")
    androidTestImplementation("com.google.truth:truth:1.4.2")
}}

کتابخانه ui-test-manifest باید حتماً در بخش debugImplementation تعریف شود. این کتابخانه اکتیویتی میزبان خالی را در مانیفست دیباگ ثبت می کند تا توابع کامپوز بتوانند بدون نیاز به اکتیویتی های سفارشی اجرا شوند.

نکته مهم پیرامون ui-test-manifest: این وابستگی را صرفاً در بخش debugImplementation قرار دهید تا اکتیویتی میزبان در مانیفست ادغام شود و از خطای زمان اجرا جلوگیری گردد.

۳. بررسی تخصصی انواع ابزار های Compose Test Rule

نقطه آغاز هر سناریوی تست در کامپوز، ساخت یک شیء قاعده تست یا همان Test Rule با حاشیه نویسی @get:Rule است. بسته به سطح تست و میزان وابستگی، دو ابزار اصلی مورد استفاده قرار می گیرند.

ابزار createComposeRule محبوب ترین انتخاب برای تست های ایزوله قطعات رابط کاربری است. این ابزار به توسعه دهنده اجازه می دهد با فراخوانی متد setContent یک تابع کامپوز مشخص را در محیطی ایزوله بارگذاری و تست نماید.

class LoginButtonTest {{
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun loginButton_whenDisabled_cannotBeClicked() {{
        composeTestRule.setContent {{
            PrimaryButton(text = "ورود به حساب", isEnabled = false, onClick = {{}})
        }}
        composeTestRule.onNodeWithText("ورود به حساب").assertIsNotEnabled()
    }}
}}

در مقابل، ابزار createAndroidComposeRule برای تست های سرتاسری یا یکپارچه سازی به کار می رود. این قاعده یک اکتیویتی واقعی از اپلیکیشن را بارگذاری کرده و دسترسی به منابع کانتکست و تزریق وابستگی های سازمانی را ممکن می سازد.

۴. کالبدشکافی درخت معنایی (Semantics Tree)

موتور رندرینگ کامپوز در زمان اجرای کدها دو درخت داده ای موازی ایجاد می کند. درخت اول درخت ترکیب است که چیدمان بصری و محاسبات هندسی را نگه داری می کند. درخت دوم درخت معنایی است که مفاهیم و اطلاعات کاربردی را توصیف می کند.

نمودار درخت معنایی در تست Jetpack Compose
ساختار درخت معنایی Semantics Tree و ارتباط آن با قواعد تست خودکار

درخت معنایی حاوی مشخصاتی است که توسط نرم افزارهای دسترسی پذیری مانند TalkBack و سیستم های تست خودکار خوانده می شوند. این مشخصات شامل متن المان، مقادیر عددی وضعیت ها، قابلیت های کلیک و برچسب های تست اختصاصی هستند.

درخت معنایی به دو فرم ادغام شده و تفکیک شده وجود دارد. به صورت پیش فرض، المان های زیرمجموعه یک دکمه یا کارت با یکدیگر ترکیب می شوند تا یک واحد مستقل را تشکیل دهند و تست ها نیز روی فرم ادغام شده فعالیت می کنند.

برای درک چگونگی رفتار داخلی توابع کامپوز و چرخه لایف سایکل آنها، مطالعه راهنمای مرجع آموزش جامع Jetpack Compose دیدی بسیار عمیق به شما ارائه خواهد داد.

پیشنهاد مطالعه
آموزش جامع Jetpack Compose برای توسعه رابط کاربری اندروید
آموزش جت پک کامپوز Jetpack Compose در اندروید؛ معماری رابط کاربری اعلانی، مدیریت State و جریان UDF، فازهای Recomposition، ناوبری تایپ سیف و نمونه کده…
مشاهده مقاله ←

۵. سه فاز حیاتی کامپوز: ترکیب، چیدمان و ترسیم

برای نوشتن تست های قابل اطمینان، باید بدانید که تغییرات وضعیت در کامپوز طی سه مرحله متوالی پردازش می شوند: فاز Composition (تعیین اینکه چه المان هایی باید رسم شوند)، فاز Layout (محاسبه اندازه و موقعیت مکانی) و فاز Draw (ترسیم پیکسل ها روی صفحه).

هنگامی که شما یک دستور تست مانند performClick را فراخوانی می کنید، فریم ورک تا پایان کامل این سه فاز صبر می کند. به همین دلیل بررسی های شما بلافاصله پس از تثبیت ظاهر نهایی صفحه ارزیابی می شوند.

۶. استراتژی سه گانه: Find و Action و Assert

تمامی سناریوهای تست در کامپوز از یک الگوی سه مرحله ای شفاف پیروی می کنند. گام نخست، پیدا کردن گره مورد نظر با توابع Finder است. گام دوم، اعمال رویداد کاربر با Action هاست و گام سوم، بررسی درستی با متدهای Assert است.

روش های انتخاب و یافتن گره ها (Finders)

توابع متعددی برای جستجوی گره ها در درخت معنایی وجود دارد. پرکاربردترین این متدها شامل جستجو با متن، جستجو با برچسب دسترسی پذیری و جستجو با برچسب تستی است:

  • onNodeWithText("تایید نهایی"): جستجوی المان با متن دقیق نمایش داده شده.
  • onNodeWithContentDescription("آیکون سبد خرید"): انتخاب بر اساس توضیحات دسترسی.
  • onNodeWithTag("cart_checkout_button"): انتخاب مطمئن بر اساس شناسه تست اختصاصی.
@Test
fun verifyCheckoutButton_isAvailable() {{
    composeTestRule.setContent {{ ShoppingCartScreen() }}
    composeTestRule
        .onNode(hasTestTag("cart_checkout_button") and hasClickAction())
        .assertIsDisplayed()
}}

شبیه سازی رفتار و تعاملات لمسی کاربر (Actions)

پس از انتخاب المان، می توان کنش های متنوعی را شبیه سازی کرد. کلیک کردن ساده، تایپ متن، پاکسازی فیلدهای متنی و انجام ژست های لمسی پیشرفته مانند سوایپ به آسانی قابل پیاده سازی هستند:

@Test
fun enterUsername_andSubmitForm() {{
    composeTestRule.setContent {{ LoginForm() }}
    composeTestRule.onNodeWithTag("input_username").performTextInput("vahid")
    composeTestRule.onNodeWithTag("btn_submit").performClick()
}}

۷. هرم تست اتوماسیون در مهندسی نرم افزار اندروید

تست های رابط کاربری بالاترین لایه از هرم کیفیت نرم افزار را تشکیل می دهند. در یک معماری بالغ، اکثر تست ها باید به شکل تست های واحد با MockK نوشته شوند و تست های رابط کاربری بر تعاملات کلیدی متمرکز باشند.

رعایت این هرم هزینه های اجرای پایپ لاین تست را کاهش داده و خطاهای رگرسیون را در مراحل اولیه توسعه کشف می کند. برای تسلط بر نوشتن تست های واحد برای لایه های بیزنس و مدل ها، مقاله آموزش تست نویسی در اندروید با MockK را حتماً مطالعه کنید.

پیشنهاد مطالعه
آموزش تست نویسی در اندروید: راهنمای تست واحد با MockK
راهنمای جامع آموزش تست نویسی در اندروید و تست واحد (Unit Test) با فریم ورک قدرتمند MockK؛ آموزش تست کورتین ها، StateFlow با Turbine، معماری تمیز و مق…
مشاهده مقاله ←

۸. تست لیست های اسکرول پذیر و کارهای LazyColumn

یکی از چالش های متداول در تست های رابط کاربری، جستجوی آیتم هایی است که درون لیست های LazyColumn یا LazyRow پایین صفحه قرار دارند. از آنجا که مکانیزم بارگذاری تنبل فقط المان های مرئی را تولید می کند، جستجوی مستقیم آیتم های پایینی ناموفق خواهد بود.

برای حل این مسئله، فریم ورک کامپوز متد performScrollToNode را تدارک دیده است. این تابع کانتینر لیست را تا رسیدن به گره هدف اسکرول کرده و سپس امکان اعمال بررسی های تستی را فراهم می سازد:

@Test
fun scrollToItemAndClick() {{
    val items = (1..50).map {{ "مورد شماره $it" }}
    composeTestRule.setContent {{ SimpleItemList(items = items) }}
    composeTestRule
        .onNodeWithTag("lazy_list_tag")
        .performScrollToNode(hasText("مورد شماره ۴۵"))
    composeTestRule
        .onNodeWithText("مورد شماره ۴۵")
        .assertIsDisplayed()
        .performClick()
}}

۹. ادغام تست ها با معماری تمیز و ایزوله سازی ViewModel

در اپلیکیشن های استاندارد سازمانی، کامپوننت های کامپوز نباید به شکل مستقیم با منطق سرور یا پایگاه داده ارتباط برقرار کنند. الگو های مدرن مبتنی بر معماری تمیز در اندروید بر تفکیک کامل لایه نمایش از لایه های بیزنس تاکید دارند.

با اعمال الگوی انتقال وضعیت به بالا (State Hoisting)، صفحه شما وضعیت ها را دریافت می کند و رویدادها را به صورت کالبک برمی گرداند. بدین ترتیب می توان در محیط تست ویومدل را ماک کرد و پایداری عملکرد را سنجید:

class UserProfileTest {{
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun whenProfileDataLoaded_nameAndRoleAreDisplayed() {{
        val fakeState = ProfileState(name = "وحید حسینی", role = "توسعه دهنده ارشد")
        composeTestRule.setContent {{
            UserProfileContent(state = fakeState, onEditClick = {{}})
        }}
        composeTestRule.onNodeWithText("وحید حسینی").assertIsDisplayed()
        composeTestRule.onNodeWithText("توسعه دهنده ارشد").assertIsDisplayed()
    }}
}}

۱۰. کنترل زمان و تست انیمیشن ها با MainClock

تست انیمیشن های بصری همواره یکی از دغدغه های اصلی توسعه دهندگان بوده است. ابزار تست کامپوز قابلیتی به نام mainClock ارائه می دهد که به شما امکان متوقف سازی ساعت زمان بندی را اعطا می کند:

@Test
fun testAnimationProgress() {{
    composeTestRule.mainClock.autoAdvance = false
    composeTestRule.setContent {{ AnimatedBoxComponent() }}

    // در ثانیه صفر باکس پنهان است
    composeTestRule.onNodeWithTag("animated_box").assertIsNotDisplayed()

    // جلو بردن زمان به میزان ۶۰۰ میلی ثانیه
    composeTestRule.mainClock.advanceTimeBy(600)
    composeTestRule.waitForIdle()

    // تایید نمایان شدن المان پس از پیشروی زمان
    composeTestRule.onNodeWithTag("animated_box").assertIsDisplayed()
    composeTestRule.mainClock.autoAdvance = true
}}

۱۱. پایپ لاین اجرای خودکار تست ها در سرور های CI/CD

برای تضمین سلامت کدهای جدید و جلوگیری از خرابی امکانات قبلی، تست های رابط کاربری باید در پایپ لاین های یکپارچه سازی مداوم به صورت خودکار اجرا شوند.

اجرای تست های کامپوز با شبیه سازهای بدون رابط گرافیکی (Headless Emulators) یا فریم ورک Robolectric بر روی سرور، گزارش های تحلیلی دقیقی از روند پایداری نرم افزار ثبت می کند.

همچنین اگر در کدهای تستی خود نیازمند تست هماهنگی نخ ها و سناریوهای غیرهمگام هستید، راهنمای مدیریت همزمانی در کاتلین با کورتین ها نحوه مدیریت پروسه های غیرهمگام را تشریح کرده است.

۱۲. دیباگ و بررسی درخت معنایی با متد printToLog

یکی از قدرتمندترین ابزار های عیب یابی در کامپوز، متد printToLog است. زمانی که تست شما نمی تواند یک المان را پیدا کند، با چاپ ساختار کامل گره ها در Logcat می توانید دقیقاً متوجه شوید چه ویژگی هایی برای آن گره ثبت شده است.

این متد سلسله مراتب درختی را با جزئیات کامل نمایش می دهد. به طور پیش فرض، درخت ادغام شده چاپ می شود اما می توان با ارسال پارامتر useUnmergedTree = true تمامی زیرگره های خام را نیز مشاهده کرد:

@Test
fun debugSemanticsTreeOutput() {{
    composeTestRule.setContent {{ ComplexDashboardScreen() }}
    
    // چاپ کل درخت معنایی در پنجره لاگ با برچسب تستی
    composeTestRule
        .onRoot(useUnmergedTree = true)
        .printToLog("DEBUG_COMPOSE_TREE")
}}

با بررسی این لاگ، می توان مشاهده کرد که آیا متن ها به درستی ادغام شده اند یا المانی خاص به دلیل نداشتن ویژگی های تعاملی از دسترس خارج شده است. این ابزار زمان عیب یابی تست های پیچیده را تا ۷۰ درصد کاهش می دهد.

۱۳. شبیه سازی ژست های چندلمسی پیچیده با PerformTouchInput

در اپلیکیشن های مدرن، تعاملات کاربر فراتر از یک کلیک ساده است. ژست های لمسی مانند بزرگ نمایی با دو انگشت (Pinch-to-zoom)، کشیدن و رها کردن (Drag and Drop) و لمس طولانی به طور مکرر مورد استفاده قرار می گیرند.

متد performTouchInput بستری کامل برای ارسال رویدادهای دقیق حرکتی فراهم می سازد. شما می توانید مختصات دقیق پیکسل ها و مدت زمان تماس انگشت با صفحه را کنترل نمایید:

@Test
fun simulateDragAndDropGesture() {{
    composeTestRule.setContent {{ DraggableCardScreen() }}
    
    composeTestRule.onNodeWithTag("draggable_item").performTouchInput {{
        down(center)
        moveTo(center + Offset(0f, 300f))
        up()
    }}
    
    composeTestRule.onNodeWithTag("drop_target_zone").assertIsDisplayed()
}}

این سطح از کنترل امکان ارزیابی دقیق رفتارهای فیزیکی رابط کاربری و انیمیشن های شتاب دار را در محیط های آزمون خودکار تضمین می کند.

۱۴. مقایسه تست های رفتاری با تست های بصری (Screenshot Testing)

تست های استانداردی که تا کنون بررسی کردیم رفتاری هستند؛ به این معنا که تعاملات منطقی را ارزیابی می کنند اما متوجه تغییر رنگ ناخواسته یا جابجایی ۵ پیکسلی یک متن نمی شوند. برای تضمین وفاداری بصری کامل، مهندسان از Screenshot Testing استفاده می کنند.

فریم ورک های مدرن نظیر Roborazzi و Paparazzi تصویری از خروجی رندر شده کامپوز ثبت کرده و آن را با تصویر مرجع (Golden Screenshot) مقایسه می نمایند. در صورت کوچک ترین انحراف در پیکسل ها، تست با خطا مواجه می شود:

رویکرد تست هدف اصلی مزیت طلایی محدودیت
تست رفتاری (Compose Rules) سنجش منطق، رویدادها و وضعیت ها سرعت بسیار بالا و مقاومت در برابر تغییرات ظاهری عدم تشخیص خطاهای چیدمان پیکسلی
تست بصری (Roborazzi) بررسی وفاداری کامل به طرح فیگما کشف خودکار پرش های ناخواسته استایل و تم دارک حساسیت بالا به فونت ها و سیستم عامل ها

ترکیب هوشمندانه تست های رفتاری با تست های اسکرین شات، پوشش کیفی پروژه های بزرگ سازمانی را به ۱۰۰ درصد نزدیک می سازد.

۱۵. اصول تست دسترسی پذیری (Accessibility Testing) در کامپوز

یک رابط کاربری استاندارد باید برای تمامی افراد، از جمله کاربران با محدودیت های بینایی یا حرکتی، قابل استفاده باشد. سیستم عامل اندروید از مشخصات درخت معنایی برای هدایت سرویس TalkBack استفاده می کند.

هنگام نوشتن تست های کامپوز، با ارزیابی ویژگی هایی نظیر contentDescription و بررسی اندازه اهداف لمسی می توان دسترسی پذیری نرم افزار را اعتبارسنجی کرد:

@Test
fun verifyIconButton_hasAccessibilityDescription() {{
    composeTestRule.setContent {{
        IconButton(onClick = {{}}) {{
            Icon(Icons.Default.Share, contentDescription = "اشتراک گذاری پست")
        }}
    }}
    
    composeTestRule
        .onNodeWithContentDescription("اشتراک گذاری پست")
        .assertIsDisplayed()
}}

طبق استانداردهای جهانی گوگل، اندازه دکمه های لمسی باید حداقل ۴۸ در ۴۸ دی پی باشد تا کاربر بتواند بدون خطا روی آنها کلیک نماید. با ابزار های تست کامپوز می توان این ابعاد را نیز به شکل برنامه نویسی تایید کرد.

۱۶. ساخت Matcher های سفارشی برای پروژه های بزرگ سازمانی

در پروژه های عظیم که صدها کامپوننت مختلف دارند، نوشتن شروط تکراری موجب شلوغی کدهای تست می شود. در چنین شرایطی، تعریف توابع توسعه پذیر (Extension Functions) برای ساخت Matcher های اختصاصی رویکردی بسیار تمیز است:

fun hasCustomBorderColor(expectedColor: Color): SemanticsMatcher {{
    return SemanticsMatcher("دارای رنگ حاشیه $expectedColor") {{ node ->
        // بررسی متادیتا های ذخیره شده در ویژگی های معنایی
        node.config.getOrNull(BorderColorSemanticsKey) == expectedColor
    }}
}}

با ایجاد این توابع کمکی، تیم های توسعه کدهای تستی بسیار خوانا و روان تولید می کنند که هزینه نگه داری نرم افزار را به حداقل ممکن می رساند.

۱۷. چک لیست مهندسی قبل از ثبت کدهای تست در مخزن گیت

برای جلوگیری از انتشار تست های شکننده و ناپایدار در تیم های اجایل، پیش از ادغام کدها به شاخه اصلی این موارد را بررسی کنید:

  1. عدم استفاده از Thread.sleep: استفاده از تاخیر های ثابت منسوخ است؛ همواره از مکانیزم های زمان بندی خودکار یا waitUntil بهره بگیرید.
  2. استفاده از برچسب های پایدار: به جای متون ترجمه پذیر که ممکن است تغییر کنند، در صفحات پیچیده از Modifier.testTag استفاده نمایید.
  3. ایزوله سازی لایه شبکه: مطمئن شوید هیچ تستی درخواست واقعی به وب سرویس ارسال نمی کند و داده ها از لایه های ماک تامین می شوند.
  4. اجرای سریع: مدت زمان اجرای هر تست قطعه باید زیر ۱ ثانیه باشد تا توسعه دهنده بازخورد آنی دریافت کند.
  5. سازگاری با تم تاریک و روشن: رفتار المان ها را در هر دو حالت Dark Mode و Light Mode اعتبارسنجی کنید تا از کنتراست رنگی مناسب اطمینان حاصل شود.

رعایت دقیق این چک لیست پیش از ایجاد درخواست پول ریکوئست (Pull Request)، ثبات و پایایی پایپ لاین تست را تضمین کرده و از شکست های ناخواسته بیلد های شبانه جلوگیری به عمل می آورد.

۱۸. حل خطاهای متداول و عیب یابی تست های ناپایدار

هنگام اجرای تست های خودکار در مقیاس وسیع، شناخت دلایل شکست آزمون ها از اتلاف زمان جلوگیری می کند:

  1. خطای تایم اوت کامپوز (ComposeTimeoutException): این خطا زمانی اتفاق می افتد که یک انیمیشن با تکرار بی نهایت در صفحه فعال باشد. در این حالت کامپوز به حالت بیکار نمی رسد. راهکار: متوقف کردن ساعت مجازی با mainClock.autoAdvance = false.
  2. خطای چند گره هم نام (Multiple nodes found): این خطا زمانی رخ می دهد که دو المان متن یا تگ یکسان داشته باشند. راهکار: استفاده از عملگرهای ترکیبی یا onAllNodes.onFirst().
  3. خطای پیدا نشدن گره: این مشکل معمولاً ناشی از عدم اسکرول به موقعیت المان در لیست های طولانی است که با متد performScrollToNode برطرف می شود.

۱۹. تزریق وابستگی با Hilt در تست های خودکار کامپوز

در اپلیکیشن های سازمانی، مدیریت وابستگی ها نقشی حیاتی در پایداری سیستم دارد. فریم ورک Hilt ابزار هایی استاندارد برای جایگزینی ماژول های پروداکشن با ماژول های تستی فراهم می کند.

با بهره گیری از حاشیه نویسی @HiltAndroidTest و استفاده از @UninstallModules، می توان دیتابیس های واقعی یا کلاینت های شبکه را با نمونه های حافظه ای (In-Memory) جایگزین کرد تا تست ها در محیطی کاملاً ایزوله اجرا شوند:

@HiltAndroidTest
class ProductFlowIntegrationTest {{
    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    @get:Rule(order = 1)
    val composeTestRule = createAndroidComposeRule()

    @Before
    fun init() {{
        hiltRule.inject()
    }}

    @Test
    fun completeCheckoutFlow_updatesInventory() {{
        composeTestRule.onNodeWithTag("btn_buy_now").performClick()
        composeTestRule.onNodeWithText("سفارش با موفقیت ثبت شد").assertIsDisplayed()
    }}
}}

ترتیب اجرای رول ها با پارامتر order کنترل می شود تا Hilt پیش از ایجاد اکتیویتی کامپوز، وابستگی های لازم را تزریق نماید.

۲۰. بهینه سازی سرعت اجرای تست ها و کش هوشمند گریدل

اجرای صدها تست رابط کاربری می تواند زمان بیلد پروژه را به شدت افزایش دهد. برای دستیابی به بالاترین راندمان، استفاده از مکانیزم های کش گریدل (Gradle Build Cache) و موازی سازی فرآیندها الزامی است.

با تنظیم پارامتر maxParallelForks در فایل تنظیمات گریدل و تفکیک تست های پایدار از سناریوهای وابسته به سرور، زمان اجرای خط لوله تا ۶۰ درصد کاهش می یابد:

tasks.withType {{
    maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
    forkEvery = 100
}}

این رویکرد مصرف حافظه را تثبیت کرده و از فریز شدن شبیه ساز در طول اجرای مداوم آزمون های نرم افزاری جلوگیری می کند. همچنین برای رفع مشکلات گلوگاه بیلد در اندروید استودیو، توصیه می شود راهکارهای افزایش سرعت گریدل در اندروید استودیو را مطالعه نمایید.

۲۱. مدیریت حافظه و پیشگیری از نشت رم در آزمون های رابط کاربری

اجرای متوالی ده ها تست شبیه ساز ممکن است به انباشت اشیای مرده در حافظه هیپ (Heap) و خطای OutOfMemoryError منجر شود. علت اصلی این رخداد، باقی ماندن مراجع اکتیویتی در متغیرهای استاتیک است.

برای مقابله با این معضل، همواره در متد @After مراجع سنگین، کلاینت های شبکه و شنونده های رویداد را پاکسازی نمایید تا جمع آورنده زباله بتواند فضای رم را آزاد سازد:

@After
fun tearDown() {{
    clearAllMocks()
    unmockkAll()
}}

رعایت این اصول به ظاهر ساده، تضمین می کند که مجموعه آزمون های شما ساعت ها بدون کوچک ترین افت سرعت یا توقف ناگهانی به فعالیت ادامه دهند.

۲۲. بنچمارک عملکرد و تست نرخ فریم با Macrobenchmark در کامپوز

علاوه بر تست های رفتاری، ارزیابی کارایی رندرینگ و نرخ ۶۰ فریم بر ثانیه برای تجربه کاربری روان حیاتی است. فریم ورک Macrobenchmark به مهندسان اجازه می دهد تا زمان راه اندازی اپلیکیشن (App Startup) و افت فریم (Jank) هنگام اسکرول را بسنجند.

با ترکیب این تست ها با پروفایل های پایه (Baseline Profiles)، کدهای کامپوز پیش از اولین اجرای کاربر کامپایل می شوند. این امر سرعت استارت برنامه را تا ۴۰ درصد افزایش داده و لکنت های انیمیشن را برطرف می سازد:

@RunWith(AndroidJUnit4::class)
class ScrollPerformanceBenchmark {{
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun measureScrollJank() = benchmarkRule.measureRepeated(
        packageName = "ir.vahidhoseini.app",
        metrics = listOf(FrameTimingMetric()),
        iterations = 5,
        setupBlock = {{ pressHome(); startActivityAndWait() }}
    ) {{
        val list = device.findObject(By.res("products_list"))
        list.scroll(Direction.DOWN, 0.8f)
    }}
}}

معیار FrameTimingMetric تمامی تاخیر های میلی ثانیه ای تحویل فریم ها به نمایشگر را ثبت می کند و در صورت وجود لگ گرافیکی در کدهای کامپوز، گزارش دقیق هشدار را صادر می نماید.

استفاده از فریم ورک Macrobenchmark در کنار پایش شاخص های Android Vitals به توسعه دهندگان اطمینان می دهد که اپلیکیشن همواره در رده برنامه های روان و پایدار قرار دارد و هیچ گونه افت رتبه ای به دلیل لکنت های بصری رخ نمی دهد. این سناریوها معمولاً به صورت شبانه روی دستگاه های واقعی اجرا می گردند.

۲۳. پرسش های متداول پیرامون تست خودکار در کامپوز

۱. تفاوت اصلی بین createComposeRule و createAndroidComposeRule چیست؟

قاعده createComposeRule یک هاست خالی و فوق العاده سبک را بارگذاری می کند که برای تست قطعات و ویجت های منفرد ایده آل است. در مقابل، createAndroidComposeRule یک اکتیویتی واقعی از پروژه را اجرا می کند که برای تست های یکپارچه نیازمند دسترسی به کانتکست و کلاس های اپلیکیشن کاربرد دارد.

۲. چرا نباید از شناسه های سنتی R.id در تست های کامپوز استفاده شود؟

در موتور کامپوز فایل های XML و مفهوم View حذف شده اند و کامپوننت ها به صورت گره هایی در درخت معنایی قرار دارند. به همین دلیل تست ها باید بر اساس مشخصات معنایی مانند متن، دسترسی پذیری یا برچسب تستی المان ها را انتخاب نمایند.

۳. چگونه می توان المان های پایین یک لیست LazyColumn را تست کرد؟

از آنجا که سیستم لودینگ تنبل المان های خارج از دید را رسم نمی کند، جستجوی مستقیم آنها با خطا مواجه می شود. برای انتخاب این گره ها باید از متد performScrollToNode استفاده کرد تا صفحه به شکل خودکار تا رسیدن به گره اسکرول شود.

۴. راهکار جلوگیری از تایم اوت تست در حضور کوروتین های طولانی چیست؟

فریم ورک کامپوز به صورت خودکار با دیسپچرهای کوروتین هماهنگ است، اما برای فرآیندهای پس زمینه خاص می توان از متد composeTestRule.waitUntil استفاده کرد تا شرط مد نظر به محض تحقق ادامه پیدا کند.

۵. آیا امکان اجرای تست های کامپوز روی کامپیوتر بدون شبیه ساز وجود دارد؟

بله، با استفاده از فریم ورک Robolectric می توان تست های رابط کاربری کامپوز را مستقیماً روی محیط JVM کامپیوتر اجرا کرد. این کار سرعت اجرای آزمون ها را تا ده برابر افزایش می دهد.

۶. نقش testTag در توسعه چیست و چه کاربردی دارد؟

متد Modifier.testTag یک شناسه متنی یکتا به گره در درخت معنایی الصاق می کند تا تست ها بتوانند المان های تعاملی بدون متن مشخص را به راحتی شناسایی کنند.

۷. چگونه می توان پیشروی انیمیشن ها را در تست متوقف یا هدایت کرد؟

با غیرفعال سازی پیشروی خودکار ساعت از طریق composeTestRule.mainClock.autoAdvance = false و به کارگیری mainClock.advanceTimeBy می توان زمان اجرای انیمیشن را فریم به فریم جلو برد.

۸. بهترین ساختار برای تست ویومدل در کامپوز کدام است؟

بهترین ساختار استفاده از الگوی State Hoisting است؛ به طوری که صفحه تنها وضعیت را دریافت کرده و رویدادها را مخابره کند. بدین ترتیب می توان صفحه را با داده های فرضی تست کرده و ویومدل را با MockK ماک کرد.

امتیاز و نظرات

۰.۰ (۰ نظر)
برای ثبت دیدگاه باید وارد حساب کاربری شوید. ورود

هنوز نظری ثبت نشده است. اولین نفر باشید.