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

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

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

কোনও পরীক্ষা সিঙ্ক্রোনাইজ করা হলে, আপনার 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(), তখন আপনার পরীক্ষাটি কম্পোজ UI-এর সাথে সিঙ্ক্রোনাইজ করা হয়। কিছু ক্ষেত্রে আপনি হয়ত এই সিঙ্ক্রোনাইজেশন বন্ধ করতে এবং নিজে থেকে ক্লক কন্ট্রোল করতে চাইবেন। যেমন, আপনি এমন একটি পয়েন্টে অ্যানিমেশনের সঠিক স্ক্রিনশট নেওয়ার সময় নিয়ন্ত্রণ করতে পারেন যেখানে UI এখনও ব্যস্ত থাকবে। অটোমেটিক সিঙ্ক্রোনাইজেশন বন্ধ করতে, mainClock-এ autoAdvance প্রপার্টি false হিসেবে সেট করুন:

composeTestRule.mainClock.autoAdvance = false

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

composeTestRule.mainClock.advanceTimeByFrame()
composeTestRule.mainClock.advanceTimeBy(milliseconds)
কন্ট্রোল করে না।

আইডল রিসোর্স

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

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

এই API, Espresso-এর Idling Resources-এর মতো একই ধরনের, এটি দেখায় যে পরীক্ষার অধীনে থাকা সাবজেক্টটি অলস আছে নাকি ব্যস্ত। IdlingResource-এর প্রয়োগ রেজিস্টার করতে কম্পোজ টেস্ট নিয়ম ব্যবহার করুন।

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

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

কিছু ক্ষেত্রে, আপনাকে Compose 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 }

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

অ্যানিমেশন টেস্ট অপ্টিমাইজ করা

হাই-ফিডেলিটি অ্যানিমেশন পরীক্ষা করার সময়, আপনাকে প্রায়ই অটো-অ্যাডভান্স বন্ধ করতে হয় এবং মধ্যবর্তী 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)
            }
        }
    }
}

মূল থ্রেড সিঙ্ক্রোনাইজেশন

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

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

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

কখন মূল থ্রেড সিঙ্ক্রোনাইজেশন ব্যবহার করতে হয়

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

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

উদাহরণ

ঐতিহাসিকভাবে, মূল থ্রেডে সিঙ্ক্রোনাইজেশন কঠোরভাবে নিষিদ্ধ হওয়ায়, ডেভেলপারদের ব্যাকগ্রাউন্ড টেস্ট রানার থ্রেড ও 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")
    }
}

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

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

composeTestRule.waitUntil(timeoutMs) { condition }

এছাড়াও, আপনি যেকোনও waitUntil সহায়ক ব্যবহার করতে পারবেন:

composeTestRule.waitUntilAtLeastOneExists(matcher, timeoutMs)

composeTestRule.waitUntilDoesNotExist(matcher, timeoutMs)

composeTestRule.waitUntilExactlyOneExists(matcher, timeoutMs)

composeTestRule.waitUntilNodeCount(matcher, count, timeoutMs)

অতিরিক্ত রিসোর্স

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