单元测试是软件开发的关键阶段. 它带来了一种称为测试驱动开发(TDD)的开发范式.
即使只有一个测试, 也胜过没有测试.
如果不编写测试, 就等于在编写历史遗留代码.
为什么要编写单元测试?
- 我们会犯错.
- 我们希望代码能正常工作.
- 我们希望开发速度更快, 信心更足, 回归更少.
说到 Android 以及各种移动平台, 应用程序测试可能是一项挑战. 实施单元测试并遵循测试驱动开发原则或类似原则, 至少常常会让人感觉不直观. 不过, 测试非常重要, 不应被视为理所当然或被忽视.
让我们跳转到单元测试的一些基本原理.
包结构
创建一个新的 Android 项目时, 默认情况下会得到以下三个源代码集. 它们是
源代码集:
main
: 包含应用程序代码.androidTest
: 包含称为 Instrumented tests 的测试.test
: 包含称为本地测试的测试.
本地测试与Instrumented测试的区别在于它们的运行方式.
本地测试(test 源代码集)
这些测试在开发机器的 JVM 上本地运行, 不需要模拟器或物理设备. 因此, 这些测试运行速度很快, 但保真度较低, 这意味着它们的行为与真实世界中的行为不太一样.
Instrumented tests(androidTest 源代码集)
这些测试在真实或模拟的Android设备上运行, 因此能反映真实世界中发生的情况, 但速度也要慢得多.
测试Runner
测试Runner是运行测试的 JUnit 组件. 没有测试Runner, 我们的测试就无法运行. JUnit 提供了一个默认的测试Runner, 我们会自动获得它.
android {
defaultConfig {
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
}
Android Studio 提供了生成测试的工具, 可以帮助你实现类的测试. 右键单击要测试的类, 选择 Generate > Test.
点击Test后, 你就可以创建测试类了.
让我们来看看测试类中会用到的一些注解.
- @Test: 该注解用于将方法标记为测试用例. 在编译类时,
@Test
注解会告诉 Java 编译器将该方法作为测试用例运行. - @Before: 此注解用于将方法标记为设置方法.
@Before
注解告诉 Java 编译器在类中的每个测试用例之前运行该方法. 这对于在运行每个测试用例之前设置测试环境非常有用. - @After: 此注解用于将方法标记为拆卸方法.
@After
注解告诉 Java 编译器在类中的每个测试用例之后运行该方法. 这有助于在每个测试用例运行后清理测试环境. - @Ignore: 此注解用于将方法标记为忽略的测试用例. 注解
@Ignore
告诉 Java 编译器不要将该方法作为测试用例运行. 这对于暂时忽略尚未准备好运行的测试用例非常有用.
断言是测试的核心. 它是一条代码语句, 用于检查代码或应用程序是否按预期运行. 在本例中, 断言是assertEquals(4, 2 + 2)
, 用于检查 4 是否等于 2 + 2.
测试的策略
在编写可读测试时, 你还可以使用其他一些策略. 这两种策略在你刚才写的测试中都有所体现.
Given, When, Then
思考测试结构的一种方法是遵循Given, When, Then
的测试记忆法. 它将测试分为三个部分:
- Given: 设置测试所需的对象和应用程序状态. 对于这个测试, 什么是
Given
. - When: 对测试对象执行实际操作.
- Then: 这是你实际检查执行操作时发生的情况的地方, 在这里你要检查测试是否通过或失败. 这通常是一些断言函数调用.
请注意, Arrange, Act, Assert(AAA) 的测试记忆法是一个类似的概念.
@Test
fun getUserProfile_givenUserId_returnUser() {
// GIVEN
val userId = 1
val user = User(userId,"","TestUser")
// WHEN
doReturn(Observable.just(user)).`when`(webservice).getMyProfile(userId)
presenter.getUserProfile(anyString())
// THEN
verify(profileView).render(ProfileState.DataState(user))
}
依赖关系配置
通常, 在添加依赖关系时使用implementation
. 当你准备与全世界分享你的应用程序时, 最好不要在我们的应用程序中加入任何测试代码或依赖项, 以免臃肿 APK 的大小. 你可以使用 gradle 配置来指定主代码或测试代码中是否包含某个库.
最常见的配置有:
implementation
- 所有源代码集(包括test
源代码集)中都有该依赖库.testImplementation
- 依赖关系仅在test
源代码集中可用.androidTestImplementation
- 依赖关系仅在androidTest
源代码集中可用.
测试替身(Test Double)
解决这个问题的办法是, 当你测试资源库时, 不要使用真正的网络或数据库代码, 而是使用一个测试替身. 测试替身是专为测试而设计的类的一个版本. 它的目的是在测试中取代类的真实版本. 这类似于特技替身是专门从事特技表演的演员, 在危险动作中代替真正的演员.
以下是一些测试替身的类型:
Fake
测试替身有一个类的"生效的"实现, 但其实现方式使其适合测试, 但不适合生产.
Mock
跟踪其方法被调用的测试替身. 测试通过或失败取决于它的方法是否被正确调用. 当你模拟一个对象时, 它会创建一个该类的空实现.
命名规则
测试名称应有助于理解测试的作用. 命名规则如下:
subjectUnderTest_actionOrInput_resultState
- 测试对象是被测试的方法或类(
getUserProfile
). - 其次是操作或输入(
givenUserId
). - 最后是预期结果(
returnUser
).
专业提示
当多个测试有重复的设置代码时, 可使用 @Before 注解创建一个设置方法并删除重复代码.
好了, 今天的内容就分享到这里啦!
一家之言, 欢迎拍砖!
Happy Coding! Stay GOLDEN!