VocabVerse创新实训(15)-查询历史和单词模块单元测试

发布于:2025-06-15 ⋅ 阅读:(19) ⋅ 点赞:(0)

第十七周山大软院创新实训记录-查询历史&单词模块单元测试

一、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 被正确调用

2.4 测试用例展示

2.5 成果展示


网站公告

今日签到

点亮在社区的每一天
去签到