Veronikapj
May 13, 2023
150

# KC23_Coroutine_Testing

https://festa.io/events/3416

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

May 13, 2023

## Transcript

Testing ߓ೙઱
2. ### KotlinConf’23 in Songdo పझ౟ ௏٘о ೙ਃೠ ੉ਬܳ ੉೧ೡ ࣻ ੓׮.

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

઱ӝীࢲ ઑӝ ੢গ х૑ 3. ഥӈী न҃ ॶ ೙ਃ হ੉ ௏٘ܳ ୭੸ച ೡ ࣻ ੓ب۾ ೞח ؊ উ੹ೠ ௏٘ ܻಖఠ݂ 4. ӝࣿ੸ ޙઁܳ ୭ࣗച ೞח উ੿੸ੋ ѐߊ ࣘب పझ౟ ௏٘੄ ੢੼

6. ### KotlinConf’23 in Songdo “ݣ౭झۨ٘ ࢚ടীࢲ ਊ۝ਸ ૑੿ೡ ࣻ ੓ח ௸ܳ

ٜ݅য઻” How we test concurrent algorithms in Kotlin Coroutines
7. ### 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
8. ### 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
9. ### 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
10. ### 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
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" ?? ?? زदࢿ੄ ࣁ҅ 1 How we test concurrent algorithms in Kotlin Coroutines
12. ### 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
13. ### 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
14. ### 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
15. ### 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
16. ### 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
17. ### 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
18. ### 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
19. ### 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
20. ### 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
21. ### 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
22. ### 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

24. ### KotlinConf’23 in Songdo ਬ׫ పझ౟ ௏٘ܳ যڌѱ द੘೧ঠ ೡ ૑

ݽܰѷ׮ݶ .. https://product.kyobobook.co.kr/detail/S000001805070
25. ### KotlinConf’23 in Songdo ਋ܻо పझ౟ ೧ঠ ೡ Ѫ 1. View

Model 2. Usecase 3. Repository

ӒܻҊ ৘৻
27. ### KotlinConf’23 in Songdo ਋ܻо పझ౟ ೧ঠ ೡ Ѫ ੑ۱җ ୹۱

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

29. ### KotlinConf’23 in Songdo పझ౟ ࢸ੿ ৘द Usecase ViewModel Repository class

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

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

UseCase( private val repository: Repository, . . . ) { suspend operator fun invoke(): List<. . .> = withContext(defaultDispatcher) { } } API - Repository р੄ ௏٘ Repository పझ౟ ௏٘ীࢲ Ѩૐ о੿ Mocking оמ
32. ### 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 оמ
33. ### KotlinConf’23 in Songdo Mocking ਸ ߄ۄࠁח ҙ੼ Mockingਸ ࢎਊೞѢա ࢎਊೞ૑

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

ঋѢա ١਷ ਤীࢲ ঱әೠ ଼ਸ ଵҊ೧ࢲ ੗न੄ झఋੌী ݏѱ ଻ఖ೧઱ࣁਃ. A ز੘ਸ ৻ࠗ ৔ೱҗ ܻ࠙ పझ౟ ؀࢚ ௿ېझী݅ ૘઺ A class Mocking
35. ### 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/)
36. ### 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
37. ### 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
38. ### 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
39. ### 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
40. ### 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
41. ### 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
42. ### 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
43. ### 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

45. ### KotlinConf’23 in Songdo TestDispatchers TestCoroutineScheduler TestScope TestDispatcher StandardTestDispatcher UnconfinedTestDispatcher runTest

https://developer.android.com/kotlin/coroutines/test?hl=ko
46. ### KotlinConf’23 in Songdo StandardTestDispatcher UnconfinedTestDispatcher ࢜۽਍ ௏ܖ౯੉ ӝࠄ झா઴۞ ௸ী

୶о ӝࠄਵ۽ ࢎਊؽ അ੤ झۨ٘ীࢲ ࢜ ௏ܖ౯ ߄۽ द੘ ࢶఖ੸ਵ۽ ࢎਊ TestDispatchers
47. ### 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() ) }
48. ### 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() ) }
49. ### KotlinConf’23 in Songdo StandardTestDispatcher @Test fun directExample() = runTest {

launch { repo.register("Alice") } launch { repo.register("Bob") } . . . } Test Thread UserRepo() val repo = UserRepositoy()
50. ### 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”)
51. ### 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о ߈ജೞӝ ੹)ী݅ प೯ؾפ׮.
52. ### 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”)
53. ### KotlinConf’23 in Songdo StandardTestDispatcher advanceUntilIdle() ؀ӝৌী թ਷ ೦ݾ੉ হਸ ٸө૑

झா઴۞ীࢲ ׮ܲ ௏ܖ౯ਸ ݽف प೯. ؀ӝ ઺ੋ ௏ܖ౯੉ ݽف प೯غب۾ ೞח જ਷ ӝࠄ ࢶఖ. ؀ࠗ࠙੄ పझ౟ दաܻয়ীࢲ ੘ز advanceTimeBy() о࢚ दрਸ ૑੿ೞҊ Ӓ ੹ী प೯غب۾ ৘ডػ ௏ܖ౯ਸ प೯ runCurrent() അ੤ о࢚ दрী ৘ডػ ௏ܖ౯ਸ प೯ ؀ӝৌী ୶оػ ௏ܖ౯੉ प೯غب۾ పझ౟ ௏ܖ౯ਸ ࢤࢿೞח ߑߨ
54. ### 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ח ࢜ ௏ܖ౯ਸ ࡅܰѱ प೯ೞݴ ௏ܖ౯ਸ ࢎਊೠ рױೠ పझ౟ী ੸೤೤פ׮.
55. ### 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()) }
56. ### 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) }
57. ### 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ীࢲ प೯غӝ ٸޙੑפ׮. పझ౟ ઺ੋ ௏٘о ӝࠄ झۨ٘ܳ ଵઑೞݶ ױਤ పझ౟ ઺ী ৘৻о ߊࢤ೤פ׮.
58. ### 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 ٣झಁ୊ܳ ࢎਊ೤פ׮.
59. ### 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 ೣࣻܳ ࢎਊ೤פ׮.
60. ### 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() } }
61. ### 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) } }
62. ### KotlinConf’23 in Songdo ਃড ௏ܖ౯ పझ౟ ೞ۰ݶ.. 1. runTest 2.

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

veronikapj