coroutines ব্যবহার করে এমন ইউনিট টেস্টিং কোডের জন্য কিছু বাড়তি মনোযোগের প্রয়োজন, কারণ তাদের সম্পাদন অসিঙ্ক্রোনাস হতে পারে এবং একাধিক থ্রেড জুড়ে ঘটতে পারে। এই গাইডটি কভার করে যে কীভাবে সাসপেন্ডিং ফাংশনগুলি পরীক্ষা করা যেতে পারে, টেস্টিং কনস্ট্রাক্টগুলির সাথে আপনাকে পরিচিত হতে হবে এবং কীভাবে আপনার কোডগুলিকে পরীক্ষাযোগ্য করে তুলতে হয়
এই গাইডে ব্যবহৃত APIগুলি kotlinx.coroutines.test লাইব্রেরির অংশ। এই APIগুলিতে অ্যাক্সেস পেতে আপনার প্রকল্পে একটি পরীক্ষা নির্ভরতা হিসাবে আর্টিফ্যাক্ট যোগ করার বিষয়টি নিশ্চিত করুন।
dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
}
পরীক্ষায় স্থগিত ফাংশন আহ্বান করা
পরীক্ষায় সাসপেন্ডিং ফাংশন কল করার জন্য, আপনাকে একটি করুটিনে থাকতে হবে। যেহেতু JUnit পরীক্ষার ফাংশনগুলি নিজেরাই ফাংশনগুলিকে স্থগিত করছে না, তাই একটি নতুন করোটিন শুরু করার জন্য আপনাকে আপনার পরীক্ষার ভিতরে একজন করটিন নির্মাতাকে কল করতে হবে।
runTest
পরীক্ষার জন্য ডিজাইন করা একটি কোরাউটিন নির্মাতা। কোরোটিন অন্তর্ভুক্ত যে কোনো পরীক্ষা মোড়ানোর জন্য এটি ব্যবহার করুন। মনে রাখবেন যে কোরোটিনগুলি শুধুমাত্র পরীক্ষায় সরাসরি নয়, পরীক্ষায় ব্যবহৃত বস্তুর দ্বারাও শুরু করা যেতে পারে।
suspend fun fetchData(): String { delay(1000L) return "Hello world" } @Test fun dataShouldBeHelloWorld() = runTest { val data = fetchData() assertEquals("Hello world", data) }
সাধারণভাবে, আপনার প্রতি পরীক্ষায় runTest
একটি আহ্বান থাকা উচিত এবং একটি এক্সপ্রেশন বডি ব্যবহার করার পরামর্শ দেওয়া হয়।
runTest
আপনার পরীক্ষার কোড মোড়ানো বেসিক সাসপেন্ডিং ফাংশন পরীক্ষা করার জন্য কাজ করবে, এবং এটি স্বয়ংক্রিয়ভাবে কোরোটিনে যেকোনো বিলম্ব এড়িয়ে যাবে, উপরের পরীক্ষাটি এক সেকেন্ডের চেয়ে অনেক দ্রুত সম্পন্ন করবে।
যাইহোক, পরীক্ষার অধীনে আপনার কোডে কী ঘটবে তার উপর নির্ভর করে অতিরিক্ত বিবেচনা করতে হবে:
-
runTest
তৈরি করা টপ-লেভেল টেস্ট করুটিন ছাড়া আপনার কোড যখন নতুন কোরোটিন তৈরি করে, তখন উপযুক্তTestDispatcher
বেছে নিয়ে সেই নতুন কোরোটিনগুলি কীভাবে নির্ধারিত হয় তা নিয়ন্ত্রণ করতে হবে। - যদি আপনার কোড অন্যান্য প্রেরকদের (উদাহরণস্বরূপ,
withContext
ব্যবহার করে) coroutine এক্সিকিউশনকে সরিয়ে দেয় তবেrunTest
এখনও সাধারণত কাজ করবে, কিন্তু বিলম্ব আর এড়িয়ে যাবে না, এবং একাধিক থ্রেডে কোড চালানোর কারণে পরীক্ষাগুলি কম অনুমানযোগ্য হবে। এই কারণে, পরীক্ষায় আপনার প্রকৃত প্রেরণকারীদের প্রতিস্থাপনের জন্য পরীক্ষা প্রেরকদের ইনজেকশন করা উচিত।
টেস্ট ডিসপ্যাচার
TestDispatchers
হল পরীক্ষার উদ্দেশ্যে CoroutineDispatcher
বাস্তবায়ন। নতুন করোটিনগুলিকে অনুমানযোগ্য করে তোলার জন্য পরীক্ষার সময় নতুন coroutines তৈরি করা হলে আপনাকে TestDispatchers
ব্যবহার করতে হবে।
TestDispatcher
এর দুটি উপলব্ধ বাস্তবায়ন রয়েছে: StandardTestDispatcher
এবং UnconfinedTestDispatcher
, যা নতুন শুরু হওয়া কোরোটিনগুলির বিভিন্ন সময়সূচী সম্পাদন করে। এই উভয়ই ভার্চুয়াল সময় নিয়ন্ত্রণ করতে এবং পরীক্ষার মধ্যে চলমান কোরোটিন পরিচালনা করতে একটি TestCoroutineScheduler
ব্যবহার করে।
একটি পরীক্ষায় শুধুমাত্র একটি শিডিউলারের দৃষ্টান্ত ব্যবহার করা উচিত , যা সমস্ত TestDispatchers
এর মধ্যে ভাগ করা হয়। শেয়ারিং শিডিউলার সম্পর্কে জানতে টেস্ট ডিসপ্যাচার ইনজেকশন করা দেখুন।
শীর্ষ-স্তরের পরীক্ষা করুটিন শুরু করতে, runTest
একটি TestScope
তৈরি করে, যা CoroutineScope
এর একটি বাস্তবায়ন যা সর্বদা একটি TestDispatcher
ব্যবহার করবে। নির্দিষ্ট করা না থাকলে, একটি TestScope
ডিফল্টরূপে একটি StandardTestDispatcher
তৈরি করবে, এবং এটিকে উচ্চ-স্তরের পরীক্ষা করুটিন চালানোর জন্য ব্যবহার করবে।
runTest
তার TestScope
এর প্রেরক দ্বারা ব্যবহৃত শিডিউলারে সারিবদ্ধ কোরোটিনগুলির ট্র্যাক রাখে এবং যতক্ষণ পর্যন্ত সেই শিডিউলারের কাজ মুলতুবি থাকে ততক্ষণ ফিরে আসবে না।
স্ট্যান্ডার্ডটেস্ট ডিসপ্যাচার
আপনি যখন একটি StandardTestDispatcher
এ নতুন কোরোটিন শুরু করেন, তখন সেগুলি অন্তর্নিহিত শিডিউলারের সাথে সারিবদ্ধ থাকে, যখনই পরীক্ষার থ্রেডটি ব্যবহার করার জন্য বিনামূল্যে থাকে তখন চালানো হবে৷ এই নতুন coroutines চালানোর জন্য, আপনাকে পরীক্ষার থ্রেড দিতে হবে (অন্যান্য coroutines ব্যবহারের জন্য এটি মুক্ত করুন)। এই সারিবদ্ধ আচরণ আপনাকে পরীক্ষার সময় কীভাবে নতুন করোটিন চালানো হয় তার উপর সুনির্দিষ্ট নিয়ন্ত্রণ দেয় এবং এটি প্রোডাকশন কোডে করোটিনগুলির সময়সূচীর অনুরূপ।
টপ-লেভেল টেস্ট করুটিন চালানোর সময় যদি টেস্ট থ্রেড কখনও পাওয়া না যায়, তবে যেকোন নতুন কোরোটিন শুধুমাত্র টেস্ট করুটিন সম্পন্ন হওয়ার পরেই চলবে (কিন্তু runTest
রিটার্নের আগে):
@Test fun standardTest() = runTest { val userRepo = UserRepository() launch { userRepo.register("Alice") } launch { userRepo.register("Bob") } assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ❌ Fails }
সারিবদ্ধ কোরোটিনগুলি চালানোর জন্য পরীক্ষার করটিন দেওয়ার বিভিন্ন উপায় রয়েছে। এই সমস্ত কল অন্য কোরোটিনগুলিকে ফেরত আসার আগে পরীক্ষার থ্রেডে চলতে দেয়:
-
advanceUntilIdle
: সারিতে কিছু অবশিষ্ট না থাকা পর্যন্ত সময়সূচীতে অন্যান্য সমস্ত কোরোটিন চালায়। সমস্ত মুলতুবি থাকা কোরোটিনগুলি চালানোর জন্য এটি একটি ভাল ডিফল্ট পছন্দ এবং এটি বেশিরভাগ পরীক্ষার পরিস্থিতিতে কাজ করবে। -
advanceTimeBy
: প্রদত্ত পরিমাণের দ্বারা ভার্চুয়াল সময়কে অগ্রসর করে এবং ভার্চুয়াল সময়ে সেই বিন্দুর আগে চালানোর জন্য নির্ধারিত যেকোন কোরোটিন চালায়। -
runCurrent
: বর্তমান ভার্চুয়াল সময়ে নির্ধারিত coroutines চালায়।
পূর্ববর্তী পরীক্ষাটি ঠিক করতে, advanceUntilIdle
ব্যবহার করা যেতে পারে দাবিটি চালিয়ে যাওয়ার আগে দুটি মুলতুবি থাকা কোরোটিনকে তাদের কাজ সম্পাদন করতে দিতে:
@Test fun standardTest() = runTest { val userRepo = UserRepository() launch { userRepo.register("Alice") } launch { userRepo.register("Bob") } advanceUntilIdle() // Yields to perform the registrations assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes }
UnconfinedTestDispatcher
যখন একটি UnconfinedTestDispatcher
এ নতুন coroutines শুরু করা হয়, তখন সেগুলি বর্তমান থ্রেডে সাগ্রহে শুরু হয়। এর মানে হল যে তারা অবিলম্বে দৌড়ানো শুরু করবে, তাদের করুটিন নির্মাতার ফিরে আসার জন্য অপেক্ষা না করে। অনেক ক্ষেত্রে, এই প্রেরণের আচরণের ফলে সহজ পরীক্ষা কোড হয়, কারণ নতুন কোরোটিন চালানোর জন্য আপনাকে ম্যানুয়ালি পরীক্ষার থ্রেড দিতে হবে না।
যাইহোক, এই আচরণ আপনি অ-পরীক্ষা প্রেরণকারীদের সাথে উত্পাদনে যা দেখতে পাবেন তার থেকে আলাদা। যদি আপনার পরীক্ষা একযোগে ফোকাস করে, তাহলে পরিবর্তে StandardTestDispatcher
ব্যবহার করতে পছন্দ করুন।
ডিফল্টের পরিবর্তে runTest
শীর্ষ-স্তরের পরীক্ষা করুটিনের জন্য এই প্রেরণকারীটি ব্যবহার করতে, একটি উদাহরণ তৈরি করুন এবং এটিকে একটি প্যারামিটার হিসাবে পাস করুন৷ এটি runTest
মধ্যে তৈরি করা নতুন কোরোটিনগুলিকে সাগ্রহে কার্যকর করবে, কারণ তারা TestScope
থেকে প্রেরককে উত্তরাধিকারী করে।
@Test fun unconfinedTest() = runTest(UnconfinedTestDispatcher()) { val userRepo = UserRepository() launch { userRepo.register("Alice") } launch { userRepo.register("Bob") } assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes }
এই উদাহরণে, লঞ্চ কলগুলি UnconfinedTestDispatcher
এ তাদের নতুন coroutines শুরু করবে, যার অর্থ হল লঞ্চের প্রতিটি কল শুধুমাত্র নিবন্ধন সম্পন্ন হওয়ার পরেই ফিরে আসবে।
মনে রাখবেন যে UnconfinedTestDispatcher
সাগ্রহে নতুন coroutines শুরু করে, কিন্তু এর মানে এই নয় যে এটি সেগুলিকেও সাগ্রহে সম্পূর্ণ করার জন্য চালাবে। যদি নতুন কোরোটিন স্থগিত হয়, অন্য কোরোটিনগুলি আবার কার্যকর করা শুরু করবে।
উদাহরণস্বরূপ, এই পরীক্ষার মধ্যে চালু করা নতুন কোরোটিন অ্যালিসকে নিবন্ধন করবে, কিন্তু delay
হলে কল করা হলে তা স্থগিত হয়ে যাবে। এটি উচ্চ-স্তরের কোরোটিনকে দাবির সাথে এগিয়ে যেতে দেয় এবং বব এখনও নিবন্ধিত না হওয়ায় পরীক্ষা ব্যর্থ হয়:
@Test fun yieldingTest() = runTest(UnconfinedTestDispatcher()) { val userRepo = UserRepository() launch { userRepo.register("Alice") delay(10L) userRepo.register("Bob") } assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ❌ Fails }
ইনজেকশন পরীক্ষা প্রেরক
পরীক্ষার অধীনে কোড থ্রেড পরিবর্তন করতে ( withContext
ব্যবহার করে) বা নতুন কোরোটিন শুরু করতে প্রেরকদের ব্যবহার করতে পারে। যখন কোড একাধিক থ্রেডে সমান্তরালভাবে কার্যকর করা হয়, পরীক্ষাগুলি ফ্ল্যাকি হতে পারে। সঠিক সময়ে দাবী করা কঠিন হতে পারে বা কাজগুলি সম্পূর্ণ হওয়ার জন্য অপেক্ষা করা কঠিন হতে পারে যদি সেগুলি ব্যাকগ্রাউন্ড থ্রেডগুলিতে চলছে যা আপনার নিয়ন্ত্রণ নেই।
পরীক্ষায়, এই প্রেরণকারীদের TestDispatchers
এর উদাহরণ দিয়ে প্রতিস্থাপন করুন। এটির বেশ কয়েকটি সুবিধা রয়েছে:
- কোডটি একক পরীক্ষার থ্রেডে চলবে, পরীক্ষাগুলিকে আরও নির্ধারক করে তুলবে
- আপনি নিয়ন্ত্রণ করতে পারেন কিভাবে নতুন করোটিনগুলি নির্ধারিত এবং কার্যকর করা হয়
- TestDispatchers ভার্চুয়াল সময়ের জন্য একটি সময়সূচী ব্যবহার করে, যা স্বয়ংক্রিয়ভাবে বিলম্ব এড়িয়ে যায় এবং আপনাকে ম্যানুয়ালি সময় অগ্রসর করতে দেয়
আপনার ক্লাসে প্রেরকদের প্রদানের জন্য নির্ভরতা ইনজেকশন ব্যবহার করে পরীক্ষায় প্রকৃত প্রেরণকারীদের প্রতিস্থাপন করা সহজ করে তোলে। এই উদাহরণগুলিতে, আমরা একটি CoroutineDispatcher
ইনজেকশন করব, তবে আপনি বিস্তৃত CoroutineContext
প্রকারটিও ইনজেকশন করতে পারেন, যা পরীক্ষার সময় আরও বেশি নমনীয়তার জন্য অনুমতি দেয়।
যে ক্লাসগুলি কোরোটিনগুলি শুরু করে, আপনি প্রেরণকারীর পরিবর্তে একটি CoroutineScope
ইনজেকশনও করতে পারেন, যেমনটি ইনজেকশন একটি স্কোপ বিভাগে বিস্তারিত রয়েছে।
TestDispatchers
, ডিফল্টরূপে, একটি নতুন সময়সূচী তৈরি করবে যখন তারা তাৎক্ষণিকভাবে কাজ করবে। runTest
এর ভিতরে, আপনি TestScope
এর testScheduler
প্রপার্টি অ্যাক্সেস করতে পারেন এবং এটি নতুন তৈরি করা TestDispatchers
এ পাস করতে পারেন। এটি ভার্চুয়াল সময় সম্পর্কে তাদের বোঝাপড়া ভাগ করে নেবে, এবং advanceUntilIdle
মতো পদ্ধতিগুলি সমস্ত পরীক্ষা প্রেরকদের সম্পূর্ণ করার জন্য কোরোটিন চালাবে।
নিম্নলিখিত উদাহরণে, আপনি একটি Repository
ক্লাস দেখতে পাচ্ছেন যা IO
প্রেরককে এর initialize
পদ্ধতিতে ব্যবহার করে একটি নতুন কোরোটিন তৈরি করে এবং কলারকে তার fetchData
পদ্ধতিতে IO
প্রেরণকারীতে স্যুইচ করে:
// Example class demonstrating dispatcher use cases class Repository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) { private val scope = CoroutineScope(ioDispatcher) val initialized = AtomicBoolean(false) // A function that starts a new coroutine on the IO dispatcher fun initialize() { scope.launch { initialized.set(true) } } // A suspending function that switches to the IO dispatcher suspend fun fetchData(): String = withContext(ioDispatcher) { require(initialized.get()) { "Repository should be initialized first" } delay(500L) "Hello world" } }
পরীক্ষায়, আপনি IO
প্রেরণকারীকে প্রতিস্থাপন করতে একটি TestDispatcher
বাস্তবায়ন ইনজেক্ট করতে পারেন।
নীচের উদাহরণে, আমরা রিপোজিটরিতে একটি StandardTestDispatcher
ইনজেক্ট করি এবং initialize
হওয়ার আগে শুরু হওয়া নতুন coroutine সম্পূর্ণ হয়েছে কিনা তা নিশ্চিত করতে advanceUntilIdle
ব্যবহার করি।
fetchData
একটি TestDispatcher
এ চালানোর মাধ্যমেও উপকৃত হবে, কারণ এটি পরীক্ষার থ্রেডে চলবে এবং পরীক্ষার সময় এটিতে থাকা বিলম্ব এড়িয়ে যাবে।
class RepositoryTest { @Test fun repoInitWorksAndDataIsHelloWorld() = runTest { val dispatcher = StandardTestDispatcher(testScheduler) val repository = Repository(dispatcher) repository.initialize() advanceUntilIdle() // Runs the new coroutine assertEquals(true, repository.initialized.get()) val data = repository.fetchData() // No thread switch, delay is skipped assertEquals("Hello world", data) } }
একটি TestDispatcher
এ শুরু হওয়া নতুন coroutines ম্যানুয়ালি অগ্রসর করা যেতে পারে যেমনটি initialize
দিয়ে উপরে দেখানো হয়েছে। উল্লেখ্য, যাইহোক, এটি উৎপাদন কোডে সম্ভব বা কাঙ্খিত হবে না। পরিবর্তে, এই পদ্ধতিটি হয় স্থগিত (অনুক্রমিক নির্বাহের জন্য), অথবা একটি Deferred
মান ফেরত দেওয়ার জন্য (সমসাময়িক সম্পাদনের জন্য) পুনরায় ডিজাইন করা উচিত।
উদাহরণস্বরূপ, আপনি একটি নতুন coroutine শুরু করতে এবং একটি Deferred
তৈরি করতে async
ব্যবহার করতে পারেন:
class BetterRepository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) { private val scope = CoroutineScope(ioDispatcher) fun initialize() = scope.async { // ... } }
এটি আপনাকে পরীক্ষা এবং উত্পাদন কোড উভয় ক্ষেত্রেই এই কোডটি সম্পূর্ণ হওয়ার জন্য নিরাপদে await
দেয়:
@Test fun repoInitWorks() = runTest { val dispatcher = StandardTestDispatcher(testScheduler) val repository = BetterRepository(dispatcher) repository.initialize().await() // Suspends until the new coroutine is done assertEquals(true, repository.initialized.get()) // ... }
runTest
মুলতুবি থাকা কোরোটিনগুলি পূরণ করার জন্য অপেক্ষা করবে যদি কোরোটিনগুলি একটি TestDispatcher
থাকে যার সাথে এটি একটি শিডিউলার ভাগ করে। এটি কর্উটিনগুলির জন্যও অপেক্ষা করবে যেগুলি শীর্ষ-স্তরের পরীক্ষা করুটিনের সন্তান, এমনকি যদি তারা অন্য প্রেরণকারীদের ( dispatchTimeoutMs
প্যারামিটার দ্বারা নির্দিষ্ট একটি টাইমআউট পর্যন্ত, যা ডিফল্টরূপে 60 সেকেন্ড)।
প্রধান প্রেরণকারী সেট করা হচ্ছে
স্থানীয় ইউনিট পরীক্ষায় , Android UI থ্রেড মোড়ানো Main
প্রেরণকারী অনুপলব্ধ হবে, কারণ এই পরীক্ষাগুলি একটি স্থানীয় JVM-এ সম্পাদিত হয়, Android ডিভাইসে নয়৷ যদি পরীক্ষার অধীনে আপনার কোডটি মূল থ্রেডের উল্লেখ করে, তবে এটি ইউনিট পরীক্ষার সময় একটি ব্যতিক্রম নিক্ষেপ করবে।
কিছু ক্ষেত্রে, আপনি Main
প্রেরণকারীকে অন্যান্য প্রেরকদের মতো একইভাবে ইনজেকশন করতে পারেন, যেমনটি পূর্ববর্তী বিভাগে বর্ণিত হয়েছে, আপনাকে পরীক্ষায় এটিকে একটি TestDispatcher
দিয়ে প্রতিস্থাপন করার অনুমতি দেয়। যাইহোক, কিছু API যেমন viewModelScope
হুডের নিচে একটি হার্ডকোড করা Main
প্রেরক ব্যবহার করে।
এখানে একটি ViewModel
বাস্তবায়নের একটি উদাহরণ রয়েছে যা viewModelScope
ব্যবহার করে একটি কোরোটিন চালু করতে যা ডেটা লোড করে:
class HomeViewModel : ViewModel() { private val _message = MutableStateFlow("") val message: StateFlow<String> get() = _message fun loadMessage() { viewModelScope.launch { _message.value = "Greetings!" } } }
সমস্ত ক্ষেত্রে একটি TestDispatcher
দিয়ে Main
প্রেরণকারীকে প্রতিস্থাপন করতে, Dispatchers.setMain
এবং Dispatchers.resetMain
ফাংশন ব্যবহার করুন।
class HomeViewModelTest { @Test fun settingMainDispatcher() = runTest { val testDispatcher = UnconfinedTestDispatcher(testScheduler) Dispatchers.setMain(testDispatcher) try { val viewModel = HomeViewModel() viewModel.loadMessage() // Uses testDispatcher, runs its coroutine eagerly assertEquals("Greetings!", viewModel.message.value) } finally { Dispatchers.resetMain() } } }
যদি Main
প্রেরণকারীকে একটি TestDispatcher
দিয়ে প্রতিস্থাপিত করা হয়, তাহলে যেকোন নতুন-নির্মিত TestDispatchers
স্বয়ংক্রিয়ভাবে Main
প্রেরণকারীর থেকে নির্ধারিত সময়সূচী ব্যবহার করবে , যদি অন্য কোনো প্রেরককে পাস না করা হয় তাহলে runTest
দ্বারা নির্মিত StandardTestDispatcher
সহ।
এটি পরীক্ষার সময় শুধুমাত্র একটি একক সময়সূচী ব্যবহার করা হয়েছে তা নিশ্চিত করা সহজ করে তোলে। এটি কাজ করার জন্য, Dispatchers.setMain
এ কল করার পরে অন্য সমস্ত TestDispatcher
দৃষ্টান্ত তৈরি করতে ভুলবেন না।
প্রতিটি পরীক্ষায় Main
প্রেরণকারীকে প্রতিস্থাপনকারী কোডের নকল এড়াতে একটি সাধারণ প্যাটার্ন হল এটিকে একটি JUnit পরীক্ষার নিয়মে বের করা:
// Reusable JUnit4 TestRule to override the Main dispatcher class MainDispatcherRule( val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(), ) : TestWatcher() { override fun starting(description: Description) { Dispatchers.setMain(testDispatcher) } override fun finished(description: Description) { Dispatchers.resetMain() } } class HomeViewModelTestUsingRule { @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun settingMainDispatcher() = runTest { // Uses Main’s scheduler val viewModel = HomeViewModel() viewModel.loadMessage() assertEquals("Greetings!", viewModel.message.value) } }
এই নিয়ম বাস্তবায়ন ডিফল্টরূপে একটি UnconfinedTestDispatcher
ব্যবহার করে, কিন্তু একটি StandardTestDispatcher
একটি প্যারামিটার হিসাবে পাস করা যেতে পারে যদি Main
প্রেরক একটি প্রদত্ত পরীক্ষার ক্লাসে সাগ্রহে কার্যকর না হয়।
যখন আপনার পরীক্ষার বডিতে একটি TestDispatcher
উদাহরণের প্রয়োজন হয়, আপনি নিয়ম থেকে testDispatcher
পুনরায় ব্যবহার করতে পারেন, যতক্ষণ না এটি পছন্দসই প্রকার। আপনি যদি পরীক্ষায় ব্যবহৃত TestDispatcher
এর ধরন সম্পর্কে স্পষ্ট হতে চান, অথবা আপনার যদি একটি TestDispatcher
প্রয়োজন হয় যা Main
এর জন্য ব্যবহৃত একটি থেকে ভিন্ন ধরনের, আপনি runTest
মধ্যে একটি নতুন TestDispatcher
তৈরি করতে পারেন। যেহেতু Main
প্রেরক একটি TestDispatcher
এ সেট করা আছে, যে কোনো নতুন তৈরি TestDispatchers
স্বয়ংক্রিয়ভাবে তার সময়সূচী ভাগ করবে।
class DispatcherTypesTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun injectingTestDispatchers() = runTest { // Uses Main’s scheduler // Use the UnconfinedTestDispatcher from the Main dispatcher val unconfinedRepo = Repository(mainDispatcherRule.testDispatcher) // Create a new StandardTestDispatcher (uses Main’s scheduler) val standardRepo = Repository(StandardTestDispatcher()) } }
একটি পরীক্ষার বাইরে প্রেরণকারী তৈরি করা
কিছু ক্ষেত্রে, পরীক্ষার পদ্ধতির বাইরে উপলব্ধ হওয়ার জন্য আপনার একটি TestDispatcher
প্রয়োজন হতে পারে। উদাহরণস্বরূপ, পরীক্ষার ক্লাসে একটি সম্পত্তির সূচনা করার সময়:
class Repository(private val ioDispatcher: CoroutineDispatcher) { /* ... */ } class RepositoryTestWithRule { private val repository = Repository(/* What TestDispatcher? */) @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun someRepositoryTest() = runTest { // Test the repository... // ... } }
আপনি যদি পূর্ববর্তী বিভাগে দেখানো হিসাবে Main
প্রেরণকারীকে প্রতিস্থাপন করেন, তবে Main
প্রেরণকারী প্রতিস্থাপিত হওয়ার পরে তৈরি TestDispatchers
স্বয়ংক্রিয়ভাবে তার সময়সূচী ভাগ করবে।
তবে, টেস্ট ক্লাসের বৈশিষ্ট্য হিসাবে তৈরি করা TestDispatchers
বা টেস্ট ক্লাসে বৈশিষ্ট্যগুলির প্রাথমিককরণের সময় তৈরি করা TestDispatchers
দের ক্ষেত্রে এটি হয় না। Main
প্রেরণকারী প্রতিস্থাপিত হওয়ার আগে এগুলি শুরু করা হয়। অতএব, তারা নতুন সময়সূচী তৈরি করবে।
আপনার পরীক্ষায় শুধুমাত্র একটি সময়সূচী আছে তা নিশ্চিত করতে, প্রথমে MainDispatcherRule
প্রপার্টি তৈরি করুন। তারপর প্রয়োজন অনুযায়ী অন্যান্য শ্রেণী-স্তরের বৈশিষ্ট্যের সূচনাকারীতে এর প্রেরণকারী (বা এর সময়সূচী, যদি আপনার একটি ভিন্ন ধরনের TestDispatcher
প্রয়োজন হয়) পুনরায় ব্যবহার করুন।
class RepositoryTestWithRule { @get:Rule val mainDispatcherRule = MainDispatcherRule() private val repository = Repository(mainDispatcherRule.testDispatcher) @Test fun someRepositoryTest() = runTest { // Takes scheduler from Main // Any TestDispatcher created here also takes the scheduler from Main val newTestDispatcher = StandardTestDispatcher() // Test the repository... } }
নোট করুন যে পরীক্ষার মধ্যে তৈরি করা runTest
এবং TestDispatchers
উভয়ই এখনও স্বয়ংক্রিয়ভাবে Main
প্রেরণকারীর সময়সূচী ভাগ করবে।
আপনি যদি Main
প্রেরণকারীকে প্রতিস্থাপন না করেন তবে আপনার প্রথম TestDispatcher
তৈরি করুন (যা একটি নতুন সময়সূচী তৈরি করে) ক্লাসের একটি সম্পত্তি হিসাবে। তারপর, প্রতিটি runTest
আমন্ত্রণ এবং প্রতিটি নতুন TestDispatcher
তৈরি করা হয়েছে, উভয় বৈশিষ্ট্য হিসাবে এবং পরীক্ষার মধ্যেই সেই সময়সূচীটিকে ম্যানুয়ালি পাস করুন:
class RepositoryTest { // Creates the single test scheduler private val testDispatcher = UnconfinedTestDispatcher() private val repository = Repository(testDispatcher) @Test fun someRepositoryTest() = runTest(testDispatcher.scheduler) { // Take the scheduler from the TestScope val newTestDispatcher = UnconfinedTestDispatcher(this.testScheduler) // Or take the scheduler from the first dispatcher, they’re the same val anotherTestDispatcher = UnconfinedTestDispatcher(testDispatcher.scheduler) // Test the repository... } }
এই নমুনায়, প্রথম প্রেরক থেকে সময়সূচী runTest
পাস করা হয়। এটি সেই সময়সূচী ব্যবহার করে TestScope
জন্য একটি নতুন StandardTestDispatcher
তৈরি করবে। আপনি সেই প্রেরণকারীতে পরীক্ষা করুটিন চালানোর জন্য সরাসরি runTest
প্রেরণকারীতে পাস করতে পারেন।
আপনার নিজস্ব টেস্টস্কোপ তৈরি করা
TestDispatchers
এর মতো, আপনাকে পরীক্ষার মূল অংশের বাইরে একটি TestScope
অ্যাক্সেস করতে হতে পারে। runTest
স্বয়ংক্রিয়ভাবে হুডের নিচে একটি TestScope
তৈরি করে, আপনি runTest
সাথে ব্যবহার করার জন্য আপনার নিজস্ব TestScope
ও তৈরি করতে পারেন।
এটি করার সময়, আপনার তৈরি করা TestScope
এ runTest
কল করতে ভুলবেন না:
class SimpleExampleTest { val testScope = TestScope() // Creates a StandardTestDispatcher @Test fun someTest() = testScope.runTest { // ... } }
উপরের কোডটি TestScope
জন্য একটি StandardTestDispatcher
তৈরি করে, সেইসাথে একটি নতুন সময়সূচী তৈরি করে। এই বস্তুগুলিও স্পষ্টভাবে তৈরি করা যেতে পারে। এটি কার্যকর হতে পারে যদি আপনি এটিকে নির্ভরতা ইনজেকশন সেটআপগুলির সাথে সংহত করতে চান।
class ExampleTest { val testScheduler = TestCoroutineScheduler() val testDispatcher = StandardTestDispatcher(testScheduler) val testScope = TestScope(testDispatcher) @Test fun someTest() = testScope.runTest { // ... } }
একটি সুযোগ ইনজেকশনের
আপনার যদি এমন একটি ক্লাস থাকে যা কোরোটিন তৈরি করে যা আপনাকে পরীক্ষার সময় নিয়ন্ত্রণ করতে হবে, আপনি সেই ক্লাসে একটি করটিন স্কোপ ইনজেকশন করতে পারেন, এটি পরীক্ষায় একটি TestScope
দিয়ে প্রতিস্থাপন করতে পারেন।
নিম্নলিখিত উদাহরণে, নতুন ব্যবহারকারীদের নিবন্ধন করতে এবং নিবন্ধিত ব্যবহারকারীদের তালিকা আনতে UserState
ক্লাস একটি UserRepository
এর উপর নির্ভর করে। যেহেতু UserRepository
এর এই কলগুলি ফাংশন কলগুলিকে স্থগিত করছে, তাই UserState
তার registerUser
ফাংশনের ভিতরে একটি নতুন coroutine শুরু করতে ইনজেকশন করা CoroutineScope
ব্যবহার করে৷
class UserState( private val userRepository: UserRepository, private val scope: CoroutineScope, ) { private val _users = MutableStateFlow(emptyList<String>()) val users: StateFlow<List<String>> = _users.asStateFlow() fun registerUser(name: String) { scope.launch { userRepository.register(name) _users.update { userRepository.getAllUsers() } } } }
এই ক্লাসটি পরীক্ষা করার জন্য, UserState
অবজেক্ট তৈরি করার সময় আপনি runTest
থেকে TestScope
পাস করতে পারেন:
class UserStateTest { @Test fun addUserTest() = runTest { // this: TestScope val repository = FakeUserRepository() val userState = UserState(repository, scope = this) userState.registerUser("Mona") advanceUntilIdle() // Let the coroutine complete and changes propagate assertEquals(listOf("Mona"), userState.users.value) } }
পরীক্ষার ফাংশনের বাইরে একটি স্কোপ ইনজেকশন করতে, উদাহরণস্বরূপ, পরীক্ষার অধীনে একটি বস্তুর মধ্যে যা পরীক্ষার ক্লাসে একটি সম্পত্তি হিসাবে তৈরি করা হয়েছে, দেখুন আপনার নিজস্ব TestScope তৈরি করা ।