আপনার পরীক্ষাগুলি সিঙ্ক্রোনাইজ করুন

কম্পোজ টেস্টগুলো ডিফল্টভাবে আপনার UI-এর সাথে সিঙ্ক্রোনাইজড থাকে। যখন আপনি ComposeTestRule ব্যবহার করে কোনো অ্যাসারশন বা অ্যাকশন কল করেন, তখন টেস্টটি আগে থেকেই সিঙ্ক্রোনাইজ হয়ে যায় এবং UI ট্রি নিষ্ক্রিয় হওয়া পর্যন্ত অপেক্ষা করে।

সাধারণত, আপনাকে কোনো পদক্ষেপ নিতে হবে না। তবে, কিছু ব্যতিক্রমী পরিস্থিতি রয়েছে যা আপনার জেনে রাখা উচিত।

যখন কোনো টেস্ট সিঙ্ক্রোনাইজ করা হয়, তখন একটি ভার্চুয়াল ঘড়ি ব্যবহার করে আপনার Compose অ্যাপটিকে সময়ের দিক থেকে এগিয়ে দেওয়া হয়। এর মানে হলো, Compose টেস্টগুলো রিয়েল টাইমে চলে না, ফলে সেগুলো যত দ্রুত সম্ভব পাস করতে পারে।

তবে, আপনি যদি আপনার টেস্টগুলোকে সিঙ্ক্রোনাইজ করার পদ্ধতিগুলো ব্যবহার না করেন, তাহলে কোনো রিকম্পোজিশন হবে না এবং UI পজ করা অবস্থায় প্রদর্শিত হবে।

@Test
fun counterTest() {
    val myCounter = mutableStateOf(0) // State that can cause recompositions.
    var lastSeenValue = 0 // Used to track recompositions.
    composeTestRule.setContent {
        Text(myCounter.value.toString())
        lastSeenValue = myCounter.value
    }
    myCounter.value = 1 // The state changes, but there is no recomposition.

    // Fails because nothing triggered a recomposition.
    assertTrue(lastSeenValue == 1)

    // Passes because the assertion triggers recomposition.
    composeTestRule.onNodeWithText("1").assertExists()
}

উল্লেখ্য যে, এই শর্তটি শুধুমাত্র কম্পোজ হায়ারার্কিগুলোর ক্ষেত্রে প্রযোজ্য, অ্যাপের বাকি অংশের ক্ষেত্রে নয়।

স্বয়ংক্রিয় সিঙ্ক্রোনাইজেশন নিষ্ক্রিয় করুন

যখন আপনি ComposeTestRule মাধ্যমে assertExists() এর মতো কোনো অ্যাসারশন বা অ্যাকশন কল করেন, তখন আপনার টেস্টটি Compose UI-এর সাথে সিনক্রোনাইজড হয়। কিছু ক্ষেত্রে আপনি এই সিনক্রোনাইজেশন বন্ধ করে ঘড়িটি নিজে নিয়ন্ত্রণ করতে চাইতে পারেন। উদাহরণস্বরূপ, আপনি কোনো অ্যানিমেশনের এমন একটি মুহূর্তের সঠিক স্ক্রিনশট নেওয়ার জন্য সময় নিয়ন্ত্রণ করতে পারেন, যখন UI তখনও ব্যস্ত থাকবে। স্বয়ংক্রিয় সিনক্রোনাইজেশন নিষ্ক্রিয় করতে, mainClock এর autoAdvance প্রপার্টিটি false এ সেট করুন।

composeTestRule.mainClock.autoAdvance = false

সাধারণত এরপর আপনি নিজেই সময় এগিয়ে দেবেন। আপনি advanceTimeByFrame() ব্যবহার করে ঠিক এক ফ্রেম অথবা advanceTimeBy() ব্যবহার করে একটি নির্দিষ্ট সময় পর্যন্ত সময় এগিয়ে নিতে পারেন।

composeTestRule.mainClock.advanceTimeByFrame()
composeTestRule.mainClock.advanceTimeBy(milliseconds)

নিষ্ক্রিয় সম্পদ

কম্পোজ টেস্ট এবং UI-কে এমনভাবে সিঙ্ক্রোনাইজ করতে পারে, যাতে প্রতিটি অ্যাকশন ও অ্যাসারশন একটি নিষ্ক্রিয় অবস্থায় সম্পন্ন হয় এবং প্রয়োজন অনুযায়ী অপেক্ষা করে বা ঘড়ির সময় এগিয়ে দেয়। তবে, কিছু অ্যাসিঙ্ক্রোনাস অপারেশন, যাদের ফলাফল UI-এর অবস্থাকে প্রভাবিত করে, সেগুলো ব্যাকগ্রাউন্ডে চলতে পারে এবং টেস্ট সে সম্পর্কে অবগত থাকে না।

আপনার টেস্টে এই নিষ্ক্রিয় রিসোর্সগুলো তৈরি ও রেজিস্টার করুন, যাতে পরীক্ষাধীন অ্যাপটি ব্যস্ত না নিষ্ক্রিয়, তা নির্ধারণ করার সময় এগুলোকে বিবেচনায় নেওয়া হয়। অতিরিক্ত নিষ্ক্রিয় রিসোর্স রেজিস্টার করার প্রয়োজন না হলে আপনাকে কোনো পদক্ষেপ নিতে হবে না; উদাহরণস্বরূপ, যদি আপনি এমন কোনো ব্যাকগ্রাউন্ড জব চালান যা Espresso বা Compose-এর সাথে সিঙ্ক্রোনাইজ করা নেই।

এই API-টি পরীক্ষাধীন সাবজেক্টটি নিষ্ক্রিয় নাকি ব্যস্ত, তা নির্দেশ করার জন্য Espresso-এর Idling Resources-এর সাথে খুবই সাদৃশ্যপূর্ণ। IdlingResource এর ইমপ্লিমেন্টেশনটি রেজিস্টার করতে Compose টেস্ট রুল ব্যবহার করুন।

composeTestRule.registerIdlingResource(idlingResource)
composeTestRule.unregisterIdlingResource(idlingResource)

ম্যানুয়াল সিঙ্ক্রোনাইজেশন

কিছু ক্ষেত্রে, আপনাকে আপনার পরীক্ষার অন্যান্য অংশের সাথে অথবা যে অ্যাপটি পরীক্ষা করছেন তার সাথে কম্পোজ UI সিঙ্ক্রোনাইজ করতে হয়।

waitForIdle() ফাংশনটি Compose-এর নিষ্ক্রিয় হওয়ার জন্য অপেক্ষা করে, কিন্তু ফাংশনটি autoAdvance প্রপার্টির উপর নির্ভরশীল:

composeTestRule.mainClock.autoAdvance = true // Default
composeTestRule.waitForIdle() // Advances the clock until Compose is idle.

composeTestRule.mainClock.autoAdvance = false
composeTestRule.waitForIdle() // Only waits for idling resources to become idle.

উল্লেখ্য যে, উভয় ক্ষেত্রেই waitForIdle() অপেক্ষমান ড্র এবং লেআউট পাসের জন্যও অপেক্ষা করে।

এছাড়াও, advanceTimeUntil() ব্যবহার করে আপনি একটি নির্দিষ্ট শর্ত পূরণ না হওয়া পর্যন্ত ঘড়িকে এগিয়ে দিতে পারেন।

composeTestRule.mainClock.advanceTimeUntil(timeoutMs) { condition }

উল্লেখ্য যে, প্রদত্ত শর্তটি এমন একটি অবস্থা যাচাই করবে যা এই ঘড়ি দ্বারা প্রভাবিত হতে পারে (এটি শুধুমাত্র Compose অবস্থার সাথেই কাজ করে)।

অ্যানিমেশন পরীক্ষা অপ্টিমাইজ করুন

উচ্চ-মানের অ্যানিমেশন পরীক্ষা করার সময়, প্রায়শই অটো-অ্যাডভান্স নিষ্ক্রিয় করে মধ্যবর্তী UI অবস্থা যাচাই করার জন্য ম্যানুয়ালি ফ্রেমগুলো পরীক্ষা করার প্রয়োজন হয়। এই নির্দিষ্ট ফ্রেম-বাই-ফ্রেম লুপগুলোর জন্য, আপনার অ্যাসারশনগুলো কার্যকর করতে runWithoutImplicitWait মেথডটি ব্যবহার করুন। সাধারণ নোড কোয়েরিগুলো (যেমন onNodeWithTag বা fetchSemanticsNode ) এমন কিছু অন্তর্নিহিত সিঙ্ক্রোনাইজেশন চালু করে যা ঘড়ি ম্যানুয়ালি নিয়ন্ত্রণ করার সময় অপ্রয়োজনীয় হয়ে পড়ে, তাই এগুলোকে এড়িয়ে গেলে আপনার টেস্ট রানটাইম উল্লেখযোগ্যভাবে দ্রুত হয়।

ব্যবহারের নির্দেশিকা

  • ম্যানুয়াল ক্লক ম্যানেজমেন্ট : যখন mainClock.autoAdvance মান false সেট করা থাকে এবং বর্তমান ফ্রেমের জন্য UI একটি জ্ঞাত ও স্থিতিশীল অবস্থায় থাকে, তখন এই API-টি ব্যবহার করুন।
  • UI থ্রেড এক্সিকিউশন : UI ট্রি-এর স্থিতিশীলতা নিশ্চিত করতে, UI থ্রেডে runWithoutImplicitWait কল করুন, যেমন runOnUiThread ব্যবহার করে। UI থ্রেডের বাইরে এটি চালালে আপনার টেস্ট রেস কন্ডিশন এবং স্টেল স্টেট রিডের ঝুঁকিতে পড়ে।
  • শুধুমাত্র পঠনযোগ্য অ্যাসারশন : ব্লকটিতে কঠোরভাবে শুধুমাত্র পঠনযোগ্য অ্যাসারশন থাকতে হবে। স্টেট পরিবর্তনকারী যেকোনো কাজ এই ব্লকের বাইরে সম্পাদন করা উচিত।

উদাহরণ

@Test
fun runWithoutImplicitWaitSample() = runComposeUiTest {
    setContent { MainScreen() }
    mainClock.autoAdvance = false

    // Trigger an animation
    onNodeWithText("Start Animation").performClick()

    // Step through the animation frame-by-frame
    while (hasPendingWork()) {
        mainClock.advanceTimeByFrame()
        waitForIdle()
        runOnUiThread {
            // Suppress implicit synchronization inside this block to avoid redundant
            // waits on each node query, making the frame assertions execute much faster.
            runWithoutImplicitWait {
                val box1 = onNodeWithTag("Box1").fetchSemanticsNode()
                val box2 = onNodeWithTag("Box2").fetchSemanticsNode()
                val box3 = onNodeWithTag("Box3").fetchSemanticsNode()

                // Assert the exact intermediate state of all three properties for this frame
                assert(box1.boundsInRoot.right <= box2.boundsInRoot.left)
                assert(box2.boundsInRoot.right <= box3.boundsInRoot.left)
            }
        }
    }
}

প্রধান থ্রেড সিঙ্ক্রোনাইজেশন

কম্পোজ টেস্টিং এখন মেইন থ্রেড সিনক্রোনাইজেশন সমর্থন করে, যার ফলে আপনি নিরাপদে সরাসরি মেইন থ্রেড থেকে waitForIdle — এবং এর ফলস্বরূপ, কম্পোজ UI অ্যাকশন ও অ্যাসারশন — কল করতে পারবেন।

পূর্বে, কম্পোজ টেস্টিং কঠোরভাবে একটি দ্বি-থ্রেড মডেল প্রয়োগ করত: টেস্ট এক্সিকিউশন একটি ব্যাকগ্রাউন্ড টেস্ট থ্রেডে ঘটত, আর UI আপডেটগুলো হতো মেইন থ্রেডে। মেইন থ্রেড থেকে waitForIdle বা runOnIdle মতো সিনক্রোনাইজেশন মেথড কল করলে (উদাহরণস্বরূপ, একটি runOnUiThread ব্লকের ভিতরে) একটি IllegalStateException থ্রো হতো, কারণ ফ্রেমওয়ার্কটি মেইন-থ্রেড সিনক্রোনাইজেশন প্রতিরোধ করার জন্য কঠোর থ্রেড চেক প্রয়োগ করত।

মেইন থ্রেড সিনক্রোনাইজেশন সক্রিয় থাকলে, কম্পোজ টেস্ট ফ্রেমওয়ার্ক এখন মেইন থ্রেডে ব্লকিং কল করা হলেও ঘড়িকে এগিয়ে দিতে এবং অপেক্ষমান কাজ প্রক্রিয়া করতে পারে।

কখন প্রধান থ্রেড সিঙ্ক্রোনাইজেশন ব্যবহার করতে হবে

যদিও বিশুদ্ধ কম্পোজ টেস্টের ক্ষেত্রে ব্যাকগ্রাউন্ড থ্রেডে টেস্ট রাখাটাই প্রচলিত, কয়েকটি নির্দিষ্ট পরিস্থিতিতে মেইন থ্রেড সিনক্রোনাইজেশন অত্যন্ত সুবিধাজনক:

  • জটিল ভিউ আন্তঃকার্যক্ষমতা : কম্পোজ এবং পুরোনো অ্যান্ড্রয়েড ভিউ উভয়ই ধারণকারী হাইব্রিড UI পরীক্ষা করার সময়, ভিউ পরিচালনা করার জন্য প্রায়শই মেইন থ্রেডে চালানোর প্রয়োজন হয়। এখন আপনি ক্রমাগত থ্রেড কনটেক্সট পরিবর্তন না করেই ক্রমানুসারে ভিউগুলির সাথে ইন্টারঅ্যাক্ট করতে এবং কম্পোজ নোডগুলিতে অ্যাসার্ট করতে পারবেন।
  • সিঙ্ক্রোনাস স্টেট পরিবর্তন : যদি আপনার আর্কিটেকচার কঠোরভাবে মেইন-থ্রেড-ভিত্তিক স্টেট হোল্ডারদের উপর নির্ভর করে, তাহলে আপনি এখন মেইন থ্রেড থেকে বের না হয়েই স্টেট পরিবর্তন করতে এবং অবিলম্বে কম্পোজ UI স্থিতিশীল হওয়ার জন্য অপেক্ষা করতে পারবেন।
  • কাস্টম টেস্ট রানার : আপনি যদি কাস্টম টেস্টিং পরিকাঠামো তৈরি করেন বা এমন পরিবেশ ব্যবহার করেন যেখানে টেস্ট রানার স্বাভাবিকভাবেই প্রধান থ্রেডে চলে, তাহলে এখন কম্পোজ টেস্টগুলো ব্যাকগ্রাউন্ড-থ্রেড ডেলিগেশনের প্রয়োজন ছাড়াই নির্বিঘ্নে এক্সিকিউট হয়।

উদাহরণ

ঐতিহাসিকভাবে, যেহেতু প্রধান থ্রেডে সিনক্রোনাইজেশন কঠোরভাবে নিষিদ্ধ ছিল, তাই ডেভেলপারদের ব্যাকগ্রাউন্ড টেস্ট রানার থ্রেড এবং UI থ্রেডের মধ্যে বারবার আসা-যাওয়া করতে হতো, যার ফলে পরীক্ষাগুলো অসংলগ্ন হয়ে পড়ত:

@Test
fun testBidirectionalInteropUIUpdates_old() {
    val scenario = launchFragmentInContainer<InteropFragment>()
    composeTestRule.waitForIdle()
    scenario.onFragment { fragment ->
        fragment.legacyButton.performClick()
    }
    // Jump to Test Thread to verify state settles inside compose
    composeTestRule.waitForIdle()
    composeTestRule.onNodeWithText("Legacy Clicks: 1").assertIsDisplayed()
    composeTestRule.onNodeWithText("Increment Legacy TextView").performClick()
    composeTestRule.waitForIdle()
    // Jump back to Main Thread to verify target view state settles
    scenario.onFragment { fragment ->
        assert(fragment.legacyTextView.text.toString() == "Compose Clicks: 1")
    }
}

প্রধান থ্রেড সিঙ্ক্রোনাইজেশন সক্রিয় থাকলে, Compose এবং View হায়ারার্কির জন্য অ্যাসারশনগুলো একই ব্লকে চালানো যেতে পারে:

@Test
fun testBidirectionalInteropUIUpdates_new() {
    val scenario = launchFragmentInContainer<InteropFragment>()
    composeTestRule.waitForIdle()
    scenario.onFragment { fragment ->
        fragment.legacyButton.performClick()
        composeTestRule.waitForIdle()
        composeTestRule.onNodeWithText("Legacy Clicks: 1").assertIsDisplayed()
        composeTestRule.onNodeWithText("Increment Legacy TextView").performClick()
        composeTestRule.waitForIdle()
        assert(fragment.legacyTextView.text.toString() == "Compose Clicks: 1")
    }
}

শর্তের জন্য অপেক্ষা করুন

যে কোনো শর্ত যা বাহ্যিক কাজের উপর নির্ভর করে, যেমন ডেটা লোডিং বা অ্যান্ড্রয়েডের মেজার বা ড্র (অর্থাৎ, কম্পোজের বাইরের মেজার বা ড্র), সেগুলোর জন্য waitUntil() এর মতো আরও সাধারণ ধারণা ব্যবহার করা উচিত:

composeTestRule.waitUntil(timeoutMs) { condition }

আপনি যেকোনো waitUntil হেল্পারও ব্যবহার করতে পারেন:

composeTestRule.waitUntilAtLeastOneExists(matcher, timeoutMs)

composeTestRule.waitUntilDoesNotExist(matcher, timeoutMs)

composeTestRule.waitUntilExactlyOneExists(matcher, timeoutMs)

composeTestRule.waitUntilNodeCount(matcher, count, timeoutMs)

অতিরিক্ত সম্পদ

  • অ্যান্ড্রয়েডে অ্যাপ পরীক্ষা করুন : অ্যান্ড্রয়েড টেস্টিং-এর প্রধান ল্যান্ডিং পেজটি টেস্টিং-এর মৌলিক বিষয় এবং কৌশল সম্পর্কে একটি বিস্তৃত ধারণা প্রদান করে।
  • টেস্টিংয়ের মূল বিষয়সমূহ : একটি অ্যান্ড্রয়েড অ্যাপ টেস্টিং করার পেছনের মূল ধারণাগুলো সম্পর্কে আরও জানুন।
  • স্থানীয় পরীক্ষা : আপনি কিছু পরীক্ষা স্থানীয়ভাবে, আপনার নিজের ওয়ার্কস্টেশনে চালাতে পারেন।
  • ইনস্ট্রুমেন্টেড টেস্ট : ইনস্ট্রুমেন্টেড টেস্ট চালানোও একটি ভালো অভ্যাস। অর্থাৎ, যে টেস্টগুলো সরাসরি ডিভাইসে চলে।
  • কন্টিনিউয়াস ইন্টিগ্রেশন : কন্টিনিউয়াস ইন্টিগ্রেশন আপনাকে আপনার টেস্টগুলোকে ডেপ্লয়মেন্ট পাইপলাইনে একীভূত করতে দেয়।
  • বিভিন্ন স্ক্রিন সাইজ পরীক্ষা করুন : ব্যবহারকারীদের জন্য যেহেতু অনেক ডিভাইস উপলব্ধ আছে, তাই আপনার বিভিন্ন স্ক্রিন সাইজ পরীক্ষা করে দেখা উচিত।
  • এসপ্রেসো : যদিও এটি ভিউ-ভিত্তিক UI-এর জন্য তৈরি, কম্পোজ টেস্টিং-এর কিছু ক্ষেত্রে এসপ্রেসো সম্পর্কিত জ্ঞান সহায়ক হতে পারে।