使用 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 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 JUnit 进行单元测试
- 使用 Mockito 进行模拟
- 使用 Espresso 进行 UI 测试
- 调试与性能分析