Akış ile iletişim kuran birimleri veya modülleri test etme şekliniz bu, test edilen kişinin akışı giriş mi yoksa çıkış olarak mı kullandığına bağlıdır.
- Test edilen kişi bir akış gözlemlerse, tablodaki kontrol edebileceğiniz sahte bağımlılıkları ifade eder.
- Birim veya modül bir akış ortaya çıkarsa, bunlardan birini okuyup doğrulayabilirsiniz. testte bir akış tarafından yayınlanan birden çok öğe.
Sahte bir yapımcı oluşturma
Test edilen kişi bir akışın tüketicisi olduğunda, akış test etmek için sık kullanılan yöntemlerden biridir. yapımcıyı sahte bir uygulamayla değiştirmektir. Örneğin, iki veri kaynağından veri alan depoyu gözlemleyen sınıf üretim:
Testi belirleyici hale getirmek için depoyu ve depoyu her zaman aynı sahte verileri yayan sahte bir depoya sahip bağımlılıklar:
Bir akışta önceden tanımlanmış bir değer serisi yayınlamak için flow
oluşturucuyu kullanın:
class MyFakeRepository : MyRepository {
fun observeCount() = flow {
emit(ITEM_1)
}
}
Testte bu sahte depo yerleştirilir ve gerçek deponun yerini alır. uygulama:
@Test
fun myTest() {
// Given a class with fake dependencies:
val sut = MyUnitUnderTest(MyFakeRepository())
// Trigger and verify
...
}
Artık test edilen konunun çıktıları üzerinde kontrol sahibi olduğunuza göre, çıkışlarını kontrol ederek düzgün şekilde çalıştığını doğrulayın.
Bir testte akış emisyonlarını iddia etme
Test edilen kişi bir akıntıyı ortaya çıkarıyorsa testin onaylamada bulunması gerekir. oluşturmaya karar veriyor.
Bir önceki örneğin deposunun bir akışı ortaya çıkardığını varsayalım:
Belirli testlerde sadece ilk emisyonu veya sonlu akıştan gelen öğe sayısını ifade eder.
Akışa yönelik ilk emisyonu, first()
yöntemini çağırarak tüketebilirsiniz. Bu
fonksiyon ilk öğe alınıncaya kadar bekler ve ardından iptal isteğini gönderir
yapımcıya ilettiğim.
@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)
}
Testin birden çok değeri kontrol etmesi gerekiyorsa toList()
işlevinin çağrılması akışa neden olur
için kaynağın tüm değerlerini yayınlamasını beklemek, sonra da bu değerleri bir
liste'ye dokunun. Bu yalnızca sonlu veri akışlarında çalışır.
@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)
}
Daha karmaşık bir öğe koleksiyonu gerektiren veya dönmeyen veri akışları için
sınırlı sayıda öğe varsa Flow
API'yi kullanarak
öğeler. Aşağıda bazı örnekler verilmiştir:
// 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)
Test sırasında sürekli toplama
Önceki örnekte görüldüğü gibi toList()
kullanarak akış toplama
Dahili olarak collect()
oluşturur ve tüm sonuç listesi hazır olana kadar askıya alınır
geri döndü.
Akışın için bir akıştan sürekli olarak değer toplayarak gerekir.
Örneğin, test edilecek aşağıdaki Repository
sınıfını ve bir
emit
yöntemi olan sahte veri kaynağı uygulamasıyla ilgili bir
değer test sırasında dinamik olarak üretilir:
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
}
Bu sahte işlemi bir testte kullanırken, A/B testinin arka planındaki
Repository
kaynaklı değerleri sürekli olarak alır. Bu örnekte
bir liste halinde toplayıp bu e-postanın içeriklerinde onaylamalar yapacaksınız.
@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
}
Burada Repository
tarafından gösterilen akış hiçbir zaman tamamlanmadığı için toList
çağrısının geri dönüşü yoktur. Koordinat toplama işlemine şu tarihte başlanıyor:
TestScope.backgroundScope
Eş yordamın test bitmeden iptal edilmesini sağlar. Aksi halde
runTest
, testin tamamlanmasını beklemeye devam ederek testin durmasına neden oluyor
ve sonunda başarısız olur.
Nasıl olduğuna dikkat edin
UnconfinedTestDispatcher
Buradaki eş yordam toplama işlemi için kullanılır. Bu sayede, toplanan veriler
eş yordam heyecanla kullanıma sunuldu ve launch
tarihinden sonra değer almaya hazır
belirtir.
Türbin Kullanma
Üçüncü taraf Türbin kitaplığının, toplama koordinasyonu oluşturmak için kullanışlı bir API'si de vardır. test etmek için başka kolaylık özellikleri olarak görebilirsiniz:
@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())
}
}
Bkz. kitaplığının dokümanlarına göz atın inceleyebilirsiniz.
StateFlows'u test etme
StateFlow
gözlemlenebilir bir
toplanabilir ve böylece zaman içinde sahip olduğu değerleri gözlemlemek için toplanabilir.
bir deneyimdir. Bu değer akışının karıştırıldığını unutmayın. Bu,
değerler StateFlow
içinde hızlı bir şekilde ayarlanırsa StateFlow
bu toplayıcıların toplayıcıları değil
Yalnızca en güncel olanı olmak üzere tüm ara değerleri alması garanti edilir.
Testlerde, karmayı aklınızda tutarsanız StateFlow
değerlerini toplayabilirsiniz
gibi başka akışlar da toplayabilirsiniz. Toplanmaya çalışılıyor
ve tüm ara değerler üzerinde hak iddia etmek, bazı test senaryolarında tercih edilebilir.
Ancak genellikle StateFlow
öğesini veri sahibi olarak ele almanızı ve
bunun yerine value
mülkünde yer alıyor. Bu şekilde, testler mevcut
nesnenin belirli bir zamandaki durumundadır ve bağlı olup olmamasına bağlı değildir
çakışmaları yaşanır.
Örneğin, bir Repository
ve
bunları bir StateFlow
içinde kullanıcı arayüzüne gösterir:
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
}
}
}
}
Bu Repository
için sahte bir uygulama şu şekilde görünebilir:
class FakeRepository : MyRepository {
private val flow = MutableSharedFlow<Int>()
suspend fun emit(value: Int) = flow.emit(value)
override fun scores(): Flow<Int> = flow
}
ViewModel'i bu sahte ile test ederken sahte değerden
ViewModel'in StateFlow
öğesinde güncellemeleri tetikler ve ardından, güncellenen
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 tarafından oluşturulan StateFlows ile çalışma
Önceki bölümde ViewModel, dönüşüm verilerini depolamak için bir MutableStateFlow
Repository
kaynaklı bir akışın yayınladığı en son değer. Bu yaygın bir modeldir
daha basit bir şekilde uygulanır.
stateIn
operatörü, soğuk akışı sıcak bir StateFlow
parametresine dönüştürür:
class MyViewModelWithStateIn(myRepository: MyRepository) : ViewModel() {
val score: StateFlow<Int> = myRepository.scores()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), 0)
}
stateIn
operatörü, SharingStarted
parametresi içerir. Bu parametre,
devreye girer ve temel akışı tüketmeye başlar. Şunun gibi seçenekler:
SharingStarted.Lazily
ve SharingStarted.WhileSubsribed
sık kullanılıyor
ViewModelleri'nde.
Testinizde StateFlow
öğesinin value
üzerinde hak talebinde bulunsanız bile
toplayıcı oluşturmanız gerekir. Bu, boş bir toplayıcı olabilir:
@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)
}
Ek kaynaklar
- Android'de Kotlin eş yordamlarını test etme
- Android'de Kotlin akışları
StateFlow
veSharedFlow
- Kotlin eş yordamları ve akışı için ek kaynaklar