iT邦幫忙

2021 iThome 鐵人賽

DAY 25
0

Keyword: Coroutine mock
直到27日,完成KMM的測試功能放在
KMMDay27


今天開始要來寫KMM的測試,由於我們使用DB或是進行網路請求都是suspend function,所以需要先撰寫一個給測試使用的基礎Coroutine方便我們進行測試.

首先,在CommonTest的專案底下,建立一個expect物件,BaseTest,裡面只有一個runTest的方法.

//這是在commonTest底下的Kotlin
import kotlinx.coroutines.CoroutineScope

expect abstract class BaseTest() {
    fun <T> runTest(block: suspend CoroutineScope.() -> T)
}

有了expect當然就需要去雙平台實作actual讓雙平台在測試時候有辦法使用,跟commonMain的expect物件一樣.必須有同樣的packagePath

Android的實作方法如下,其中的coroutineTestRule是我們自己寫的Rule,他會在測試開始與結束時重設Dispather,避免測試的順序影響到測試的結果..

// 這是在androidTest 底下的Kotlin
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
actual abstract class BaseTest {
    @get:Rule
    var coroutineTestRule = CoroutineTestRule()

    actual fun <T> runTest(block: suspend CoroutineScope.() -> T) {
        runBlocking { block() }
    }
}

CouroutineTestRule如下,非常簡單的結構

// 這是在androidTest 底下的Kotlin
class CoroutineTestRule(
    private val testDispatcher: ExecutorCoroutineDispatcher = Executors.newSingleThreadExecutor()
        .asCoroutineDispatcher()
) : TestWatcher() {
    override fun starting(description: Description?) {
        super.starting(description)
        Dispatchers.setMain(testDispatcher)//設定Main
    }

    override fun finished(description: Description?) {
        super.finished(description)
        Dispatchers.resetMain()//重設Main
    }
}

這邊我們有用到junit等等的常用測試工具,所以要在gradle(shared)的androidTest部分加上他們的引用

//這是在build.gradle.kts中的Kotlin

...
val androidTest by getting {
            dependencies {
                ...
                implementation(Develop.KotlinTest.jvm)
                implementation(Develop.KotlinTest.junit)
                implementation(Develop.AndroidXTest.core)
                implementation(Develop.AndroidXTest.junit)
                implementation(Develop.AndroidXTest.runner)
                implementation(Develop.AndroidXTest.rules)
						...
            }
        }
...
//這是在buildSrc內的Kotlin設定

Develop{
object AndroidXTest {
        val core = "androidx.test:core:${Versions.AndroidX.test}"
        val junit = "androidx.test.ext:junit:${Versions.AndroidX.test_ext}"
        val runner = "androidx.test:runner:${Versions.AndroidX.test}"
        val rules = "androidx.test:rules:${Versions.AndroidX.test}"
    }
object KotlinTest {
        val common = "org.jetbrains.kotlin:kotlin-test-common:${Versions.kotlin}"
        val annotations = "org.jetbrains.kotlin:kotlin-test-annotations-common:${Versions.kotlin}"
        val jvm = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlin}"
        val junit = "org.jetbrains.kotlin:kotlin-test-junit:${Versions.kotlin}"
    }
}
Versions{
val kotlin = "1.5.21"
}

這樣我們就準備好Android的測試Coroutine了,接下來再去實作iOS使用的測試Coroutine

同樣的,在同樣的package path下,建立與expect同名的actual實作

//這是在iosTest底下的Kotlin語言
actual abstract class BaseTest {
    @OptIn(DelicateCoroutinesApi::class)
    actual fun <T> runTest(block: suspend CoroutineScope.() -> T) {
        var error: Throwable? = null
//由於iOS沒有Coroutine環境,所以改成GlobalScope來進行測試
        GlobalScope.launch(Dispatchers.Main) {
            try {
                block()
            } catch (t: Throwable) {
                error = t
            } finally {
                CFRunLoopStop(CFRunLoopGetCurrent())//這邊是由Native提供的iOS方法
            }
        }
        CFRunLoopRun()//這邊是由Native提供的iOS方法
        error?.also { throw it }
    }
}

有了這兩個基礎的測試後,就有coroutine環境可以進行更進一步的測試


上一篇
Day 24:讓iOS也吃到SQL Delight
下一篇
Day 26: Server我也不要了,Mock Ktor 環境
系列文
挑戰 Kotlin Multiplatform Mobile 跨平台開發,透過共同的Kotlin模組同時打造iOS與Android應用!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言