কো-রুটিন এক্সিকিউশনের উপর নিয়ন্ত্রণ উন্নত করার জন্য Compose টেস্টিং API-গুলোর ( createComposeRule , createAndroidComposeRule , runComposeUiTest , runAndroidComposeUiTest , ইত্যাদি) v2 সংস্করণ এখন উপলব্ধ। এই আপডেটে সম্পূর্ণ API সারফেসের পুনরাবৃত্তি করা হয়নি; শুধুমাত্র যে API-গুলো টেস্ট এনভায়রনমেন্ট তৈরি করে, সেগুলোই আপডেট করা হয়েছে।
v1 API-গুলো এখন আর ব্যবহার করা হয় না, এবং নতুন API-গুলোতে স্থানান্তরিত হওয়ার জন্য দৃঢ়ভাবে সুপারিশ করা হচ্ছে। স্থানান্তরিত হলে আপনার টেস্টগুলো স্ট্যান্ডার্ড কো-রুটিন আচরণের সাথে সামঞ্জস্যপূর্ণ থাকে এবং ভবিষ্যতের সামঞ্জস্যজনিত সমস্যা এড়ানো যায়। অপ্রচলিত v1 API-গুলোর তালিকার জন্য, API ম্যাপিং দেখুন।
এই পরিবর্তনগুলো androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ এবং androidx.compose.ui:ui-test:1.11.0-alpha03+ -এ অন্তর্ভুক্ত করা হয়েছে।
v1 API-গুলো UnconfinedTestDispatcher এর উপর নির্ভর করলেও, v2 API-গুলো চলমান কম্পোজিশনের জন্য ডিফল্টরূপে StandardTestDispatcher ব্যবহার করে। এই পরিবর্তনটি Compose টেস্টের আচরণকে স্ট্যান্ডার্ড runTest API-গুলোর সাথে সামঞ্জস্যপূর্ণ করে এবং কো-রুটিন এক্সিকিউশনের ক্রমের উপর সুস্পষ্ট নিয়ন্ত্রণ প্রদান করে।
পরীক্ষার পরিবেশ কনফিগার করুন
Compose test v2 API-গুলো টেস্ট এনভায়রনমেন্ট কাস্টমাইজ করতে ComposeUiTestConfig ব্যবহার করে। যে API-গুলো টেস্টের জন্য সেটআপ ফাংশন তৈরি করে, যেমন createComposeRule , runComposeUiTest এবং অন্যান্য সম্পর্কিত API-গুলো, ComposeUiTestConfig গ্রহণ করে। এই কনফিগারেশন অবজেক্টটি effectContext , runTestContext এবং testTimeout এর মতো পরিবেশ-সম্পর্কিত API-গুলোকে একটিমাত্র অবজেক্টে একত্রিত করে।
কনফিগারেশন মডেলটি inputMode পরিচালনা করে। কম্পোজ টেস্ট v2 এপিআইগুলো প্রতিটি টেস্টের শুরুতে ডিফল্টরূপে InputMode.Touch প্রয়োগ করে, যাতে ডিটারমিনিজম নিশ্চিত করা যায় এবং টেস্টগুলোর মধ্যে ইনপুট মোডের স্টেট ফাঁস হওয়া প্রতিরোধ করা যায়।
ComposeUiTestConfig হলো Compose Test v2 API-এর একটি অংশ, যা ডিফল্টরূপে StandardTestDispatcher ব্যবহার করে। যদি আপনার টেস্টগুলো v1 API ব্যবহার করে থাকে, তবে ComposeUiTestConfig গ্রহণ করার আগে v2 টেস্টিং API-তে মাইগ্রেট করুন (Migrate to v2 testing APIs) দেখুন।
ComposeUiTestConfig এ স্থানান্তরিত করুন
টেস্টে সেটআপ ফাংশন তৈরির ওভারলোডগুলোর মধ্যে, effectContext , runTestContext , বা testTimeout এর মতো স্বতন্ত্র কনফিগারেশন প্যারামিটার গ্রহণকারী বেশ কিছু ওভারলোড এখন আর ব্যবহৃত হয় না। নিচের উদাহরণে দেখানো অনুযায়ী, আপনার টেস্টগুলো আপডেট করে এর পরিবর্তে ComposeUiTestConfig ব্যবহার করুন:
val testConfig = ComposeUiTestConfig( effectContext = EmptyCoroutineContext, runTestContext = EmptyCoroutineContext, testTimeout = 30.seconds ) @get:Rule val rule = createComposeRule(config = testConfig) // OR runComposeUiTest(config = testConfig) {}
ডিফল্ট ইনপুট মোড
টেস্ট শুরু হওয়ার আগে ইন্সট্রুমেন্টেশন এপিআই (API)-এর মাধ্যমে কনফিগার করা নন-টাচ ইনপুট মোডের উপর নির্ভর করলে, মাইগ্রেশনের সময় টেস্টগুলো ব্যর্থ হতে পারে। টেস্টের সেটআপ ফাংশনগুলোর মধ্যে, সিস্টেম আরও বেশি ডিটারমিনিজম (determinism) নিশ্চিত করতে এবং স্টেট লিকেজ (state leakage) প্রতিরোধ করার জন্য প্রতিটি টেস্টের শুরুতে ডিফল্টরূপে InputMode.Touch প্রয়োগ করে, যা অ্যাম্বিয়েন্ট ডিভাইস স্টেট এবং প্রি-টেস্ট সেটআপকে ওভাররাইড করে।
এর সমাধান করতে, ComposeUiTestConfig এ প্রয়োজনীয় ইনপুট মোড নির্দিষ্ট করুন:
class FocusTest { @get:Rule val rule = createComposeRule( config = ComposeUiTestConfig(inputMode = InputMode.Keyboard) ) @Test fun testFocus() {} }
সম্পূর্ণ টেস্ট ক্লাসের পরিবর্তে স্বতন্ত্র টেস্ট কেসগুলির জন্য ইনপুট মোড কনফিগার করতে, runComposeUiTest এ ComposeUiTestConfig পাস করুন:
class FocusTest { @Test fun testTouchMode() = runComposeUiTest { // Runs with the default InputMode.Touch } @Test fun testKeyboardMode() = runComposeUiTest( ComposeUiTestConfig(inputMode = InputMode.Keyboard) ) { // Runs with InputMode.Keyboard } }
অন্যান্য মাইগ্রেশন সমস্যা এবং তার সমাধানের জন্য, ‘সাধারণ ব্যর্থতা এবং সেগুলি কীভাবে সমাধান করবেন’ দেখুন।
v2 টেস্টিং এপিআই-তে স্থানান্তরিত করুন
v2 API-তে আপগ্রেড করার সময়, আপনি সাধারণত Find + Replace ব্যবহার করে প্যাকেজ ইম্পোর্টগুলো আপডেট করতে এবং নতুন ডিসপ্যাচার পরিবর্তনগুলো গ্রহণ করতে পারেন।
বিকল্পভাবে, নিম্নলিখিত প্রম্পটটি ব্যবহার করে জেমিনিকে কম্পোজ টেস্টিং এপিআই-এর v2-তে মাইগ্রেশন করতে বলুন:
এই প্রম্পটটি v2 টেস্টিং এপিআই-তে স্থানান্তরিত হতে এই নির্দেশিকাটি ব্যবহার করবে। এআই প্রম্পট
v1 টেস্টিং এপিআই থেকে v2 টেস্টিং এপিআই-তে মাইগ্রেট করুন
Migrate to Compose testing v2 APIs using the official
migration guide.
বাতিলকৃত v1 API-গুলোকে তাদের v2 প্রতিস্থাপকগুলোর সাথে মেলাতে নিম্নলিখিত সারণিটি ব্যবহার করুন:
অপ্রচলিত (v1) | প্রতিস্থাপন (v2) |
|---|---|
| |
| |
| |
| |
| |
| |
| |
|
পশ্চাৎ সামঞ্জস্য এবং ব্যতিক্রম
বিদ্যমান v1 API-গুলো এখন অপ্রচলিত, কিন্তু পূর্বের আচরণ বজায় রাখতে এবং বড় ধরনের পরিবর্তন রোধ করতে UnconfinedTestDispatcher ব্যবহার অব্যাহত রয়েছে।
নিম্নলিখিতটিই একমাত্র ব্যতিক্রম যেখানে ডিফল্ট আচরণ পরিবর্তিত হয়েছে:
AndroidComposeUiTestEnvironment ক্লাসে কম্পোজিশন চালানোর জন্য ব্যবহৃত ডিফল্ট টেস্ট ডিসপ্যাচারটি UnconfinedTestDispatcher থেকে StandardTestDispatcher এ পরিবর্তিত হয়েছে। এটি সেইসব ক্ষেত্রে প্রভাব ফেলে যেখানে আপনি কনস্ট্রাক্টর ব্যবহার করে একটি ইনস্ট্যান্স তৈরি করেন, অথবা AndroidComposeUiTestEnvironment সাবক্লাস করে সেই কনস্ট্রাক্টরটিকে কল করেন।
মূল পরিবর্তন: কো-রুটিন এক্সিকিউশনের উপর প্রভাব
API-এর v1 এবং v2 এর মধ্যে প্রধান পার্থক্য হলো কো-রুটিনগুলো কীভাবে ডিসপ্যাচ করা হয়:
- v1 API (
UnconfinedTestDispatcher): যখন একটি কো-রুটিন চালু করা হতো, তখন এটি বর্তমান থ্রেডে তাৎক্ষণিকভাবে এক্সিকিউট হতো এবং প্রায়শই টেস্ট কোডের পরবর্তী লাইন চলার আগেই এর কাজ শেষ হয়ে যেত। প্রোডাকশনের আচরণের বিপরীতে, এই তাৎক্ষণিক এক্সিকিউশন একটি লাইভ অ্যাপ্লিকেশনে ঘটতে পারে এমন আসল টাইমিং সমস্যা বা রেস কন্ডিশনকে অনিচ্ছাকৃতভাবে আড়াল করে দিতে পারত। - v2 API (
StandardTestDispatcher): যখন একটি কো-রুটিন চালু করা হয়, তখন এটি কিউতে যুক্ত হয় এবং টেস্টটি স্পষ্টভাবে ভার্চুয়াল ক্লক এগিয়ে না দেওয়া পর্যন্ত এক্সিকিউট হয় না। স্ট্যান্ডার্ড কম্পোজ টেস্ট API (যেমনwaitForIdle()) ইতিমধ্যেই এই সিনক্রোনাইজেশনটি পরিচালনা করে, তাই এই স্ট্যান্ডার্ড API-গুলির উপর নির্ভরশীল বেশিরভাগ টেস্ট কোনো পরিবর্তন ছাড়াই কাজ করতে থাকবে।
সাধারণ ত্রুটি এবং সেগুলি সমাধানের উপায়
v2-তে আপগ্রেড করার পর যদি আপনার টেস্টগুলো ব্যর্থ হয়, তাহলে সেগুলোতে সম্ভবত নিম্নলিখিত প্যাটার্নটি দেখা যাবে:
- ব্যর্থতা : আপনি একটি টাস্ক চালু করেন (উদাহরণস্বরূপ, একটি ViewModel ডেটা লোড করে), কিন্তু আপনার অ্যাসারশনটি সাথে সাথেই ব্যর্থ হয় কারণ ডেটা তখনও "Loading" অবস্থায় থাকে।
- কারণ : v2 API-গুলোতে কো-রুটিনগুলো তাৎক্ষণিকভাবে এক্সিকিউট না হয়ে কিউ-তে যুক্ত হয়। টাস্কটি কিউ-তে যুক্ত হলেও, ফলাফল যাচাই করার আগে এটি আসলে কখনও রান হয়নি।
- সংশোধন : সময়কে স্পষ্টভাবে এগিয়ে দিন। কখন কাজটি সম্পাদন করতে হবে, তা আপনাকে অবশ্যই v2 ডিসপ্যাচারকে স্পষ্টভাবে জানিয়ে দিতে হবে।
পূর্ববর্তী পদ্ধতি
v1-এ, টাস্কটি চালু হয়ে সাথে সাথেই শেষ হয়ে যেত। v2-এ, নিম্নলিখিত কোডটি ব্যর্থ হয় কারণ loadData() আসলে এখনও রান করেনি।
// In v1, this launched and finished immediately.
viewModel.loadData()
// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)
সুপারিশকৃত পদ্ধতি
অ্যাসার্ট করার আগে কিউতে থাকা টাস্কগুলো সম্পাদন করতে waitForIdle বা runOnIdle ব্যবহার করুন।
বিকল্প ১ : waitForIdle ব্যবহার করলে UI নিষ্ক্রিয় না হওয়া পর্যন্ত ঘড়ি এগিয়ে যায়, যা কো-রুটিনটি চলেছে কিনা তা যাচাই করে।
viewModel.loadData()
// Explicitly run all queued tasks
composeTestRule.waitForIdle()
assertEquals(Success, viewModel.state.value)
বিকল্প ২ : runOnIdle ব্যবহার করলে UI নিষ্ক্রিয় হয়ে যাওয়ার পর UI থ্রেডে কোড ব্লকটি এক্সিকিউট হয়।
viewModel.loadData()
// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
assertEquals(Success, viewModel.state.value)
}
ম্যানুয়াল সিঙ্ক্রোনাইজেশন
ম্যানুয়াল সিঙ্ক্রোনাইজেশন জড়িত পরিস্থিতিতে, যেমন যখন অটো-অ্যাডভান্সিং নিষ্ক্রিয় থাকে, তখন একটি কো-রুটিন চালু করলে তা অবিলম্বে কার্যকর হয় না, কারণ টেস্ট ক্লকটি পজ করা থাকে। ভার্চুয়াল ক্লককে এগিয়ে না নিয়ে কিউ-তে থাকা কো-রুটিনগুলো কার্যকর করতে, runCurrent() API ব্যবহার করুন। এটি বর্তমান ভার্চুয়াল সময়ের জন্য নির্ধারিত টাস্কগুলো চালায়।
composeTestRule.mainClock.scheduler.runCurrent()
waitForIdle() এর বিপরীতে, যা UI স্থিতিশীল না হওয়া পর্যন্ত টেস্ট ক্লক এগিয়ে দেয়, runCurrent() বর্তমান ভার্চুয়াল সময় বজায় রেখে অপেক্ষাধীন কাজগুলো সম্পাদন করে। এই আচরণটি এমন মধ্যবর্তী অবস্থাগুলো যাচাই করতে সক্ষম করে, যা ক্লকটি নিষ্ক্রিয় অবস্থায় চলে গেলে বাদ পড়ে যেত।
টেস্ট এনভায়রনমেন্টে ব্যবহৃত অন্তর্নিহিত টেস্ট শিডিউলারটি উন্মুক্ত করা হয়েছে। এই শিডিউলারটি কোটলিন runTest এপিআই (RunTest API)-এর সাথে একত্রে টেস্ট ক্লক সিঙ্ক্রোনাইজ করতে ব্যবহার করা যেতে পারে।
runComposeUiTest এ স্থানান্তরিত করুন
আপনি যদি কোটলিন runTest API-এর পাশাপাশি Compose টেস্ট API ব্যবহার করেন, তাহলে runComposeUiTest এ পরিবর্তন করার জন্য দৃঢ়ভাবে সুপারিশ করা হচ্ছে।
পূর্ববর্তী পদ্ধতি
runTest সাথে createComposeRule ব্যবহার করলে দুটি পৃথক ঘড়ি তৈরি হয়: একটি `Compose`-এর জন্য এবং অন্যটি টেস্ট কো-রুটিন স্কোপের জন্য। এই কনফিগারেশনের কারণে আপনাকে টেস্ট শিডিউলারটি ম্যানুয়ালি সিঙ্ক্রোনাইজ করতে হতে পারে।
@get:Rule val composeTestRule = createComposeRule() @Test fun testWithCoroutines() { composeTestRule.setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } // NOT RECOMMENDED // Fails: runTest creates a new, separate scheduler. // Advancing time here does NOT advance the compose clock. // To fix this without migrating, you would need to share the scheduler // by passing 'composeTestRule.mainClock.scheduler' to runTest. runTest { composeTestRule.onNodeWithText("Loading...").assertIsDisplayed() advanceTimeBy(1000) composeTestRule.onNodeWithText("Done!").assertIsDisplayed() } }
সুপারিশকৃত পদ্ধতি
runComposeUiTest API স্বয়ংক্রিয়ভাবে আপনার টেস্ট ব্লকটিকে তার নিজস্ব runTest স্কোপের মধ্যে এক্সিকিউট করে। টেস্টের ঘড়িটি `Compose` এনভায়রনমেন্টের সাথে সিঙ্ক্রোনাইজ করা থাকে, তাই আপনাকে আর ম্যানুয়ালি শিডিউলার পরিচালনা করতে হয় না।
@Test fun testWithCoroutines() = runComposeUiTest { setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } onNodeWithText("Loading...").assertIsDisplayed() mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */) onNodeWithText("Done!").assertIsDisplayed() } }