Upgrade to Pro — share decks privately, control downloads, hide ads and more …

KC23_Coroutine_Testing

 KC23_Coroutine_Testing

https://festa.io/events/3416

KotlinConf'23 Global in Songdo 에서 Coroutine Testing 이란 주제로 발표한 내용입니다.

Veronikapj

May 13, 2023
Tweet

More Decks by Veronikapj

Other Decks in Programming

Transcript

  1. KotlinConf’23 in Songdo పझ౟ ௏٘о ೙ਃೠ ੉ਬܳ ੉೧ೡ ࣻ ੓׮.

    ௏ܖ౯ ղࠗীࢲ ੌযաח ੌਸ ੉೧ೡ ࣻ ੓׮. ௏ܖ౯ పझ౟ APIܳ ࢎਊ೧ࢲ పझ౟ ௏٘ܳ ੘ࢿೡ ࣻ ੓׮. য়ט੄ ݾ੸
  2. KotlinConf’23 in Songdo 1. ੢গী ҙೠ नࣘೠ ೖ٘ߔ 2. ѐߊ

    ઱ӝীࢲ ઑӝ ੢গ х૑ 3. ഥӈী न҃ ॶ ೙ਃ হ੉ ௏٘ܳ ୭੸ച ೡ ࣻ ੓ب۾ ೞח ؊ উ੹ೠ ௏٘ ܻಖఠ݂ 4. ӝࣿ੸ ޙઁܳ ୭ࣗച ೞח উ੿੸ੋ ѐߊ ࣘب పझ౟ ௏٘੄ ੢੼
  3. KotlinConf’23 in Songdo “ݣ౭झۨ٘ ࢚ടীࢲ ਊ۝ਸ ૑੿ೡ ࣻ ੓ח ௸ܳ

    ٜ݅য઻” How we test concurrent algorithms in Kotlin Coroutines
  4. KotlinConf’23 in Songdo import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicInteger class ConcurrentBoundedQueue<T>(private val

    capacity: Int) { private val queue = ConcurrentLinkedQueue<T>() private val size = AtomicInteger() fun add(value: T): Boolean { if (size.get() == capacity) { return false } queue.offer(value) size.incrementAndGet() return true } fun poll(): T? { val value = queue.poll() ?: return null size.decrementAndGet() return value } fun size(): Int = size.get() } How we test concurrent algorithms in Kotlin Coroutines
  5. KotlinConf’23 in Songdo import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicInteger class ConcurrentBoundedQueue<T>(private val

    capacity: Int) { private val queue = ConcurrentLinkedQueue<T>() private val size = AtomicInteger() fun add(value: T): Boolean { if (size.get() == capacity) { return false } queue.offer(value) size.incrementAndGet() return true } fun poll(): T? { val value = queue.poll() ?: return null size.decrementAndGet() return value } fun size(): Int = size.get() } Ӓۢ GPT ࢶࢤשਸ ޺Ҋ ࢎਊೡ ࣻ ੓ח ௏٘ ੌөਃ? How we test concurrent algorithms in Kotlin Coroutines
  6. KotlinConf’23 in Songdo val q = BoundedQueue<Int>(2) q.add(1) : true

    q.add(2) : true q.add(3) : false q.poll() : 1 ݏח Ѿҗੋоਃ? ࣻز పझ౟ How we test concurrent algorithms in Kotlin Coroutines
  7. KotlinConf’23 in Songdo val q = BoundedQueue<Int>(2) q.add(1) : true

    q.add(2) : true q.add(3) : false // ਊ۝ ୡҗ q.poll() : 1 // ୐ߣ૩ ೦ݾ ୶୹ ࣻز పझ౟ ݏח Ѿҗੋоਃ? How we test concurrent algorithms in Kotlin Coroutines
  8. KotlinConf’23 in Songdo val q = BoundedQueue<String>(2) Thread1 Thread2 q.add("a")

    : true q.add("b") : true q.poll() // "a" q.poll() : // "b" ?? ?? زदࢿ੄ ࣁ҅ 1 How we test concurrent algorithms in Kotlin Coroutines
  9. KotlinConf’23 in Songdo val q = BoundedQueue<String>(2) Thread1 Thread2 q.add("a")

    : true q.poll() // "a" q.add("b") : true q.poll() : // "b" زदࢿ੄ ࣁ҅ 1 How we test concurrent algorithms in Kotlin Coroutines
  10. KotlinConf’23 in Songdo val q = BoundedQueue<String>(2) Thread1 Thread2 q.add("a")

    : true q.add("b") : true q.poll() // “b" q.poll() : // “a" ?? ?? زदࢿ੄ ࣁ҅ 2 How we test concurrent algorithms in Kotlin Coroutines
  11. KotlinConf’23 in Songdo val q = BoundedQueue<String>(2) Thread1 Thread2 q.add("a")

    : true q.add("b") : true q.poll() : // “a" q.poll() // “b" زदࢿ੄ ࣁ҅ 2 How we test concurrent algorithms in Kotlin Coroutines
  12. KotlinConf’23 in Songdo val q = BoundedQueue<String>(2) ?? ?? زदࢿ੄

    ࣁ҅ 3 Thread1 Thread2 q.add("a") : true q.add("b") : true q.poll() // “b" q.add(“c") : true q.poll() : // “a" How we test concurrent algorithms in Kotlin Coroutines
  13. KotlinConf’23 in Songdo val q = BoundedQueue<String>(2) زदࢿ੄ ࣁ҅ 3

    Thread1 Thread2 q.add("b") : true q.add("a") : true q.poll() // “b" q.add(“c") : true q.poll() : // “a" How we test concurrent algorithms in Kotlin Coroutines
  14. KotlinConf’23 in Songdo val q = BoundedQueue<Int>(2) Thread1 Thread2 q.add(“2")

    : true q.add(“6") : true q.add(“ - 8“) : true ૒੽ ج۰ ࠇद׮! ⁉ How we test concurrent algorithms in Kotlin Coroutines
  15. KotlinConf’23 in Songdo Ѿҗ ٮۄоӝ Thread1 Thread2 q.add(“6") : true

    size.get() == 0 queue.offer(6 ) Thread1 Thread2 q.add(“2") : true q.add(“6") : true q.add(“ - 8“) : true ⁉ queue empty Size 0 fun add(value: T): Boolean { if (size.get() == capacity) { return false } queue.offer(value) size.incrementAndGet() return true } How we test concurrent algorithms in Kotlin Coroutines
  16. KotlinConf’23 in Songdo Ѿҗ ٮۄоӝ Thread1 Thread2 q.add(“6") : true

    size.get() == 0 queue.offer(6 ) Thread1 Thread2 q.add(“2") : true q.add(“6") : true q.add(“ - 8“) : true ⁉ queue 6 Size 0 How we test concurrent algorithms in Kotlin Coroutines
  17. KotlinConf’23 in Songdo Ѿҗ ٮۄоӝ Thread1 Thread2 q.add(“6") : true

    size.get() == 0 queue.offer(6 ) q.add(“2") : true Thread1 Thread2 q.add(“2") : true q.add(“6") : true q.add(“ - 8“) : true ⁉ queue 6, 2 Size 1 How we test concurrent algorithms in Kotlin Coroutines
  18. KotlinConf’23 in Songdo Ѿҗ ٮۄоӝ Thread1 Thread2 q.add(“6") : true

    size.get() == 0 queue.offer(6 ) q.add(“2") : true q.add(“ - 8“) : true Thread1 Thread2 q.add(“2") : true q.add(“6") : true q.add(“ - 8“) : true ⁉ queue 6, 2, -8 Size 2 How we test concurrent algorithms in Kotlin Coroutines
  19. KotlinConf’23 in Songdo Ѿҗ ٮۄоӝ Thread1 Thread2 q.add(“6") : true

    size.get() == 0 queue.offer(6 ) q.add(“2") : true q.add(“ - 8“) : true size.incrementAndGet() : 3 result: true Thread1 Thread2 q.add(“2") : true q.add(“6") : true q.add(“ - 8“) : true ⁉ queue 6, 2, -8 Size 3 How we test concurrent algorithms in Kotlin Coroutines
  20. KotlinConf’23 in Songdo Ѿҗ ٮۄоӝ https://kotlinconf.com/talks/400205/ Lincheck : https://github.com/Kotlin/kotlinx-lincheck =

    Invalid execution results = Init part: [addLast(4): void] Parallel part: | pollFirst(): 4 | addFirst(-4): void | | | peekLast(): 4 [-,1] | --- values in "[..]" brackets indicate the number of completed operations in each of the parallel threads seen at the beginning of the current operation --- = The following interleaving leads to the error = Parallel part trace: | pollFirst() | | | pollFirst(): 4 at ConcurrentDequeTest.pollFirst(ConcurrentDequeTest.kt:39) | | | first(): Node@1 at ConcurrentLinkedDeque.pollFirst(ConcurrentLinkedDeque.java:915) | | | item.READ: null at ConcurrentLinkedDeque.pollFirst(ConcurrentLinkedDeque.java:917) | | | next.READ: Node@2 at ConcurrentLinkedDeque.pollFirst(ConcurrentLinkedDeque.java:925) | | | item.READ: 4 at ConcurrentLinkedDeque.pollFirst(ConcurrentLinkedDeque.java:917) | | | prev.READ: null at ConcurrentLinkedDeque.pollFirst(ConcurrentLinkedDeque.java:919) | | | switch | | | | addFirst(-4): void | | | peekLast(): 4 | | | thread is finished | | compareAndSet(Node@2,4,null): true at ConcurrentLinkedDeque.pollFirst(..920) | | | unlink(Node@2) at ConcurrentLinkedDeque.pollFirst(ConcurrentLinkedDeque.java:921) | | | result: 4 | | | thread is finished | | How we test concurrent algorithms in Kotlin Coroutines
  21. KotlinConf’23 in Songdo ਬ׫ పझ౟ ௏٘ܳ যڌѱ द੘೧ঠ ೡ ૑

    ݽܰѷ׮ݶ .. https://product.kyobobook.co.kr/detail/S000001805070
  22. KotlinConf’23 in Songdo ਋ܻо పझ౟ ೧ঠ ೡ Ѫ ੑ۱җ ୹۱

    ӒܻҊ ৘৻ ViewModelী ؀೧ࢲ పझ౟ ௏٘ܳ ੘ࢿೠ׮ݶ 1. Repository ژח Usecase ীࢲ ߉ח ੑ۱ ч਷? 2. ചݶী ಴दೡ ੿࢚ ୹۱ ч਷? 3. ৘৻ܳ ചݶী ಴द೧઱۰ݶ ֈӡ ч਷ ޖ঺ੋо? ੉੹ ۨ੉য ( API, DB ) ੄ чਸ न҃ ॶ ೙ਃ
  23. KotlinConf’23 in Songdo పझ౟ ࢸ੿ ৘द Usecase ViewModel Repository class

    UseCase( private val repository: Repository, . . . ) { suspend operator fun invoke(): List<. . .> = withContext(defaultDispatcher) { } }
  24. KotlinConf’23 in Songdo పझ౟ ࢸ੿ ৘द Usecase ViewModel Repository class

    UseCase( private val repository: Repository, . . . ) { suspend operator fun invoke(): List<. . .> = withContext(defaultDispatcher) { } } API - Repository р੄ ௏٘ Repository పझ౟ ௏٘ীࢲ Ѩૐ о੿
  25. KotlinConf’23 in Songdo పझ౟ ࢸ੿ ৘द Usecase ViewModel Repository class

    UseCase( private val repository: Repository, . . . ) { suspend operator fun invoke(): List<. . .> = withContext(defaultDispatcher) { } } API - Repository р੄ ௏٘ Repository పझ౟ ௏٘ীࢲ Ѩૐ о੿ Mocking оמ
  26. KotlinConf’23 in Songdo పझ౟ ࢸ੿ ৘द Usecase ViewModel Repository class

    UseCase( private val repository: Repository, . . . ) { suspend operator fun invoke(): List<. . .> = withContext(defaultDispatcher) { // పझ౟ ؀࢚ ۽૒ } } API - Repository р੄ ௏٘ Repository పझ౟ ௏٘ীࢲ Ѩૐ о੿ Repository Ѿҗ ா੉झী ؀೧ Usecase ۽૒ чী ؀ೠ పझ౟ ௏٘ ੘ࢿ Mocking оמ
  27. KotlinConf’23 in Songdo Mocking ਸ ߄ۄࠁח ҙ੼ Mockingਸ ࢎਊೞѢա ࢎਊೞ૑

    ঋѢա ١਷ ਤীࢲ ঱әೠ ଼ਸ ଵҊ೧ࢲ ੗न੄ झఋੌী ݏѱ ଻ఖ೧઱ࣁਃ. B class A class A classо B classী ੄ઓ
  28. KotlinConf’23 in Songdo Mocking ਸ ߄ۄࠁח ҙ੼ Mockingਸ ࢎਊೞѢա ࢎਊೞ૑

    ঋѢա ١਷ ਤীࢲ ঱әೠ ଼ਸ ଵҊ೧ࢲ ੗न੄ झఋੌী ݏѱ ଻ఖ೧઱ࣁਃ. A ز੘ਸ ৻ࠗ ৔ೱҗ ܻ࠙ పझ౟ ؀࢚ ௿ېझী݅ ૘઺ A class Mocking
  29. KotlinConf’23 in Songdo ٘٣য ௏ܖ౯ 1. suspending functions పझ౟ ߑߨ

    2. The Coroutine test apis 3. ࢎਊ ৘ઁ 4. Main Dispatcher ׮ܖӝ Untangling Coroutine Testing Untangling Coroutine Testing (https://kotlinconf.com/talks/389145/)
  30. KotlinConf’23 in Songdo Testing suspending functions suspend fun fetchData(): String

    { delay(1000L) return "Hello world" } @Test fun dataIsHelloWorld() { val data = fetchData() assertEquals("Hello world", data) } https://developer.android.com/kotlin/coroutines/test?hl=ko
  31. KotlinConf’23 in Songdo Testing suspending functions suspend fun fetchData(): String

    { delay(1000L) return "Hello world" } @Test fun dataIsHelloWorld() { val data = fetchData() assertEquals("Hello world", data) } suspend ੉ӝ ٸޙী Compile Error https://developer.android.com/kotlin/coroutines/test?hl=ko
  32. KotlinConf’23 in Songdo Testing suspending functions suspend fun fetchData(): String

    { delay(1000L) return "Hello world" } @Test fun dataIsHelloWorld() = runTest { val data = fetchData() assertEquals("Hello world", data) } suspend పझ౟ח “runTest” https://developer.android.com/kotlin/coroutines/test?hl=ko
  33. KotlinConf’23 in Songdo Testing suspending functions suspend fun fetchData(): String

    { return "Hello world" } @Test fun dataIsHelloWorld() = runTest { val data = fetchData() assertEquals("Hello world", data) } delay(1000L) Delayח ੗زਵ۽ Ѥցڭ https://developer.android.com/kotlin/coroutines/test?hl=ko
  34. KotlinConf’23 in Songdo Testing suspending functions suspend fun fetchData(): String

    = withContext(Dispatchers.IO) { delay(1000L) return "Hello world" } @Test fun dataIsHelloWorld() = runTest { val data = fetchData() assertEquals("Hello world", data) } https://developer.android.com/kotlin/coroutines/test?hl=ko
  35. KotlinConf’23 in Songdo Testing suspending functions suspend fun fetchData(): String

    = withContext(Dispatchers.IO) { delay(1000L) return "Hello world" } Test Thread Dispatchers.IO fetchData() delay() assert() https://developer.android.com/kotlin/coroutines/test?hl=ko
  36. KotlinConf’23 in Songdo Testing suspending functions suspend fun fetchData(): String

    = withContext(Dispatchers.IO) { delay(1000L) return "Hello world" } Test Thread Dispatchers.IO fetchData() delay() assert() ੸੺൤ పझ౟ غ૑ ঋ਺ https://developer.android.com/kotlin/coroutines/test?hl=ko
  37. KotlinConf’23 in Songdo Testing suspending functions suspend fun fetchData(): String

    = withContext(Dispatchers.IO) { delay(1000L) return "Hello world" } Test Thread Dispatchers.IO fetchData() delay() assert() ੸੺ೠ TestDispatcher ࢶఖ https://developer.android.com/kotlin/coroutines/test?hl=ko
  38. KotlinConf’23 in Songdo StandardTestDispatcher UnconfinedTestDispatcher ࢜۽਍ ௏ܖ౯੉ ӝࠄ झா઴۞ ௸ী

    ୶о ӝࠄਵ۽ ࢎਊؽ അ੤ झۨ٘ীࢲ ࢜ ௏ܖ౯ ߄۽ द੘ ࢶఖ੸ਵ۽ ࢎਊ TestDispatchers
  39. KotlinConf’23 in Songdo StandardTestDispatcher @Test fun directExample() = runTest {

    val repo = UserRepositoy() launch { repo.register("Alice") } launch { repo.register("Bob") } assertEquals( listOf("Alice", "Bob"), repo.getAllUsers() ) }
  40. KotlinConf’23 in Songdo StandardTestDispatcher @Test fun directExample() = runTest {

    val repo = UserRepositoy() launch { repo.register("Alice") } launch { repo.register("Bob") } assertEquals( listOf("Alice", "Bob"), repo.getAllUsers() ) }
  41. KotlinConf’23 in Songdo StandardTestDispatcher @Test fun directExample() = runTest {

    launch { repo.register("Alice") } launch { repo.register("Bob") } . . . } Test Thread UserRepo() val repo = UserRepositoy()
  42. KotlinConf’23 in Songdo StandardTestDispatcher @Test fun directExample() = runTest {

    launch { repo.register("Alice") } launch { repo.register("Bob") } . . . } Test Thread UserRepo() val repo = UserRepositoy() reg(“Alice”) reg(“Bob”)
  43. KotlinConf’23 in Songdo StandardTestDispatcher @Test fun directExample() = runTest {

    launch { repo.register("Alice") } launch { repo.register("Bob") } assertEquals( listOf("Alice", “Bob"), repo.getAllUsers()) } Test Thread UserRepo() val repo = UserRepositoy() reg(“Alice”) reg(“Bob”) assert() ݽٚ ࢜ ௏ܖ౯਷ పझ౟ ௏ܖ౯੉ ৮ܐػ റ(Ӓ۞ա runTestо ߈ജೞӝ ੹)ী݅ प೯ؾפ׮.
  44. KotlinConf’23 in Songdo StandardTestDispatcher @Test fun directExample() = runTest {

    launch { repo.register("Alice") } launch { repo.register("Bob") } advanceUntilIdle() assertEquals( listOf("Alice", “Bob"), repo.getAllUsers()) } Test Thread UserRepo() val repo = UserRepositoy() assert() reg(“Alice”) reg(“Bob”)
  45. KotlinConf’23 in Songdo StandardTestDispatcher advanceUntilIdle() ؀ӝৌী թ਷ ೦ݾ੉ হਸ ٸө૑

    झா઴۞ীࢲ ׮ܲ ௏ܖ౯ਸ ݽف प೯. ؀ӝ ઺ੋ ௏ܖ౯੉ ݽف प೯غب۾ ೞח જ਷ ӝࠄ ࢶఖ. ؀ࠗ࠙੄ పझ౟ दաܻয়ীࢲ ੘ز advanceTimeBy() о࢚ दрਸ ૑੿ೞҊ Ӓ ੹ী प೯غب۾ ৘ডػ ௏ܖ౯ਸ प೯ runCurrent() അ੤ о࢚ दрী ৘ডػ ௏ܖ౯ਸ प೯ ؀ӝৌী ୶оػ ௏ܖ౯੉ प೯غب۾ పझ౟ ௏ܖ౯ਸ ࢤࢿೞח ߑߨ
  46. KotlinConf’23 in Songdo UnconfinedTestDispatcher @Test fun directExample() = runTest(UnconfinedTestDispatcher()) {

    val repo = UserRepositoy() launch { repo.register("Alice") } launch { repo.register("Bob") } assertEquals( listOf("Alice", "Bob"), repo.getAllUsers() ) } UnconfinedTestDispatcherח ࢜ ௏ܖ౯ਸ ࡅܰѱ प೯ೞݴ ௏ܖ౯ਸ ࢎਊೠ рױೠ పझ౟ী ੸೤೤פ׮.
  47. KotlinConf’23 in Songdo Test Thread UserRepo() assert() reg(“Alice”) reg(“Bob”) UnconfinedTestDispatcher

    @Test fun directExample() = runTest(UnconfinedTestDispatcher()) { val repo = UserRepositoy() launch { repo.register("Alice") } launch { repo.register("Bob") } assertEquals( listOf("Alice", “Bob"), repo.getAllUsers()) }
  48. KotlinConf’23 in Songdo Main dispatcher class HomeViewModel : ViewModel() {

    private val _message = MutableStateFlow("") val message: StateFlow<String> get() = message fun loadMessage() { viewModelScope.launch { _message.value = "Greetings!" } } } @Test fun testGreeting() = runTest { val viewModel = HomeViewModel() viewModel.loadMessage() assertEquals("Greetings!", viewModel.message.value) }
  49. KotlinConf’23 in Songdo Main dispatcher java.lang.illegalStateException Module with the Main

    dispatcher had failed to initialze. (For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used) Why? class HomeViewModel : ViewModel() { private val _message = MutableStateFlow("") val message: StateFlow<String> get() = message fun loadMessage() { viewModelScope.launch { _message.value = "Greetings!" } } } ۽ஸ ױਤ పझ౟ীࢲח Android UI झۨ٘ܳ ېೝೞח Main ٣झಁ୊ܳ ࢎਊೡ ࣻ হणפ׮. ੉۞ೠ పझ౟ח Android ӝӝо ইצ ۽ஸ JVMীࢲ प೯غӝ ٸޙੑפ׮. పझ౟ ઺ੋ ௏٘о ӝࠄ झۨ٘ܳ ଵઑೞݶ ױਤ పझ౟ ઺ী ৘৻о ߊࢤ೤פ׮.
  50. KotlinConf’23 in Songdo Main dispatcher @Test fun testGreeting() = runTest

    { val testDispatcher = UnconfinedTestDispatcher(testScheduler) Dispatchers.setMain(testDispatcher) try { val viewModel = HomeViewModel() viewModel.loadMessage() assertEquals("Greetings!", viewModel.message.value) } finally { Dispatchers.resetMain() } } viewModelScope৬ э਷ ੌࠗ APIח ղࠗ੸ਵ۽ ೞ٘௏٬ػ Main ٣झಁ୊ܳ ࢎਊ೤פ׮.
  51. KotlinConf’23 in Songdo Main dispatcher @Test fun testGreeting() = runTest

    { val testDispatcher = UnconfinedTestDispatcher(testScheduler) Dispatchers.setMain(testDispatcher) try { val viewModel = HomeViewModel() viewModel.loadMessage() assertEquals("Greetings!", viewModel.message.value) } finally { Dispatchers.resetMain() } } ݽٚ ҃਋ী Main ٣झಁ୊ܳ TestDispatcher۽ ߄Բ۰ݶ Dispatchers.setMain ߂ Dispatchers.resetMain ೣࣻܳ ࢎਊ೤פ׮.
  52. KotlinConf’23 in Songdo MainDispatcherRule class MainDispatcherRule( val dispatcher: TestDispatcher =

    UnconfinedTestDispatcher(), ) : TestWatcher() { override fun testing(description: Description) { Dispatchers.setMain(testDispatcher) } override fun finished(description: Description) { Dispatchers.resetMain() } }
  53. KotlinConf’23 in Songdo MainDispatcherRule class HomeViewModelTestUsingRule { @get:Rule val mainDispatcherRule

    = MainDispatcherRule() @Test fun testGreeting() = runTest { val viewModel = HomeViewModel() viewModel.loadMessage() assertEquals("Greetings!", viewModel.message.value) } }
  54. KotlinConf’23 in Songdo ਃড ௏ܖ౯ పझ౟ ೞ۰ݶ.. 1. runTest 2.

    Test Dispatchers 3. Main Dispatcher Resources • goo.gle/coroutine-test-guide • goo.gle/flow-test-guide