Slide 1

Slide 1 text

Android Test Night #8 2023/03/10 Coroutines Test 入門

Slide 2

Slide 2 text

● Kenji Abe ● @STAR_ZERO ● Google Developers Expert for Android, Kotlin ● DeNA Co., Ltd.

Slide 3

Slide 3 text

// build.gradle dependencies { // ... testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") }

Slide 4

Slide 4 text

supsend関数のテスト

Slide 5

Slide 5 text

class Sample { suspend fun run(): String { return "hoge" } }

Slide 6

Slide 6 text

class SampleTest { @Test fun testRun() = runTest { val sample = Sample() val result = sample.run() assert(result == "hoge") } }

Slide 7

Slide 7 text

class SampleTest { @Test fun testRun() = runTest { val sample = Sample() val result = sample.run() assert(result == "hoge") } }

Slide 8

Slide 8 text

● runBlockingのようなもの ● delay関数はすべてスキップされる ○ Vertial Timeを制御することができる ● TestScopeが使用される runTest

Slide 9

Slide 9 text

TestDispatchers

Slide 10

Slide 10 text

● StandardTestDispatcher ○ runTestデフォルト ○ TestCoroutineSchedulerを使用するシンプルなDispatcher ● UnconfinedTestDispatcher ○ Dispatchers.Unconfinedのようなもの ○ トップレベルのlaunch/asyncがすぐに実行される TestDispatchers

Slide 11

Slide 11 text

class SampleTest { @Test fun testRun() = runTest { val sample = Sample() val result = sample.run() assert(result == "hoge") } }

Slide 12

Slide 12 text

@Test fun test() = runTest { var result = 0 launch { result = 1 } assert(result == 1) }

Slide 13

Slide 13 text

@Test fun test() = runTest { var result = 0 launch { result = 1 } assert(result == 1) } ❌ Failed

Slide 14

Slide 14 text

@Test fun test() = runTest { var result = 0 launch { result = 1 } assert(result == 1) } ① ② ③

Slide 15

Slide 15 text

@Test fun test() = runTest { var result = 0 launch { result = 1 } runCurrent() assert(result == 1) } ✅ Success

Slide 16

Slide 16 text

@Test fun test() = runTest { var result = 0 launch { result = 1 } runCurrent() assert(result == 1) } ① ② ③ ④

Slide 17

Slide 17 text

@Test fun test() = runTest { var result = 0 launch { delay(1000) result = 1 } runCurrent() assert(result == 1) }

Slide 18

Slide 18 text

@Test fun test() = runTest { var result = 0 launch { delay(1000) result = 1 } runCurrent() assert(result == 1) } ❌ Failed

Slide 19

Slide 19 text

@Test fun test() = runTest { var result = 0 launch { delay(1000) result = 1 } runCurrent() assert(result == 1) } ① ② ③ ④ ⑤

Slide 20

Slide 20 text

@Test fun test() = runTest { // ... advanceUntilIdle() // または advanceTimeBy(1001) assert(result == 1) } ✅ Success

Slide 21

Slide 21 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { var result = 0 launch { result = 1 } assert(result == 1) }

Slide 22

Slide 22 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { var result = 0 launch { result = 1 } assert(result == 1) } ✅ Success

Slide 23

Slide 23 text

Mainスレッド対応

Slide 24

Slide 24 text

class Sample { suspend fun run(): String = withContext(Dispatchers.Main) { "hoge" } }

Slide 25

Slide 25 text

@Test fun testRun() = runTest { val sample = Sample() val result = sample.run() assert(result == "hoge") }

Slide 26

Slide 26 text

@Test fun testRun() = runTest { val sample = Sample() val result = sample.run() assert(result == "hoge") }

Slide 27

Slide 27 text

@Before fun setUp() { Dispatchers.setMain(StandardTestDispatcher()) } @After fun tearDown() { Dispatchers.resetMain() }

Slide 28

Slide 28 text

Flowのテスト

Slide 29

Slide 29 text

class Sample { val flow = MutableSharedFlow() suspend fun emit(value: Int) { flow.emit(value) } }

Slide 30

Slide 30 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { val sample = Sample() val result = mutableListOf() val job = launch { sample.flow.toCollection(result) } assert(result.size == 0) sample.emit(1) assert(result.size == 1) assert(result.first() == 1) job.cancel() }

Slide 31

Slide 31 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { val sample = Sample() val result = mutableListOf() // ... }

Slide 32

Slide 32 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { val sample = Sample() val result = mutableListOf() val job = launch { sample.flow.toCollection(result) } // ... }

Slide 33

Slide 33 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { val sample = Sample() val result = mutableListOf() val job = launch { sample.flow.toCollection(result) } // ... }

Slide 34

Slide 34 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { // ... val job = launch { sample.flow.toCollection(result) } assert(result.size == 0) // ... }

Slide 35

Slide 35 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { // ... val job = launch { sample.flow.toCollection(result) } sample.emit(1) // ... }

Slide 36

Slide 36 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { // ... sample.emit(1) assert(result.size == 1) assert(result.first() == 1) job.cancel() }

Slide 37

Slide 37 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { // ... val job = launch { sample.flow.toCollection(result) } // ... job.cancel() }

Slide 38

Slide 38 text

@Test fun test() = runTest(UnconfinedTestDispatcher()) { // ... backgroundScope.launch { sample.flow.toCollection(result) } // ... // job.cancel() }

Slide 39

Slide 39 text

Turbine (時間の都合上省略...) https://github.com/cashapp/turbine

Slide 40

Slide 40 text

まとめ

Slide 41

Slide 41 text

● runTest ● StandardTestDispatcher / UnconfinedTestDispatcher ● Dispatchers.setMain ● backgroundScope まとめ

Slide 42

Slide 42 text

ありがとうございました