数据层测试:Room数据库、DAO、迁移与存储
数据层是整个应用的基石。你想想看,如果存进去的数据读出来是错的,那UI层再漂亮也没用。今天我们就来聊聊Android数据层的测试,重点放在Room数据库、DAO、迁移,以及SharedPreferences、DataStore和文件存储上。
这部分内容,我建议你把它当作「必杀技」来掌握。因为在实际项目中,数据层出问题往往是最隐蔽的——它不像UI闪退那么明显,但后果可能更严重。
核心要点:数据层测试的目标是确保数据的读写、迁移、持久化都符合预期。说白了,就是保证数据「存得进去、读得出来、格式不变」。
Room数据库测试:@RunWith与@JUnit4
Room是Google官方推荐的本地数据库方案。测试Room数据库,我们通常用@RunWith(AndroidJUnit4::class)来跑。为什么?因为Room依赖Android框架的SQLite实现,不能在纯JVM上跑。
我个人习惯在测试中创建一个内存数据库,而不是用实体设备上的文件数据库。这样测试跑得快,而且每次都是干净的环境。
@RunWith(AndroidJUnit4::class)
class UserDatabaseTest {
private lateinit var database: UserDatabase
@Before
fun setUp() {
// 创建内存数据库,避免污染真实存储
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
UserDatabase::class.java
).build()
}
@After
fun tearDown() {
database.close()
}
@Test
fun testDatabaseCreation() {
assertNotNull(database)
assertTrue(database.isOpen)
}
}
小技巧:用inMemoryDatabaseBuilder创建的数据库,在进程结束后自动销毁。我一般会在@After里手动close,确保资源释放。
DAO测试:验证CRUD操作
DAO(Data Access Object)是操作数据库的接口。测试DAO时,我通常会覆盖增删改查的每个场景。别嫌麻烦,我在项目中遇到过因为DAO查询条件写错,导致线上数据混乱的事故。
@Test
fun testInsertAndQueryUser() {
val user = User(id = 1, name = "张三", age = 28)
database.userDao().insertUser(user)
val loadedUser = database.userDao().getUserById(1)
assertEquals("张三", loadedUser.name)
assertEquals(28, loadedUser.age)
}
@Test
fun testUpdateUser() {
val user = User(id = 1, name = "张三", age = 28)
database.userDao().insertUser(user)
val updatedUser = user.copy(name = "李四")
database.userDao().updateUser(updatedUser)
val result = database.userDao().getUserById(1)
assertEquals("李四", result.name)
}
@Test
fun testDeleteUser() {
val user = User(id = 1, name = "张三", age = 28)
database.userDao().insertUser(user)
database.userDao().deleteUser(user)
val result = database.userDao().getUserById(1)
assertNull(result)
}
你可能会问:「为什么要测这么基础的操作?」嗯,我曾经在一个项目里,因为实体类的字段类型从Int改成了Long,但DAO的查询语句没同步更新,结果数据全读不出来。这种坑,踩过一次就记住了。
数据库迁移测试:版本升级的守护神
数据库迁移是Room测试里最容易翻车的地方。我见过太多团队在发版前才发现迁移脚本写错了,导致用户数据丢失。
测试迁移的核心思路是:先创建一个旧版本的数据库,插入一些数据,然后执行迁移,最后验证数据是否完整。
@Test
fun testMigrationFrom1To2() {
// 创建版本1的数据库
val oldDb = Room.databaseBuilder(
ApplicationProvider.getApplicationContext(),
UserDatabase::class.java,
"test_user_db"
).build()
// 插入旧版本数据
oldDb.userDao().insertUser(User(id = 1, name = "测试用户", age = 25))
// 关闭旧数据库,模拟升级
oldDb.close()
// 执行迁移到版本2
val newDb = Room.databaseBuilder(
ApplicationProvider.getApplicationContext(),
UserDatabase::class.java,
"test_user_db"
).addMigrations(MIGRATION_1_2).build()
// 验证数据还在
val user = newDb.userDao().getUserById(1)
assertNotNull(user)
assertEquals("测试用户", user?.name)
newDb.close()
}
注意:迁移测试一定要覆盖边界情况。比如旧版本有1000条数据,迁移后是否全部保留?我曾经因为迁移脚本里少写了一个ALTER TABLE,导致用户的历史记录全部丢失。那次的教训太深刻了。
SharedPreferences与DataStore测试
SharedPreferences是老牌的数据存储方案,DataStore是它的替代者。测试它们的关键在于:验证写入和读取的一致性。
我个人更推荐DataStore,因为它基于Flow,天然支持异步和响应式。但SharedPreferences在遗留项目中仍然大量存在。
// SharedPreferences测试
@Test
fun testSharedPreferences() {
val context = ApplicationProvider.getApplicationContext<Context>()
val prefs = context.getSharedPreferences("test_prefs", Context.MODE_PRIVATE)
prefs.edit().putString("key", "value").apply()
val result = prefs.getString("key", null)
assertEquals("value", result)
}
// DataStore测试
@Test
fun testDataStore() = runTest {
val context = ApplicationProvider.getApplicationContext<Context>()
val dataStore = context.dataStore
dataStore.edit { prefs ->
prefs[PreferenceKeys.USER_NAME] = "测试用户"
}
val userName = dataStore.data.first()[PreferenceKeys.USER_NAME]
assertEquals("测试用户", userName)
}
避坑指南:DataStore的测试需要依赖runTest协程测试库。我刚开始用的时候忘了加这个,结果测试一直超时。后来才发现是协程作用域的问题。
文件存储测试:读写与权限
文件存储测试相对简单,但容易忽略权限问题。在Android 10以上,外部存储的访问策略变了,测试时要注意。
@Test
fun testFileReadWrite() {
val context = ApplicationProvider.getApplicationContext<Context>()
val file = File(context.filesDir, "test.txt")
// 写入
file.writeText("Hello, World!")
assertTrue(file.exists())
// 读取
val content = file.readText()
assertEquals("Hello, World!", content)
// 删除
file.delete()
assertFalse(file.exists())
}
这里有个细节:filesDir是应用内部存储,不需要额外权限。但如果用外部存储,记得在测试中模拟权限授予。
知识体系总览
下面这张图概括了数据层测试的核心内容。我把它画出来,方便你对照复习。
测试策略总结
| 测试类型 | 核心关注点 | 常用工具 | 常见陷阱 |
|---|---|---|---|
| Room数据库 | 数据库创建、版本管理 | AndroidJUnit4, inMemoryDatabase | 忘记关闭数据库连接 |
| DAO测试 | CRUD操作的正确性 | JUnit, Truth断言库 | 字段类型变更未同步 |
| 迁移测试 | 版本升级数据不丢失 | addMigrations, 旧版本数据模拟 | 迁移脚本遗漏字段 |
| SharedPreferences | 读写一致性 | Context.getSharedPreferences | 多进程并发问题 |
| DataStore | 异步读写、Flow响应 | runTest, PreferenceKeys | 协程作用域未正确配置 |
| 文件存储 | 文件读写、权限管理 | File API, Context.filesDir | Android 10+ 权限变化 |
数据层测试看起来琐碎,但它是整个应用的「压舱石」。我见过太多项目因为数据库迁移没测好,导致用户数据丢失,最后不得不发紧急修复版本。这种经历,一次就够了。
记住:测试不是浪费时间,而是在为未来的自己「买保险」。你投入的每一分钟,都会在某个深夜救你一命。