Mock একটি বিকল্প অবজেক্ট যা একটি বাস্তব কম্পোনেন্টের আচরণ অনুকরণ করে এবং এর সাথে মিথস্ক্রিয়া যাচাই করতে দেয়। Stub-এর বিপরীতে, যা কেবল একটি পূর্বনির্ধারিত মান ফেরত দেয়, Mock পদ্ধতি কলের ঘটনা, পাস করা আর্গুমেন্ট এবং কলের সংখ্যা রেকর্ড করে। Mockito (2024) অনুসারে, Mock Java এবং Kotlin প্রকল্পে সবচেয়ে জনপ্রিয় Test Double প্রকার, যা মোবাইল অ্যাপ্লিকেশনের 70% এর বেশি ইউনিট টেস্টে ব্যবহৃত হয়।
মুখ্য বিষয়
Mock হল একটি অবজেক্ট যা মকিং ফ্রেমওয়ার্ক (Mockito, MockK, EasyMock) দ্বারা তৈরি করা হয়, যা একটি ইন্টারফেস বা ক্লাস অনুকরণ করে এবং এর পদ্ধতির সমস্ত কল রেকর্ড করে। ডেভেলপার প্রত্যাশা নির্ধারণ করে: পদ্ধতি X আর্গুমেন্ট Y সহ কল করা হবে এবং Z ফেরত দেবে। টেস্ট কার্যকর হওয়ার পর, Mock যাচাই করে যে প্রত্যাশাগুলি বাস্তব কলের সাথে মিলেছে কিনা।
শব্দটি Test Doubles-এর নাট্য রূপক থেকে এসেছে: Mock হল একজন «অনুকরণকারী» যে কেবল মঞ্চে দাঁড়ায় না (Dummy-এর মতো) বরং একটি ভূমিকা পালন করে এবং যাচাই করে যে তার সাথে মিথস্ক্রিয়া সঠিক ছিল কিনা। যদি পরীক্ষাধীন কোডটি সেই পদ্ধতিটি কল না করে যা Mock প্রত্যাশা করেছিল, বা ভুল আর্গুমেন্ট দিয়ে কল করে — টেস্টটি লঙ্ঘিত প্রত্যাশার বার্তা সহ ব্যর্থ হয়।
Mock ফ্রেমওয়ার্ক ফ্যাক্টরির মাধ্যমে তৈরি করা হয়: mockk<MyInterface>() বা Mockito.mock(MyClass.java)। ফ্রেমওয়ার্ক একটি প্রক্সি অবজেক্ট তৈরি করে যা সমস্ত পদ্ধতি কল আটকায়। প্রতিটি কল পূর্বনির্ধারিত প্রত্যাশার সাথে তুলনা করা হয়। যদি কলটি প্রত্যাশার সাথে মেলে — নির্দিষ্ট মান ফেরত দেওয়া হয়। যদি না মেলে — Mock কনফিগারেশনের উপর নির্ভর করে একটি ডিফল্ট মান ফেরত দেয় বা ব্যতিক্রম ছুঁড়ে দেয়।
Mock প্রয়োজনীয় যখন পরীক্ষাধীন কোডটি এমন উপাদানের সাথে মিথস্ক্রিয়া করে যার পার্শ্বপ্রতিক্রিয়া রয়েছে: সার্ভারে ডেটা পাঠানো, ডাটাবেসে লেখা, লগিং, analytics, নেভিগেশন, সিস্টেম ডায়ালগ দেখানো। Mock ছাড়া, বাস্তব基础设施 না চালিয়ে এই মিথস্ক্রিয়াগুলি যাচাই করা সম্ভব নয়। Google Testing Blog অনুসারে, টেস্ট সার্ভার চালু না করেই অ্যাপ্লিকেশনটি সত্যিই analytics ইভেন্ট পাঠিয়েছে কিনা তা যাচাই করার একমাত্র উপায় হল Mock।
Mock এবং Stub-এর মধ্যে পার্থক্য টেস্টিংয়ের সবচেয়ে আলোচিত বিষয়গুলির মধ্যে একটি। উভয় প্রকারই একটি বাস্তব নির্ভরতা প্রতিস্থাপন করে, কিন্তু মৌলিকভাবে ভিন্ন উপায়ে।
| মানদণ্ড | Mock | Stub |
|---|---|---|
| মূল প্রশ্ন | পদ্ধতিটি কি কল করা হয়েছে? | কী ফলাফল ফেরত দেওয়া হয়েছে? |
| যাচাইকরণ | আচরণ (verify) | অবস্থা (assert) |
| ডেটা ফেরত | ঐচ্ছিক | বাধ্যতামূলক |
| উদাহরণ | verify(analytics).logEvent(“click”) | assertEquals(5, repository.getCount()) |
| কখন ব্যবহার করবেন | পার্শ্বপ্রতিক্রিয়া | ডেটা ফেরত |
সিদ্ধান্তের জন্য একটি সহজ পরীক্ষা: নিজেকে জিজ্ঞাসা করুন «যদি আমি কোডের এই লাইনটি মুছে ফেলি, তবে কি টেস্ট ব্যর্থ হবে?» যদি টেস্টটি রিটার্ন ভ্যালু পরীক্ষা করে — আপনার Stub প্রয়োজন (assert-ভিত্তিক যাচাইকরণ)। যদি টেস্টটি পরীক্ষা করে যে কোডটি সঠিক আর্গুমেন্ট সহ একটি পদ্ধতি কল করেছে — আপনার Mock প্রয়োজন (verify-ভিত্তিক যাচাইকরণ)। এই দ্বৈততা Command-Query Separation প্যাটার্ন অনুসরণ করে: যে পদ্ধতিগুলি অবস্থা পরিবর্তন করে (commands) তাদের Mock প্রয়োজন; যে পদ্ধতিগুলি ডেটা ফেরত দেয় (queries) তাদের Stub প্রয়োজন।
Mockito এবং MockK-এর মধ্যে নির্বাচন করা Kotlin-এ Android প্রকল্পের টেস্ট স্ট্যাক সেটআপ করার সময় প্রথম সিদ্ধান্তগুলির মধ্যে একটি। উভয় লাইব্রেরিই একই উদ্দেশ্য পূরণ করে, কিন্তু Kotlin-নির্দিষ্ট বৈশিষ্ট্যগুলির প্রতি ভিন্ন দৃষ্টিভঙ্গি সহ।
Mockito Java প্রকল্পের জন্য ডি ফ্যাক্টো স্ট্যান্ডার্ড। সংস্করণ 5.x বিল্ট-ইন MockMaker-এর কারণে ফাইনাল ক্লাস, স্ট্যাটিক পদ্ধতি এবং কনস্ট্রাক্টরের জন্য মকিং সমর্থন করে। Kotlin প্রকল্পের জন্য, Mockito-র অতিরিক্ত সেটআপ প্রয়োজন: উন্নত সিনট্যাক্সের জন্য mockito-kotlin এক্সটেনশন, ফাইনাল ক্লাসের জন্য mockito-inline। Mockito অতিরিক্ত অ্যাডাপ্টার ছাড়া Kotlin করুটিন এবং suspend ফাংশন সমর্থন করে না।
MockK বিশেষভাবে Kotlin-এর জন্য তৈরি করা হয়েছিল। এটি নেটিভভাবে করুটিন (coEvery, coVerify), সিলড ক্লাস, ডেটা ক্লাস, অবজেক্ট সিঙ্গেলটন এবং এক্সটেনশন ফাংশন সমর্থন করে। MockK সিনট্যাক্স ল্যাম্বডা ব্লক সহ DSL ব্যবহার করে, যা Kotlin কোডে স্বাভাবিক মনে হয়। MockK অতিরিক্ত সেটআপ ছাড়াই প্রপার্টি মকিং সমর্থন করে — এটি Android প্রকল্পের জন্য গুরুত্বপূর্ণ যা LiveData, StateFlow এবং Delegates ব্যবহার করে।
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)
// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user
বেনচমার্ক (JVM Benchmark, 2024) দেখায় যে MockK Java Reflections-এর পরিবর্তে Kotlin বাইটকোডের সাথে সরাসরি কাজ করার কারণে Kotlin প্রকল্পের জন্য Mockito-র তুলনায় 15–20% দ্রুত মক অবজেক্ট তৈরি করে। হাজার হাজার ইউনিট টেস্ট সহ প্রকল্পের জন্য, বিল্ড গতির পার্থক্য লক্ষণীয় হতে পারে: MockK বড় প্রকল্পে সম্পূর্ণ টেস্ট রানে 30–60 সেকেন্ড বাঁচায়।
তিনটি দৃশ্যকল্প পরীক্ষা করা যাক: Mock নির্ভরতা সহ ViewModel টেস্টিং, API কল যাচাই সহ UseCase টেস্টিং, এবং coVerify সহ করুটিন টেস্টিং।
class ProfileViewModelTest {
private val analytics = mockk<AnalyticsService>()
private val repo = mockk<UserRepository>()
private val vm = ProfileViewModel(repo, analytics)
fun `profile opened logs analytics event`() {
every { analytics.logEvent("profile_opened") } returns Unit
vm.onViewCreated()
verify { analytics.logEvent("profile_opened") }
}
}
class SendMessageUseCaseTest {
private val api = mockk<MessagingApi>()
private val useCase = SendMessageUseCase(api)
fun `send message with correct payload`() = runTest {
val message = Message(text = "Hello", userId = 42)
coEvery { api.sendMessage(any()) } returns MessageResult.Sent("msg_1")
val result = useCase.execute(message)
coVerify {
api.sendMessage(match {
it.text == "Hello" && it.userId == 42
})
}
assertTrue(result is MessageResult.Sent)
}
}
class OrderUseCaseTest {
private val api = mockk<OrderApi>()
private val useCase = OrderUseCase(api)
private val slot = slot<OrderRequest>()
fun `order request contains correct items`() = runTest {
coEvery { api.placeOrder(capture(slot)) } returns OrderResult.Placed("order_1")
useCase.execute(listOf("item_a", "item_b"))
assertEquals(2, slot.captured.items.size)
assertEquals("item_a", slot.captured.items[0])
}
}
মোবাইল ডেভেলপমেন্টে Mock-এর কার্যকর ব্যবহার শৃঙ্খলা প্রয়োজন। এই নিয়মগুলি লঙ্ঘন করলে টেস্টগুলি ভঙ্গুর বাধায় পরিণত হয় যা প্রতিটি রিফ্যাক্টরিংয়ের সাথে ভেঙে যায়।
একটি কঠোর নিয়ম: Mock শুধুমাত্র সেই নির্ভরতার জন্য তৈরি করা উচিত যা অ্যাপ্লিকেশন সীমানা অতিক্রম করে: API ক্লায়েন্ট, ডাটাবেস, ফাইল সিস্টেম, সিস্টেম পরিষেবা (LocationManager, BluetoothAdapter, Camera)। অভ্যন্তরীণ অ্যাপ্লিকেশন ক্লাস — ডোমেন এন্টিটি, Value Objects, সাধারণ ইউটিলিটি — Mock দিয়ে প্রতিস্থাপন করা উচিত নয়। তাদের আচরণ বাস্তব অবজেক্টের মাধ্যমে পরীক্ষা করা হয়।
প্রতিটি টেস্টে ঠিক একটি যৌক্তিক পরীক্ষা থাকা উচিত — হয় verify (Mock-এর জন্য) অথবা assert (Stub-এর জন্য)। একই টেস্টে অবস্থা এবং আচরণ যাচাইকরণ মিশ্রিত করবেন না। যদি আপনার API কল এবং এর ফলাফল উভয়ই পরীক্ষা করতে হয় — আলাদা নাম সহ দুটি পৃথক টেস্ট তৈরি করুন। এই নিয়ম, «প্রতি টেস্টে একটি assert» নামে পরিচিত, Kent Beck (2002)-এর সুপারিশ থেকে এসেছে।
মৌলিক মকিং ছাড়াও, উন্নত কৌশল রয়েছে যা মোবাইল ডেভেলপমেন্টে নির্দিষ্ট কাজগুলি সমাধান করে: মাল্টিথ্রেডিং পরীক্ষা, Flow অবস্থা যাচাই এবং বাস্তব অবজেক্টের আংশিক মকিং।
Spy (বা আংশিক mock) একটি অবজেক্ট তৈরি করতে দেয় যা বাস্তব বাস্তবায়নে কল ডেলিগেট করে কিন্তু পৃথক পদ্ধতি ওভাররাইড করতে দেয়। MockK-এ, spyk একটি বাস্তব ক্লাস ইনস্ট্যান্সের উপর ভিত্তি করে তৈরি করা হয়: val repo = spyk(InMemoryUserRepository())। every-এর মাধ্যমে সংজ্ঞায়িত প্রত্যাশা সহ কলগুলি Mock-এর মাধ্যমে যায়; বাকিগুলি বাস্তব অবজেক্টের মাধ্যমে যায়। Spy বিশেষ করে লিগ্যাসি কোড পরীক্ষার জন্য উপযোগী যেখানে ডিপেন্ডেন্সি ইনজেকশন এখনও প্রয়োগ করা হয়নি এবং আপনার শুধুমাত্র একটি পদ্ধতি ওভাররাইড করতে হবে।
Jetpack Compose ব্যবহারকারী আধুনিক Android প্রকল্পে, ViewModel StateFlow-এর মাধ্যমে অবস্থা প্রকাশ করে। MockK Flow নির্ভরতা মক করার অনুমতি দেয়, এবং Turbine লাইব্রেরি নির্গমন যাচাই সহজ করে। ক্লাসিক প্যাটার্ন: Flow ফেরত দেওয়া UseCase-এর জন্য MockK, ViewModel নির্গমন যাচাইয়ের জন্য Turbine। Kotlin Coroutines ব্যবহারকারী প্রকল্পের জন্য এই স্ট্যাক Android Testing ডকুমেন্টেশন (Google, 2024) দ্বারা সুপারিশকৃত।
class SearchViewModelTest {
private val searchUseCase = mockk<SearchUseCase>()
private val vm = SearchViewModel(searchUseCase)
fun `search emits results`() = runTest {
coEvery { searchUseCase.search("android") } returns
flowOf(SearchResult.Success(listOf(Item("Android TDD"))))
vm.search("android")
vm.state.test {
val state = awaitItem()
assertTrue(state.items.isNotEmpty())
cancelAndIgnoreRemainingEvents()
}
}
}
সচরাচর জিজ্ঞাসিত প্রশ্ন
Mock একটি ধারণা, Test Double-এর একটি প্রকার যা আচরণ যাচাই করে। Mockito Java এবং Android-এ Mock অবজেক্ট তৈরির একটি লাইব্রেরি। অন্যান্য লাইব্রেরি: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS)।
Mock-এর সাথে suspend ফাংশন পরীক্ষার জন্য MockK (coEvery / coVerify) বা mockito-kotlin সহ Mockito ব্যবহার করুন। MockK নেটিভভাবে করুটিন সমর্থন করে: coEvery একটি suspend ফাংশনের আচরণ সংজ্ঞায়িত করে, coVerify করুটিনের ভিতরে তার কল যাচাই করে। সমস্ত suspend কল অবশ্যই runTest (kotlinx-coroutines-test)-এর ভিতরে সম্পাদন করতে হবে।
হ্যাঁ। MockK-এ, returnsMany ব্যবহার করুন: every { api.getData() } returnsMany listOf(response1, response2)। Mockito-এ — thenReturn(value1).thenReturn(value2)-এর একটি চেইন। এটি অনুক্রমিক কলের সাথে বিভিন্ন প্রতিক্রিয়া ফেরত দেওয়ার আচরণ পরীক্ষার জন্য দরকারী।
MockK-এ, relaxed = true সহ @MockK অ্যানোটেশন ব্যবহার করুন এবং @After পদ্ধতিতে clearMocks(mock) কল করুন। Mockito-এ — Mockito.reset(mock)। সেরা অভ্যাস: ক্রস-টেস্ট হস্তক্ষেপ দূর করতে @Before-এর মাধ্যমে প্রতিটি টেস্টের জন্য নতুন Mock তৈরি করুন।
MockK সিলড ক্লাসের সাথে সঠিকভাবে কাজ করে: every { useCase() } returns Result.Success(data)। Mockito সরাসরি সিলড ক্লাস সমর্থন করে না, যার জন্য ওয়ার্কঅ্যারাউন্ড প্রয়োজন। এটি একটি কারণ কেন Kotlin প্রকল্পের জন্য Mockito-এর পরিবর্তে MockK সুপারিশ করা হয়।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন