如何在 KMM 上为 SQLDelight 编写单元测试

Posted

技术标签:

【中文标题】如何在 KMM 上为 SQLDelight 编写单元测试【英文标题】:How to Write Unit Tests for SQLDelight on KMM 【发布时间】:2021-04-16 04:38:51 【问题描述】:

我想知道如何在 KMM 上为 SQLDelight 编写单元测试。首先,我什至无法正确添加 SQLDelight 依赖项。

    val commonTest by getting 
        dependencies 
            implementation(kotlin("test-common"))
            implementation(kotlin("test-annotations-common"))
            // SQLDelight tests
            implementation("com.squareup.sqldelight:sqlite-driver:1.4.3")
        
    

在我添加依赖然后同步项目之后,项目甚至没有构建。有人可以告诉我这是否是添加 sqlite 驱动程序依赖项的正确方法吗?

任何帮助将不胜感激!

【问题讨论】:

【参考方案1】:

您可以在KaMPKit 中看到一个基本示例。

如果您在非测试代码中配置了 sqldelight,则不需要在 commonTest 中对它自己的驱动程序依赖。

在我们的测试代码中,我们有一个 expect 来创建用于测试的数据库连接。

internal expect fun testDbConnection(): SqlDriver

然后在iosandroid 代码中,actual 定义。

依赖配置看起来(大致)如下:

commonMain 
  implementation("com.squareup.sqldelight:runtime:1.4.4")


androidMain 
 implementation("com.squareup.sqldelight:android-driver:1.4.4")


iosMain 
  implementation("com.squareup.sqldelight:native-driver:1.4.4")

有了它,你应该可以编写 sqldelight 测试了。

【讨论】:

感谢您的回答!我遇到了另一个问题。 “预期的函数‘createDriver’在 JVM 的模块 KMM.shared (test) 中没有实际声明”。在 KaMPKit 项目中,我没有找到任何与 JVM 相关的内容。【参考方案2】:

我在使用 Context 进行测试时遇到问题,发现使用内存数据库更快。这还具有不需要设备进行测试的好处。

我的做法:

    将 JdbcSqliteDriver 添加到 androidTest 源集(“共享”下的 build.gradle)
val androidTest by getting 
    dependencies 
        // ...
        implementation("com.squareup.sqldelight:sqlite-driver:1.4.4")
    

    添加期望/实际函数来创建驱动程序:
在“commonTest”目录下的文件中(例如 createTestSqlDriver.kt)

internal expect fun createTestSqlDriver(): SqlDriver

在“androidTest”下的文件中
internal actual fun createTestSqlDriver(): SqlDriver 
    return JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).apply 
        MyDatabase.Schema.create(this)
    

    现在我可以在“commonTest”下的测试中创建和使用数据库。比如:
internal class MyClassDbTests 

    private val sqlDriver = createTestSqlDriver()
    private val myDatabase = MyDatabase(sqlDriver)

    fun insert_addItems_verifyCorrectNumOfItemsInDb() 
        // GIVEN
        val myQueries = myDatabase.mydbQueries
        myQueries.deleteAllEvents()
        
        val numItemsBeforeInsertion = myQueries.selectAll().executeAsList().size

        // WHEN
        myQueries.insertItem(1, 2, 3)
        myQueries.insertItem(10, 20, 30)

        val numItemsAfterInsertion = myQueries.selectAll().executeAsList().size

        // THEN
        assertEquals(0, numItemsBeforeInsertion)
        assertEquals(2, numItemsAfterInsertion)
    

我发现以下帖子很有用:

SQLDelight 1.x Quick Start Guide for Android SQLDelight Testing

【讨论】:

用这个IN_MEMORY测试更快【参考方案3】:

感谢您的回答!我遇到了另一个问题。 “预期的函数‘createDriver’在 JVM 的模块 KMM.shared (test) 中没有实际声明”。在 KaMPKit 项目中,我没有找到任何与 JVM 相关的内容。

Getting Started on JVM with SQLite 包含必要的说明。

你需要添加一个依赖

dependencies 
  implementation "com.squareup.sqldelight:sqlite-driver:1.5.0"

进入你的“jvmMain”sourceSet,接下来在你的“jvmMain”模块中实现真正有趣的createDriver。


我很欣赏 Kevin 的回答,并补充说使用 SqlDeLite 的测试应该放在平台模块(“androidTest”和“iosTest”)中,而不是“commonTest”中。

您需要向您的 SUT 提供实际驱动程序的实现,使用应用上下文。 对于单元测试,您需要替代上下文,例如查看Robolectric。

添加依赖

dependencies 
    implementation("org.robolectric:robolectric:4.4")

进入“androidTest”sourceSet(不知道iOS能用什么),获取应用上下文:

val context = ApplicationProvider.getApplicationContext<Context>()

并使用它来获取驱动程序的平台实现:

val driver = DatabaseDriverFactory(context).createDriver(Database.Schema, "test.db")

【讨论】:

【参考方案4】:

例子

package com.viki.vikilitics_kmm

import com.squareup.sqldelight.sqlite.driver.JdbcDriver
import com.google.common.truth.Truth.assertThat
import com.squareup.sqldelight.db.SqlDriver
import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver
import com.viki.vikiliticskmm.Event
import com.viki.vikiliticskmm.EventQueries
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.sql.DriverManager
import java.sql.Connection


class AndroidEventDatabaseTest 
    private lateinit var queries: EventQueries

    // When your test needs a driver
    @Before
    fun before() 
        val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)

        val database = EventDatabase(driver)

        EventDatabase.Schema.create(driver)
        queries = database.eventQueries
    


    @Test
    fun `select all events`() 
        queries.insertEvent("1", "2", "Click,Open")
        queries.insertEvent("2", "2", "Click,Close")

        assertThat(queries.selectAllEvents().executeAsList())
            .containsExactly(
                Event(
                    as_counter = "1",
                    t_ms = "2",
                    event_map = "Click,Open"
                ),
                Event(
                    as_counter = "2",
                    t_ms = "2",
                    event_map = "Click,Close"
                )
            )
    

    @Test
    fun `delete multiple events`() 

        queries.insertEvent("1", "1", "Click,Open")
        queries.insertEvent("1", "2", "Click,Close,Read,Open")
        queries.insertEvent("1", "3", "Click,Open")
        queries.insertEvent("2", "3", "Click,Open")

        val event1 = listOf("1","3")
        val event2 = listOf("1","2")
        val event3 = listOf("1","4")
        val eventList = listOf(event1,event2,event3)
        for (event in eventList)
            queries.deleteEventListByKey(event.elementAt(0), event.elementAt(1))
        

        assertThat(queries.selectAllEvents().executeAsList())
            .containsExactly(
                Event(
                    as_counter = "1",
                    t_ms = "1",
                    event_map = "Click,Open"
                ), Event(
                    as_counter = "2",
                    t_ms = "3",
                    event_map = "Click,Open"
                ),
            )

    

    @Test
    fun `delete single event`() 

        queries.insertEvent("1", "1", "Click,Open")
        queries.insertEvent("1", "2", "Click,Close,Read,Open")
        queries.insertEvent("1", "3", "Click,Open")
        queries.insertEvent("2", "3", "Click,Open")
        queries.deleteEventListByKey("1", "3")

        assertThat(queries.selectAllEvents().executeAsList())
            .containsExactly(
                Event(
                    as_counter = "1",
                    t_ms = "1",
                    event_map = "Click,Open"
                ), Event(
                    as_counter = "2",
                    t_ms = "3",
                    event_map = "Click,Open"
                ),
                Event(
                    as_counter = "1",
                    t_ms = "2",
                    event_map = "Click,Close,Read,Open"
                )
            )

    

    @Test
    fun `update events`() 

        queries.insertEvent("1", "2", "Click,Open")
        queries.insertEvent("1", "2", "Click,Close")
        assertThat(queries.selectAllEvents().executeAsList())
            .containsExactly(
                Event(
                    as_counter = "1",
                    t_ms = "2",
                    event_map = "Click,Close"
                )
            )


    


参考资料 https://github.com/touchlab/KaMPKit/blob/main/shared/src/commonTest/kotlin/co/touchlab/kampkit/SqlDelightTest.kt

【讨论】:

以上是关于如何在 KMM 上为 SQLDelight 编写单元测试的主要内容,如果未能解决你的问题,请参考以下文章

KMM: sqldelight:coroutines-extensions 将 kotlinx-coroutines-core 版本设置为 1.3.9

我应该将我的包名用于 KMM SqlDelight 配置吗?

KMM 入门使用 SQLDelight 操作数据库

KMM 入门使用 SQLDelight 操作数据库

KMM 入门使用 SQLDelight 操作数据库

KMM 入门使用 SQLDelight 操作数据库