0Pricing
Android Academy · 课时

使用 JUnit 进行单元测试

使用 JUnit 4 编写快速、可靠的单元测试。使用 @Test、@Before、@After 和断言,使用 InstantTaskExecutorRule 测试 LiveData,并使用 runTest 测试协程。

使用 JUnit 进行单元测试 是 CoddyKit 上的免费 Android Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。

为什么要编写测试

测试不是奢侈品,而是您的安全网:

  • 在用户发现错误之前捕获它们
  • 放心重构(如果测试通过,就说明没有破坏功能)
  • 记录预期行为
  • 添加功能时防止回归

经过充分测试的代码通常也具有更好的设计——如果代码很难测试,说明设计可能耦合得过于紧密。

Android 测试的类型

分为两类:

  • 单元测试(test/ 文件夹)— 在 JVM 上运行,不需要设备。速度快。用于测试普通的 Kotlin/Java 逻辑。
  • 插桩测试(androidTest/ 文件夹)— 在设备或模拟器上运行。速度较慢。用于测试 Android 特定代码(Room 数据库、界面)。

业务逻辑应优先使用单元测试。只有必须与 Android API 交互时,才使用插桩测试。

JUnit 4 设置

Android 项目默认包含 JUnit 4。将以下内容添加到 app/build.gradle:

// app/build.gradle:
dependencies {
    testImplementation 'junit:junit:4.13.2'
    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
    testImplementation 'androidx.arch.core:core-testing:2.2.0'  // for LiveData
}

您的第一个 JUnit 测试

在 src/test/java/ 中创建一个类,并使用 @Test 注解标记测试方法:

import org.junit.Test
import org.junit.Assert.*

class CalculatorTest {

    private val calc = Calculator()

    @Test
    fun `addition returns correct sum`() {
        val result = calc.add(2, 3)
        assertEquals(5, result)
    }

    @Test
    fun `division by zero throws exception`() {
        assertThrows(ArithmeticException::class.java) {
            calc.divide(10, 0)
        }
    }
}

@Before 和 @After

使用生命周期注解,在每次测试前后设置和清理状态:

class UserRepositoryTest {

    private lateinit var repo: UserRepository
    private lateinit var fakeDao: FakeUserDao

    @Before
    fun setUp() {
        fakeDao = FakeUserDao()          // fresh instance before each test
        repo = UserRepository(fakeDao)
    }

    @After
    fun tearDown() {
        fakeDao.clear()                  // clean up after each test
    }

    @Test
    fun `getUsers returns all users`() {
        fakeDao.insert(User(1, "Alice"))
        fakeDao.insert(User(2, "Bob"))
        assertEquals(2, repo.getUsers().size)
    }
}

常用断言

JUnit 在 Assert.* 中提供断言方法:

// Equality:
assertEquals(expected, actual)
assertNotEquals(unexpected, actual)

// Null:
assertNull(value)
assertNotNull(value)

// Boolean:
assertTrue(condition)
assertFalse(condition)

// Same object reference:
assertSame(expected, actual)

// Custom message on failure:
assertEquals("User ID should be 1", 1, user.id)

// Exception:
assertThrows(IllegalArgumentException::class.java) {
    User(id = -1, name = "")
}

测试 ViewModel

要测试 LiveData,请添加 InstantTaskExecutorRule,使 LiveData 能够同步发布数据:

class UserViewModelTest {

    @get:Rule
    val instantTaskRule = InstantTaskExecutorRule()

    private val fakeRepo = FakeUserRepository()
    private lateinit var viewModel: UserViewModel

    @Before
    fun setUp() {
        viewModel = UserViewModel(fakeRepo)
    }

    @Test
    fun `users are loaded on init`() {
        fakeRepo.usersToReturn = listOf(User(1, "Alice"), User(2, "Bob"))
        viewModel.loadUsers()

        val result = viewModel.users.value
        assertNotNull(result)
        assertEquals(2, result?.size)
    }
}

测试协程

使用 TestCoroutineDispatcher / StandardTestDispatcher 控制测试中的协程执行:

class SyncWorkerTest {

    @get:Rule
    val instantTaskRule = InstantTaskExecutorRule()

    private val testDispatcher = StandardTestDispatcher()

    @Before
    fun setUp() {
        Dispatchers.setMain(testDispatcher)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }

    @Test
    fun `sync completes successfully`() = runTest {
        val repo = FakeRepository()
        val worker = SyncUseCase(repo, testDispatcher)
        val result = worker.run()
        assertTrue(result.isSuccess)
    }
}

Fake 与 Mock

在测试中替换真实依赖有两种策略:

  • Fake — 专为测试设计的可运行实现(例如,用内存列表代替真实数据库)。易于编写和阅读,也不需要库。
  • Mock — 自动生成的存根,可以记录调用并让您验证交互(Mockito、MockK)。功能更强,但过度使用时可能变得脆弱。

简单场景优先使用 Fake;需要验证方法调用时使用 Mock。

编写 Fake

Fake 实现与真实类相同的接口,但使用内存存储:

interface UserDao {
    fun getAll(): List<User>
    fun insert(user: User)
}

class FakeUserDao : UserDao {
    private val storage = mutableListOf<User>()

    override fun getAll(): List<User> = storage.toList()
    override fun insert(user: User) { storage.add(user) }

    fun clear() { storage.clear() }
}

测试命名约定

好的测试名称应描述被测试的内容以及预期结果。在 Kotlin 中使用反引号包围的函数名称,可以获得易读的描述:

  • `getUsers returns empty list when no users exist`
  • `login throws exception when password is blank`
  • `add returns correct sum for negative numbers`

模式:`subject does X when Y`

快速检查

@Before 在 JUnit 测试类中的作用是什么?

回顾:使用 JUnit 进行单元测试

可靠的应用都经过测试:

  • 单元测试放在 src/test/ 中,在 JVM 上运行,速度快
  • 使用 @Test 标注,并使用 assertEquals、assertTrue、assertThrows
  • 使用 @Before / @After 进行设置和清理
  • 使用 InstantTaskExecutorRule 同步测试 LiveData
  • 使用 runTest + StandardTestDispatcher 测试协程
  • 简单替换优先使用 Fake,而不是 Mock

下一节:使用 Mockito 模拟依赖,以验证交互。

常见问题解答

「使用 JUnit 进行单元测试」课时是免费的吗?

是的 — 「使用 JUnit 进行单元测试」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。

「使用 JUnit 进行单元测试」这节课中我会学到什么?

使用 JUnit 4 编写快速、可靠的单元测试。使用 @Test、@Before、@After 和断言,使用 InstantTaskExecutorRule 测试 LiveData,并使用 runTest 测试协程。 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Android Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「使用 JUnit 进行单元测试」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Android Academy 课中编写并运行代码吗?

能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 JUnit 进行单元测试
  2. 使用 Mockito 进行模拟
  3. 使用 Espresso 进行 UI 测试
  4. 调试与性能分析
← 返回 Android Academy