第十七周山大软院创新实训记录-查询历史&单词模块单元测试
一、SearchHistoryRepositoryTest
1.1 总体介绍
@OptIn(ExperimentalCoroutinesApi::class)
class SearchHistoryRepositoryTest {
private lateinit var searchHistoryDAO: SearchHistoryDAO
private lateinit var searchHistoryRepository: SearchHistoryRepository
@Before
fun setUp() {
searchHistoryDAO = mockk()
searchHistoryRepository = SearchHistoryRepository(searchHistoryDAO)
}
@Test
fun getTotalSearchHistoryNum_returnsCorrectCount() = runTest {
// Arrange
coEvery { searchHistoryDAO.countSearchHistory() } returns 42
// Act
val count = searchHistoryRepository.getTotalSearchHistoryNum()
// Assert
assertEquals(42, count)
coVerify(exactly = 1) { searchHistoryDAO.countSearchHistory() }
}
@Test
fun insertSearchHistory_callsDAOWithCorrectData() = runTest {
// Arrange
val history = SearchHistory(input = "hello", searchDate = Calendar.getInstance())
coEvery { searchHistoryDAO.insertSearchHistory(history) } returns Unit
// Act
searchHistoryRepository.insertSearchHistory(history)
// Assert
coVerify(exactly = 1) { searchHistoryDAO.insertSearchHistory(history) }
}
@Test
fun removeSearchHistory_callsDAODelete() = runTest {
// Arrange
coEvery { searchHistoryDAO.deleteSearchHistoryList() } returns Unit
// Act
searchHistoryRepository.removeSearchHistory()
// Assert
coVerify(exactly = 1) { searchHistoryDAO.deleteSearchHistoryList() }
}
}
上述代码是使用 Kotlin 编写的单元测试类,用于测试 SearchHistoryRepository 类的行为是否符合预期,测试使用了以下关键技术栈:
JUnit:用于编写和运行测试
MockK:用于创建模拟对象(Mock),使得测试不依赖真实的数据库实现
Kotlin Coroutines 测试:通过 runTest 测试挂起函数
Assert & Verify:使用 assertEquals 来断言返回结果是否正确,使用 coVerify 来验证方法是否被调用
1.2 类与依赖解释
private lateinit var searchHistoryDAO: SearchHistoryDAO
private lateinit var searchHistoryRepository: SearchHistoryRepository
SearchHistoryDAO 是接口类,代表数据库操作层
SearchHistoryRepository 是数据仓库类,封装了对 DAO 的调用逻辑,供 ViewModel 或其他业务层使用
@Before
fun setUp() {
searchHistoryDAO = mockk()
searchHistoryRepository = SearchHistoryRepository(searchHistoryDAO)
}
每个测试开始前,都会调用 setUp() 初始化模拟的 DAO,并将它注入到 SearchHistoryRepository
1.3测试方法详解
1.3.1 测试总历史记录数量返回正确
@Test
fun getTotalSearchHistoryNum_returnsCorrectCount() = runTest {
// Arrange
coEvery { searchHistoryDAO.countSearchHistory() } returns 42
// Act
val count = searchHistoryRepository.getTotalSearchHistoryNum()
// Assert
assertEquals(42, count)
coVerify(exactly = 1) { searchHistoryDAO.countSearchHistory() }
}
上述代码:
Arrange(准备):模拟 DAO 的 countSearchHistory() 方法返回 42
Act(执行):调用仓库的方法 getTotalSearchHistoryNum()
Assert(断言):验证返回值是否为 42;验证 DAO 的方法是否被恰好调用一次
1.3.2 测试插入历史记录是否调用 DAO
@Test
fun insertSearchHistory_callsDAOWithCorrectData() = runTest {
val history = SearchHistory(input = "hello", searchDate = Calendar.getInstance())
coEvery { searchHistoryDAO.insertSearchHistory(history) } returns Unit
searchHistoryRepository.insertSearchHistory(history)
coVerify(exactly = 1) { searchHistoryDAO.insertSearchHistory(history) }
}
创建一个模拟的 SearchHistory 实例;模拟 DAO 的 insertSearchHistory() 方法可以正常运行;调用仓库方法插入历史记录;验证 DAO 是否被正确调用,并传入相同的 history 对象
1.3.3 测试删除所有历史记录是否调用 DAO 的删除方法
@Test
fun removeSearchHistory_callsDAODelete() = runTest {
coEvery { searchHistoryDAO.deleteSearchHistoryList() } returns Unit
searchHistoryRepository.removeSearchHistory()
coVerify(exactly = 1) { searchHistoryDAO.deleteSearchHistoryList() }
}
模拟 DAO 的删除方法 deleteSearchHistoryList() 成功返回;调用仓库的 removeSearchHistory() 方法;验证 DAO 的删除方法是否被正确调用一次
1.4 其它技术要点
@OptIn(ExperimentalCoroutinesApi::class):启用了协程测试的实验 API
runTest { ... }:用于运行协程环境下的挂起函数测试
mockk():来自 MockK 库,创建模拟对象
coEvery { ... } returns ...:MockK 中用于模拟挂起函数的行为
coVerify { ... }:验证挂起函数是否被调用
1.5 测试用例展示
1.6 成果展示
二、WordRepositoryTest
2.1 总体介绍
class WordRepositoryTest {
private lateinit var wordDAO: WordDAO
private lateinit var repository: WordRepository
@Before
fun setup() {
wordDAO = mockk()
repository = WordRepository(wordDAO)
}
@Test
fun `searchWord returns word when dao returns data`() = runTest {
val spelling = "apple"
val expectedWord = Word(
spelling = spelling,
ipa = "ˈæpəl",
cn = mapOf("noun" to listOf("苹果")),
en = mapOf("noun" to listOf("fruit")),
pronName = "UK"
).apply { id = 1L }
coEvery { wordDAO.getWordBySpelling(spelling) } returns expectedWord
val result = repository.searchWord(spelling)
assertNotNull(result)
assertEquals(expectedWord.spelling, result?.spelling)
assertEquals(expectedWord.ipa, result?.ipa)
assertEquals(expectedWord.pronName, result?.pronName)
coVerify(exactly = 1) { wordDAO.getWordBySpelling(spelling) }
}
@Test
fun `searchWord returns null when dao returns null`() = runTest {
val spelling = "nonexistent"
coEvery { wordDAO.getWordBySpelling(spelling) } returns null
val result = repository.searchWord(spelling)
assertNull(result)
coVerify(exactly = 1) { wordDAO.getWordBySpelling(spelling) }
}
@Test
fun `getSearchSuggestions returns list of strings`() = runTest {
val spellingPrefix = "app"
val expectedSuggestions = listOf("apple", "application", "appetite")
coEvery { wordDAO.getWordSuggestionsBySimilarSpelling("$spellingPrefix%") } returns expectedSuggestions
val result = repository.getSearchSuggestions(spellingPrefix)
assertEquals(expectedSuggestions, result)
coVerify(exactly = 1) { wordDAO.getWordSuggestionsBySimilarSpelling("$spellingPrefix%") }
}
}
上述代码用 Kotlin 编写的单元测试类,用于测试 WordRepository 中封装的单词查询与建议功能是否正确
2.2 类和依赖解释
private lateinit var wordDAO: WordDAO
private lateinit var repository: WordRepository
@Before
fun setup() {
wordDAO = mockk()
repository = WordRepository(wordDAO)
}
WordDAO:是访问数据库中“单词”表的接口;WordRepository:是仓库类,用于封装对 DAO 的调用,通常供 ViewModel 使用;测试前调用 setup(),创建模拟 DAO,并构建仓库对象
2.3 测试方法详解
2.3.1 测试查询到存在单词的情况
@Test
fun `searchWord returns word when dao returns data`() = runTest {
val spelling = "apple"
val expectedWord = Word(...)
coEvery { wordDAO.getWordBySpelling(spelling) } returns expectedWord
val result = repository.searchWord(spelling)
assertNotNull(result)
assertEquals(expectedWord.spelling, result?.spelling)
...
coVerify(exactly = 1) { wordDAO.getWordBySpelling(spelling) }
}
测试目的:验证当 DAO 成功返回 Word 数据时,仓库方法 searchWord() 能正确返回该数据
测试步骤:
Arrange-模拟 DAO 返回一个 Word 实例
Act-调用 repository.searchWord(spelling)
Assert-检查结果不为 null,各字段正确,DAO 被正确调用
2.3.2 测试查询不到单词的情况
@Test
fun `searchWord returns null when dao returns null`() = runTest {
val spelling = "nonexistent"
coEvery { wordDAO.getWordBySpelling(spelling) } returns null
val result = repository.searchWord(spelling)
assertNull(result)
coVerify(exactly = 1) { wordDAO.getWordBySpelling(spelling) }
}
测试目的:验证当 DAO 返回 null 时(即数据库中无此单词),仓库方法也应返回 null
测试步骤:
Arrange-模拟 DAO 返回 null
Act-调用 repository.searchWord()
Assert-结果应为 null,DAO 被正确调用一次
2.3.3 测试获取拼写建议
@Test
fun `getSearchSuggestions returns list of strings`() = runTest {
val spellingPrefix = "app"
val expectedSuggestions = listOf("apple", "application", "appetite")
coEvery { wordDAO.getWordSuggestionsBySimilarSpelling("$spellingPrefix%") } returns expectedSuggestions
val result = repository.getSearchSuggestions(spellingPrefix)
assertEquals(expectedSuggestions, result)
coVerify(exactly = 1) { wordDAO.getWordSuggestionsBySimilarSpelling("$spellingPrefix%") }
}
测试目的:验证仓库中的 getSearchSuggestions() 方法是否正确封装 DAO 的模糊搜索逻辑
测试步骤:
Arrange-模拟 DAO 返回推荐单词列表
Act-调用仓库方法获取建议
Assert-检查返回值与模拟数据一致,DAO 被正确调用