Kime 测试指南
测试架构
本项目采用多层测试策略,确保代码质量和稳定性。
测试类型
1. 单元测试 (Unit Tests)
- 位置:
app/src/test/java/ - 目的: 测试纯 Kotlin 逻辑,不依赖 Android 框架
- 适用场景:
- 工具类方法测试
- 业务逻辑测试
- 算法测试
示例测试类:
SchemaConfigHelperTest- 测试配置解析逻辑ExtensionManagerTest- 测试插件管理逻辑
2. 仪器测试 (Instrumented Tests)
- 位置:
app/src/androidTest/java/ - 目的: 测试与 Android 框架交互的组件
- 运行速度: 较慢(需要在设备/模拟器上运行)
- 适用场景:
- 数据库操作测试
- SharedPreferences 测试
- Context 相关测试
- Native 库交互测试
示例测试类:
RimeEngineTest- 测试 RIME 引擎初始化和基本操作ExtensionManagerInstrumentedTest- 测试插件管理器的实际加载
3. UI 测试 (UI Tests)
- 位置:
app/src/androidTest/java/ - 框架: Jetpack Compose Testing
- 目的: 测试 UI 组件的行为和外观
- 运行速度: 中等
- 适用场景:
- Compose 组件测试
- 用户交互测试
- 界面状态测试
示例测试类:
CandidateBarTest- 测试候选词栏 UI 组件
运行测试
运行所有单元测试
bash
./gradlew test运行所有仪器测试
bash
./gradlew connectedAndroidTest运行特定测试类
bash
# 单元测试
./gradlew test --tests "com.kingzcheung.kime.settings.SchemaConfigHelperTest"
# 仪器测试
./gradlew connectedAndroidTest --tests "com.kingzcheung.kime.rime.RimeEngineTest"运行所有测试
bash
./gradlew connectedCheck测试最佳实践
1. 测试命名规范
使用描述性测试名称,使用反引号包含空格:
kotlin
@Test
fun `parseSchemaYaml should parse valid schema`() {
// 测试代码
}2. 测试结构
遵循 AAA 模式:
- Arrange - 准备测试数据
- Act - 执行被测试的代码
- Assert - 验证结果
kotlin
@Test
fun `getCandidates should return empty array when not initialized`() {
// Arrange
val engine = RimeEngine.getInstance()
// Act
val candidates = engine.getCandidates()
// Assert
assertTrue(candidates.isEmpty())
}3. 使用测试规则
kotlin
class MyTest {
@get:Rule
val coroutineTestRule = CoroutineTestRule()
@get:Rule
val sharedPreferencesRule = SharedPreferencesTestRule()
}4. 测试隔离
每个测试应该独立运行,不依赖其他测试的结果:
kotlin
@Before
fun setup() {
// 重置状态
ExtensionManager.release()
}
@After
fun tearDown() {
// 清理资源
ExtensionManager.release()
}5. Mock 外部依赖
使用 Mockito 模拟外部依赖:
kotlin
@Test
fun `plugin should return emojis correctly`() {
val mockPlugin = mock<EmojiPlugin>()
`when`(mockPlugin.getEmojis(null, null, 10)).thenReturn(
listOf(EmojiItem("1", "(^_^)", "(^_^)", null, "kaomoji"))
)
val result = mockPlugin.getEmojis(null, null, 10)
assertEquals(1, result.size)
}测试覆盖率
生成测试覆盖率报告
bash
./gradlew createDebugCoverageReport报告位置:app/build/reports/coverage/debug/index.html
持续集成 (CI)
在 CI 环境中运行测试:
yaml
# .github/workflows/test.yml
- name: Run Unit Tests
run: ./gradlew test
- name: Run Instrumented Tests
run: ./gradlew connectedAndroidTest测试工具类
CoroutineTestRule
用于测试协程代码:
kotlin
class MyViewModelTest {
@get:Rule
val coroutineTestRule = CoroutineTestRule()
@Test
fun testCoroutine() = runTest {
// 测试协程代码
}
}SharedPreferencesTestRule
用于测试 SharedPreferences:
kotlin
class SettingsTest {
@get:Rule
val prefsRule = SharedPreferencesTestRule()
@Test
fun testSettings() {
val prefs = prefsRule.getSharedPreferences()
// 测试代码
}
}常见测试场景
1. 测试 Singleton
kotlin
@Test
fun `getInstance should return singleton`() {
val instance1 = RimeEngine.getInstance()
val instance2 = RimeEngine.getInstance()
assertSame(instance1, instance2)
}2. 测试异步操作
kotlin
@Test
fun `predict should return results`() = runTest {
val result = ExtensionManager.predict(context, "测试", 5)
assertNotNull(result)
assertTrue(result.isNotEmpty())
}3. 测试 Compose UI
kotlin
@Test
fun `CandidateBar should display candidates`() {
val candidates = listOf("你好", "世界")
composeTestRule.setContent {
CandidateBar(
candidates = candidates,
selectedIndex = 0,
onCandidateClick = {},
onCandidateLongClick = {}
)
}
composeTestRule.onNodeWithText("你好").assertIsDisplayed()
}故障排查
测试失败常见原因
Native 库未加载
- 确保在仪器测试中正确初始化 RimeEngine
- 使用
@Before注解的 setup 方法初始化
Context 为空
- 使用
InstrumentationRegistry.getInstrumentation().targetContext
- 使用
协程测试超时
- 使用
runTest而不是runBlocking - 配置
TestDispatcher
- 使用
Compose 测试找不到节点
- 检查是否使用了正确的语义属性
- 使用
printToLog()调试 UI 树