কোটলিন টেস্টিং অ্যান্ড্রয়েডে প্রবাহিত হয়

আপনি যেভাবে ইউনিট বা মডিউলগুলি পরীক্ষা করেন যা প্রবাহের সাথে যোগাযোগ করে তা নির্ভর করে পরীক্ষার অধীন বিষয় ইনপুট বা আউটপুট হিসাবে প্রবাহ ব্যবহার করে কিনা তার উপর।

  • যদি পরীক্ষার অধীনে বিষয় একটি প্রবাহ পর্যবেক্ষণ করে, আপনি নকল নির্ভরতার মধ্যে প্রবাহ তৈরি করতে পারেন যা আপনি পরীক্ষা থেকে নিয়ন্ত্রণ করতে পারেন।
  • যদি ইউনিট বা মডিউল একটি প্রবাহ প্রকাশ করে, আপনি পরীক্ষায় একটি প্রবাহ দ্বারা নির্গত এক বা একাধিক আইটেম পড়তে এবং যাচাই করতে পারেন।

ভুয়া প্রযোজক তৈরি করা

যখন পরীক্ষার অধীন বিষয় একটি প্রবাহের ভোক্তা হয়, তখন এটি পরীক্ষা করার একটি সাধারণ উপায় হল প্রযোজককে একটি জাল বাস্তবায়নের সাথে প্রতিস্থাপন করা। উদাহরণস্বরূপ, একটি ক্লাস দেওয়া হয়েছে যা একটি সংগ্রহস্থল পর্যবেক্ষণ করে যা উত্পাদনে দুটি ডেটা উত্স থেকে ডেটা নেয়:

পরীক্ষার অধীনে বিষয় এবং ডেটা স্তর
চিত্র 1. পরীক্ষার অধীনে বিষয় এবং ডেটা স্তর।

পরীক্ষা নির্ধারক করতে, আপনি সংগ্রহস্থল এবং এর নির্ভরতাগুলিকে একটি জাল সংগ্রহস্থল দিয়ে প্রতিস্থাপন করতে পারেন যা সর্বদা একই জাল ডেটা নির্গত করে:

নির্ভরতা একটি জাল বাস্তবায়ন সঙ্গে প্রতিস্থাপিত হয়
চিত্র 2. নির্ভরতা একটি জাল বাস্তবায়ন দিয়ে প্রতিস্থাপিত হয়।

একটি প্রবাহে মানগুলির একটি পূর্বনির্ধারিত সিরিজ নির্গত করতে, flow বিল্ডার ব্যবহার করুন:

class MyFakeRepository : MyRepository {
    fun observeCount() = flow {
        emit(ITEM_1)
    }
}

পরীক্ষায়, এই জাল সংগ্রহস্থলটি ইনজেকশন দেওয়া হয়, বাস্তব বাস্তবায়ন প্রতিস্থাপন করে:

@Test
fun myTest() {
    // Given a class with fake dependencies:
    val sut = MyUnitUnderTest(MyFakeRepository())
    // Trigger and verify
    ...
}

এখন যেহেতু পরীক্ষার অধীনে বিষয়ের আউটপুটগুলির উপর আপনার নিয়ন্ত্রণ আছে, আপনি এটির আউটপুটগুলি পরীক্ষা করে যাচাই করতে পারেন যে এটি সঠিকভাবে কাজ করে।

একটি পরীক্ষায় প্রবাহ নির্গমন নিশ্চিত করা

যদি পরীক্ষার অধীন বিষয় একটি প্রবাহ উন্মোচিত হয়, পরীক্ষা তথ্য প্রবাহ উপাদানের উপর দাবী করা প্রয়োজন.

ধরা যাক যে পূর্ববর্তী উদাহরণের সংগ্রহস্থল একটি প্রবাহ প্রকাশ করে:

জাল নির্ভরতা সহ ভান্ডার যা একটি প্রবাহ প্রকাশ করে
চিত্র 3. একটি ভান্ডার (পরীক্ষার অধীন বিষয়) জাল নির্ভরতা সহ যা একটি প্রবাহ প্রকাশ করে।

নির্দিষ্ট পরীক্ষার মাধ্যমে, আপনাকে শুধুমাত্র প্রথম নির্গমন বা প্রবাহ থেকে আসা একটি সীমিত সংখ্যক আইটেম পরীক্ষা করতে হবে।

আপনি first() কল করে প্রবাহের প্রথম নির্গমনটি গ্রাস করতে পারেন। এই ফাংশনটি প্রথম আইটেমটি প্রাপ্ত না হওয়া পর্যন্ত অপেক্ষা করে এবং তারপর প্রযোজকের কাছে বাতিলকরণ সংকেত পাঠায়।

@Test
fun myRepositoryTest() = runTest {
    // Given a repository that combines values from two data sources:
    val repository = MyRepository(fakeSource1, fakeSource2)

    // When the repository emits a value
    val firstItem = repository.counter.first() // Returns the first item in the flow

    // Then check it's the expected item
    assertEquals(ITEM_1, firstItem)
}

যদি পরীক্ষার একাধিক মান পরীক্ষা করতে হয়, toList() কল করার ফলে ফ্লোকে উৎসের সমস্ত মান নির্গত করার জন্য অপেক্ষা করতে হয় এবং তারপর সেই মানগুলিকে তালিকা হিসাবে ফেরত দেয়। এটি শুধুমাত্র সীমিত ডেটা স্ট্রিমের জন্য কাজ করে।

@Test
fun myRepositoryTest() = runTest {
    // Given a repository with a fake data source that emits ALL_MESSAGES
    val messages = repository.observeChatMessages().toList()

    // When all messages are emitted then they should be ALL_MESSAGES
    assertEquals(ALL_MESSAGES, messages)
}

ডেটা স্ট্রিমগুলির জন্য যেগুলির জন্য আইটেমগুলির আরও জটিল সংগ্রহের প্রয়োজন হয় বা একটি সীমিত সংখ্যক আইটেম ফেরত দেয় না, আপনি আইটেমগুলি বাছাই এবং রূপান্তর করতে Flow API ব্যবহার করতে পারেন৷ এখানে কিছু উদাহরণঃ:

// Take the second item
outputFlow.drop(1).first()

// Take the first 5 items
outputFlow.take(5).toList()

// Takes the first item verifying that the flow is closed after that
outputFlow.single()

// Finite data streams
// Verify that the flow emits exactly N elements (optional predicate)
outputFlow.count()
outputFlow.count(predicate)

একটি পরীক্ষার সময় ক্রমাগত সংগ্রহ

toList() ব্যবহার করে একটি ফ্লো সংগ্রহ করা যেমন পূর্ববর্তী উদাহরণে দেখা যায়, অভ্যন্তরীণভাবে collect() ব্যবহার করে এবং সম্পূর্ণ ফলাফলের তালিকা ফেরত দেওয়ার জন্য প্রস্তুত না হওয়া পর্যন্ত স্থগিত করা হয়।

ক্রিয়াগুলিকে ইন্টারলিভ করার জন্য যেগুলি প্রবাহকে নির্গত মানগুলিকে নির্গত করে এবং যে মানগুলি নির্গত হয়েছিল তার উপর দাবী করে, আপনি একটি পরীক্ষার সময় একটি প্রবাহ থেকে ক্রমাগত মান সংগ্রহ করতে পারেন৷

উদাহরণ স্বরূপ, পরীক্ষা করার জন্য নিম্নলিখিত Repository ক্লাস নিন, এবং একটি সহগামী জাল ডেটা সোর্স বাস্তবায়ন যাতে পরীক্ষার সময় গতিশীলভাবে মান তৈরি করার জন্য একটি emit পদ্ধতি রয়েছে:

class Repository(private val dataSource: DataSource) {
    fun scores(): Flow<Int> {
        return dataSource.counts().map { it * 10 }
    }
}

class FakeDataSource : DataSource {
    private val flow = MutableSharedFlow<Int>()
    suspend fun emit(value: Int) = flow.emit(value)
    override fun counts(): Flow<Int> = flow
}

একটি পরীক্ষায় এই জালটি ব্যবহার করার সময়, আপনি একটি সংগ্রহকারী কোরোটিন তৈরি করতে পারেন যা Repository থেকে ক্রমাগত মানগুলি গ্রহণ করবে৷ এই উদাহরণে, আমরা সেগুলিকে একটি তালিকায় সংগ্রহ করছি এবং তারপরে এর বিষয়বস্তুতে দাবী সম্পাদন করছি:

@Test
fun continuouslyCollect() = runTest {
    val dataSource = FakeDataSource()
    val repository = Repository(dataSource)

    val values = mutableListOf<Int>()
    backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
        repository.scores().toList(values)
    }

    dataSource.emit(1)
    assertEquals(10, values[0]) // Assert on the list contents

    dataSource.emit(2)
    dataSource.emit(3)
    assertEquals(30, values[2])

    assertEquals(3, values.size) // Assert the number of items collected
}

যেহেতু এখানে Repository দ্বারা উন্মোচিত প্রবাহটি কখনই সম্পূর্ণ হয় না, toList কল যা এটি সংগ্রহ করছে তা কখনই ফিরে আসে না। TestScope.backgroundScope এ coroutine সংগ্রহ করা শুরু করা নিশ্চিত করে যে পরীক্ষা শেষ হওয়ার আগে coroutine বাতিল হয়ে যাবে। অন্যথায়, runTest তার সমাপ্তির জন্য অপেক্ষা করতে থাকবে, যার ফলে পরীক্ষাটি সাড়া দেওয়া বন্ধ করবে এবং শেষ পর্যন্ত ব্যর্থ হবে।

এখানে কোরোটিন সংগ্রহের জন্য কীভাবে UnconfinedTestDispatcher ব্যবহার করা হয় তা লক্ষ্য করুন। এটি নিশ্চিত করে যে সংগ্রহ করা কোরোটিনটি সাগ্রহে চালু করা হয়েছে এবং launch রিটার্নের পরে মান গ্রহণের জন্য প্রস্তুত।

টারবাইন ব্যবহার করে

তৃতীয় পক্ষের টারবাইন লাইব্রেরি একটি সংগ্রহ করুটিন তৈরি করার জন্য একটি সুবিধাজনক API, সেইসাথে ফ্লো পরীক্ষা করার জন্য অন্যান্য সুবিধার বৈশিষ্ট্যগুলি অফার করে:

@Test
fun usingTurbine() = runTest {
    val dataSource = FakeDataSource()
    val repository = Repository(dataSource)

    repository.scores().test {
        // Make calls that will trigger value changes only within test{}
        dataSource.emit(1)
        assertEquals(10, awaitItem())

        dataSource.emit(2)
        awaitItem() // Ignore items if needed, can also use skip(n)

        dataSource.emit(3)
        assertEquals(30, awaitItem())
    }
}

আরও বিস্তারিত জানার জন্য লাইব্রেরির ডকুমেন্টেশন দেখুন।

স্টেটফ্লো পরীক্ষা করা

StateFlow হল একটি পর্যবেক্ষণযোগ্য ডেটা ধারক, যা একটি স্ট্রীম হিসাবে সময়ের সাথে সাথে থাকা মানগুলি পর্যবেক্ষণ করতে সংগ্রহ করা যেতে পারে। মনে রাখবেন যে মানগুলির এই প্রবাহটি সংমিশ্রিত, যার মানে হল যে যদি মানগুলি একটি StateFlow এ দ্রুত সেট করা হয়, সেই StateFlow এর সংগ্রাহকরা সমস্ত মধ্যবর্তী মান পাওয়ার নিশ্চয়তা পায় না, শুধুমাত্র সাম্প্রতিকতম একটি।

পরীক্ষায়, আপনি যদি সংমিশ্রণকে মনে রাখেন, আপনি একটি StateFlow এর মান সংগ্রহ করতে পারেন যেমন আপনি টারবাইন সহ অন্য কোনো প্রবাহ সংগ্রহ করতে পারেন। সমস্ত মধ্যবর্তী মান সংগ্রহ এবং জোর দেওয়ার চেষ্টা করা কিছু পরীক্ষার পরিস্থিতিতে বাঞ্ছনীয় হতে পারে।

যাইহোক, আমরা সাধারণত StateFlow ডেটা ধারক হিসাবে বিবেচনা করার এবং পরিবর্তে এর value সম্পত্তির উপর জোর দেওয়ার পরামর্শ দিই। এইভাবে, পরীক্ষাগুলি নির্দিষ্ট সময়ে বস্তুর বর্তমান অবস্থাকে যাচাই করে এবং সংমিশ্রণ ঘটবে কি না তার উপর নির্ভর করে না।

উদাহরণস্বরূপ, এই ভিউমডেলটি নিন যা একটি Repository থেকে মান সংগ্রহ করে এবং StateFlow UI-তে তাদের প্রকাশ করে:

class MyViewModel(private val myRepository: MyRepository) : ViewModel() {
    private val _score = MutableStateFlow(0)
    val score: StateFlow<Int> = _score.asStateFlow()

    fun initialize() {
        viewModelScope.launch {
            myRepository.scores().collect { score ->
                _score.value = score
            }
        }
    }
}

এই Repository জন্য একটি জাল বাস্তবায়ন এইরকম দেখতে পারে:

class FakeRepository : MyRepository {
    private val flow = MutableSharedFlow<Int>()
    suspend fun emit(value: Int) = flow.emit(value)
    override fun scores(): Flow<Int> = flow
}

এই জাল দিয়ে ViewModel পরীক্ষা করার সময়, আপনি ViewModel-এর StateFlow এ আপডেট ট্রিগার করতে জাল থেকে মান নির্গত করতে পারেন, এবং তারপর আপডেট করা value উপর জোর দিতে পারেন:

@Test
fun testHotFakeRepository() = runTest {
    val fakeRepository = FakeRepository()
    val viewModel = MyViewModel(fakeRepository)

    assertEquals(0, viewModel.score.value) // Assert on the initial value

    // Start collecting values from the Repository
    viewModel.initialize()

    // Then we can send in values one by one, which the ViewModel will collect
    fakeRepository.emit(1)
    assertEquals(1, viewModel.score.value)

    fakeRepository.emit(2)
    fakeRepository.emit(3)
    assertEquals(3, viewModel.score.value) // Assert on the latest value
}

StateIn দ্বারা তৈরি StateFlows এর সাথে কাজ করা

পূর্ববর্তী বিভাগে, Repository থেকে একটি প্রবাহ দ্বারা নির্গত সর্বশেষ মান সংরক্ষণ করতে ViewModel একটি MutableStateFlow ব্যবহার করে। এটি একটি সাধারণ প্যাটার্ন, সাধারণত stateIn অপারেটর ব্যবহার করে একটি সহজ উপায়ে প্রয়োগ করা হয়, যা একটি ঠান্ডা প্রবাহকে একটি গরম StateFlow রূপান্তর করে:

class MyViewModelWithStateIn(myRepository: MyRepository) : ViewModel() {
    val score: StateFlow<Int> = myRepository.scores()
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), 0)
}

stateIn অপারেটরের একটি SharingStarted প্যারামিটার আছে, যা নির্ধারণ করে কখন এটি সক্রিয় হয় এবং অন্তর্নিহিত প্রবাহ ব্যবহার করা শুরু করে। SharingStarted.Lazily এবং SharingStarted.WhileSubsribed এর মতো বিকল্পগুলি প্রায়শই ViewModels-এ ব্যবহৃত হয়৷

এমনকি যদি আপনি আপনার পরীক্ষায় StateFlow -এর value সম্পর্কে জোর দিয়ে থাকেন, তাহলে আপনাকে একটি সংগ্রাহক তৈরি করতে হবে। এটি একটি খালি সংগ্রাহক হতে পারে:

@Test
fun testLazilySharingViewModel() = runTest {
    val fakeRepository = HotFakeRepository()
    val viewModel = MyViewModelWithStateIn(fakeRepository)

    // Create an empty collector for the StateFlow
    backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
        viewModel.score.collect()
    }

    assertEquals(0, viewModel.score.value) // Can assert initial value

    // Trigger-assert like before
    fakeRepository.emit(1)
    assertEquals(1, viewModel.score.value)

    fakeRepository.emit(2)
    fakeRepository.emit(3)
    assertEquals(3, viewModel.score.value)
}

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