- 后端
- ORM
【免费下载链接】sqldelight
SQLDelight - Generates typesafe Kotlin APIs from SQL
本文面向使用 SQLDelight 的 Android 开发者,讲解如何在 JVM 单元测试中引入app.cash.sqldelight:sqlite-driver,用JdbcSqliteDriver替代 Android 平台驱动,从而在没有模拟器、没有真机的情况下完成数据库建表、查询与迁移逻辑的验证。读完本文,你将掌握测试依赖的配置方式、内存/临时数据库的初始化套路、迁移验证的 schema 传入方式,以及如何让 JVM 端的 SQLite 版本与 Android 内建版本保持一致。
为什么要在 Android 测试中替换驱动
SQLDelight 在 Android 平台默认使用 AndroidSqliteDriver(位于drivers/android-driver),它通过androidx.sqlite的SupportSQLiteOpenHelper与框架层 SQLite 交互。这类测试依赖 Android 运行时环境,通常需要在模拟器或真机上执行,启动慢、不稳定、CI 成本高。
在验证迁移(.sqm文件)或纯粹校验数据库层行为时,数据库语义本身与平台无关,因此完全可以把驱动换成基于 JDBC 的 JVM 实现JdbcSqliteDriver,让同一套Database、Database.Schema与生成的查询 API 直接跑在本地 JVM 上。JdbcSqliteDriver是 SQLDelight 官方提供的 JVM SQLite 驱动(POM_DESCRIPTION=JVM SQLite driver for SQLDelight),与 Android 驱动实现的是同一套SqlDriver接口,所以业务代码、schema 与迁移脚本都可以无缝复用,测试代码只需在初始化处换一行驱动即可。
添加测试依赖:Kotlin DSL 与 Groovy 两种写法
原文档给出的依赖坐标是app.cash.sqldelight:sqlite-driver,需要放在testImplementation(而非implementation)作用域,确保驱动只参与 JVM 单元测试编译与运行,不会打进 APK:
// build.gradle.kts(Kotlin DSL) dependencies { testImplementation("app.cash.sqldelight:sqlite-driver:<版本号>") }// build.gradle(Groovy DSL) dependencies { testImplementation "app.cash.sqldelight:sqlite-driver:<版本号>" }其中<版本号>需要替换为你实际使用的 SQLDelight 版本(原文档以{{ versions.sqldelight }}占位)。该驱动模块内部依赖org.xerial:sqlite-jdbc,仓库 gradle/libs.versions.toml 中将其默认版本声明为3.53.4.0(sqliteJdbc = { module = "org.xerial:sqlite-jdbc", version = "3.53.4.0" }),实际生效版本以你的依赖解析结果为准。
由于依赖声明在testImplementation,Robolectric 或纯 JUnit 测试都能直接运行,无需任何 Android 设备。
初始化驱动:内存库 + Schema 创建
依赖就绪后,测试中只需要两步:构造JdbcSqliteDriver,然后调用Database.Schema.create(driver)建表:
private lateinit var driver: JdbcSqliteDriver @Before fun before() { driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) Database.Schema.create(driver) }JdbcSqliteDriver.IN_MEMORY是驱动内置的常量,源码中定义在 JdbcSqliteDriver.kt 的 companion object 中:
companion object { const val IN_MEMORY = "jdbc:sqlite:" }需要留意一个容易误读的细节:IN_MEMORY的值是"jdbc:sqlite:"(空路径),根据驱动源码中的 KDoc,jdbc:sqlite:会创建一个临时数据库,临时文件在连接关闭时被删除;而jdbc:sqlite::memory:才是严格的纯内存数据库。因此IN_MEMORY更准确的理解是"用完即弃的临时库",对单元测试来说效果等价且足够干净。
Database.Schema.create(driver)执行的是 SQLDelight 编译器生成的建表脚本(对应你的.sq/.sqm文件),这一步保证了后续查询测试能在真实 schema 上运行,而不是空库。
源码佐证:驱动的 URL 形态与连接管理
JdbcSqliteDriver的构造器接收 JDBC 连接串,源码 KDoc 完整列举了支持的形态:
jdbc:sqlite:/path/to/myDatabase.db—— 文件数据库,改动写入文件系统;jdbc:sqlite:—— 临时数据库,连接关闭即删除临时文件(即IN_MEMORY);jdbc:sqlite::memory:—— 纯内存数据库;jdbc:sqlite:file:memdb1?mode=memory&cache=shared—— 可跨连接共享的具名内存库。
驱动内部通过connectionManager(url, properties)按路径自动选择连接管理策略(见 JdbcSqliteDriver.kt):
- 空路径、
:memory:、file::memory:、:resource:前缀或 URL 含mode=memory时,走StaticConnectionManager:整个驱动复用一个连接,close()时关闭连接; - 其余(文件数据库)走
ThreadedConnectionManager:每个线程通过ThreadLocal持有独立连接,连接必须在打开的线程上关闭,事务结束后自动归还。
对于单线程的单元测试,IN_MEMORY对应的StaticConnectionManager足够简单可靠。事务底层通过BEGIN TRANSACTION/END TRANSACTION/ROLLBACK TRANSACTION三条原生语句实现,行为与 Android 端一致。
迁移验证:把 Schema 直接传给构造器
原文档开篇提到"像迁移验证这类测试",这是替换驱动的典型场景。SQLDelight 的.sqm迁移文件在编译期会生成Database.Schema.migrate(...)逻辑。JVM 驱动对此提供了专门的重载构造器,定义在 JdbcSqliteSchema.kt:
fun JdbcSqliteDriver( url: String, properties: Properties = Properties(), schema: SqlSchema<QueryResult.Value<Unit>>, migrateEmptySchema: Boolean = false, vararg callbacks: AfterVersion, ): JdbcSqliteDriver其内部逻辑为:
- 通过
PRAGMA user_version读取当前库版本; - 若版本为 0 且
migrateEmptySchema = false(默认),调用schema.create(driver)建库并写入schema.version; - 若版本小于
schema.version,调用schema.migrate(driver, version, schema.version, *callbacks)执行迁移,并更新user_version。
也就是说,迁移测试只需要把Database.Schema传入构造器:
val driver = JdbcSqliteDriver( JdbcSqliteDriver.IN_MEMORY, Properties(), Database.Schema, )驱动会在事务中自动完成建库或迁移,无需手动调用create。vararg callbacks: AfterVersion还支持在迁移到指定版本后执行回调(对应AfterVersion),可以用于断言迁移中间态。更完整的迁移主题可参考 docs/jvm_sqlite/migrations.md。
对齐 Android 内建 SQLite 版本
Android 系统内建 SQLite 的版本由设备的 API level 决定,通常低于最新版;如果你的测试想模拟"真机上的 SQLite 行为"(例如验证某些仅在旧版本上成立的 SQL 语义),可以覆盖sqlite-jdbc的版本,使其与目标 Android 版本的 SQLite 一致。原文档给出的示例是:Android API 23 内建 SQLite 3.8.10.2,则做如下覆盖:
dependencies { testImplementation('org.xerial:sqlite-jdbc') { // 覆盖 sqlite-driver 使用的 sqlite 版本,以匹配 Android API 23 version { strictly('3.8.10.2') } } }要点说明:
- 坐标不写版本号,让 Gradle 解析由
sqlite-driver传递进来的默认版本,再用version { strictly(...) }强制固定为指定版本,避免依赖升级悄悄改变测试环境; - 需要同时声明
testImplementation("app.cash.sqldelight:sqlite-driver:..."),覆盖语句只是调整传递依赖的解析结果; - 目标版本需要与你关心的 Android 版本内建 SQLite 对应(
org.xerial:sqlite-jdbc的发布版本号与 SQLite C 库版本对应,如 3.8.10.2);仓库当前默认的sqlite-jdbc为 3.53.4.0,其语义更接近新版本 SQLite,如需模拟旧设备行为必须显式覆盖。
如果你使用 Kotlin DSL,等价写法为:
testImplementation("org.xerial:sqlite-jdbc") { version { strictly("3.8.10.2") } }进阶实践:磁盘文件库与驱动自测参考
除了内存库,JdbcSqliteDriver也支持指向真实文件的连接串(如jdbc:sqlite:test.db,见 docs/jvm_sqlite/index.md),适合需要验证落盘行为、重启后数据持久化的测试;这类场景下连接走ThreadedConnectionManager,多线程并发测试时注意各线程连接是独立的。
驱动自身的测试文件也可以作为使用范例:仓库 drivers/sqlite-driver/src/test/kotlin/com/squareup/sqldelight/driver/sqlite/ 下的SqliteDriverTest.kt、SqliteQueryTest.kt、SqliteTransacterTest.kt均以JdbcSqliteDriver(IN_MEMORY)起步,覆盖了查询、事务与驱动基础行为;SqliteEphemeralTest.kt则展示了"jdbc:sqlite:$suffix"临时库的另一种构造方式。这些测试与 drivers/driver-test 中的跨驱动通用测试套件互为印证,说明JdbcSqliteDriver与 Android 驱动在行为层面保持了一致。
小结
将 Android 测试中的数据库验证迁移到 JVM,只需三步:在testImplementation加入app.cash.sqldelight:sqlite-driver;用JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)初始化内存/临时库;对迁移场景把Database.Schema传入驱动构造器自动建库或迁移。必要时通过strictly覆盖sqlite-jdbc版本以对齐目标 Android 版本的内建 SQLite。这套方案让数据库相关测试完全摆脱模拟器与真机,跑得更快、更稳定,且与 Android 环境下的真实驱动保持语义一致。
- 后端
- ORM
【免费下载链接】sqldelight
SQLDelight - Generates typesafe Kotlin APIs from SQL
相关推荐
VirtualApp数据库迁移测试:验证迁移正确性
VirtualApp数据库迁移测试:验证迁移正确性 在移动应用开发中,数据库迁移是确保数据一致性和应用稳定性的关键环节。VirtualApp(简称VA)作为一款
移动开发虚拟化WRF模型高级应用:区域气候模拟与极端天气预测案例
WRF模型高级应用:区域气候模拟与极端天气预测案例 Weather Research and Forecasting WRF 模型是一款被广泛应用的中尺度气象数
GLM-5.3的网络安全能力有多强?CyberGym 84.5分背后的开源SOTA漏洞发现
GLM 5.3的网络安全能力有多强?CyberGym 84.5分背后的开源SOTA漏洞发现 GLM 5.3 是智谱AI开源的大语言模型,与 GLM 5.2 共用
后端ORM
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考