不会测试的开发不是好开发——鲁迅
![](https://img.haomeiwen.com/i3155702/6c83c7d7eab8fe69.jpeg)
一直以来,关于如何写测试代码的相关内容资源都比较少,之前在优达学城看到了这部分的视频,但由于没有中文字幕,对有些小伙伴可能不太友好。因此我决定将其整理成系列文章,那么就从认识 test 开始吧
本文内容来自 Udacity Advanced Android with Kotlin-Lesson 10-5.1 Testing:Basics
结构
作为 Android 开发者我们知道在 Android Studio 的 Android 视图中有三部分代码
- app 的逻辑代码(main source set)
- androidTest 代码
- local test 代码
![](https://img.haomeiwen.com/i3155702/903bf5a1c8c3a4d5.png)
test 代码知道所有的 main source set 中的代码,因此可以测试这些类。但是 app 代码不知道 test 中的代码,并且 androidTest 和 test 都不知道对方的存在。事实上,当你构建出 apk 并提交应用市场时,测试代码并没有包含在内
依赖引用
下面标记的依赖 使用了 test 的引用方式 testImplementation
和 androidTestImplementation
![](https://img.haomeiwen.com/i3155702/771a992a6429faac.png)
注意:这些 test 代码不会打包到最终的 apk 文件中
testImplementation
引用的 JUnit
依赖只能在 test source set 中使用,这种依赖范围的限制是 Gradle
实现的
简单总结下:
- 三种 source sets:
main
,test
,androidTest
- 测试代码能访问 app 代码
- app 代码 不能 访问测试代码
- 测试不会被打入到 Apk 中
- 依赖范围包括:
testImplementation
和androidTestImplementation
运行第一个 test
我们打开 test source set ,看到其中有一个 ExampleUnitTest
![](https://img.haomeiwen.com/i3155702/55dce2454c012d87.png)
可以看到其内部只有一个 addition_isCorrect()
方法
有两个要素使它成为一个 test:
- 使用了
@Test
注解 - 它存在于两个 test source set 之一
有了这两个要素,这个方法就可以独立的作为 test 运行
本示例 test 测试的内容在第 15 行,它被称之为断言(assertion
)
断言是 test 的核心内容,它检查你的代码或者 app 行为是否符合你的预期
本示例中,断言检查 4 是否等于 2 + 2
按照规定,您需要将你的 预期结果 传入到 expected 参数中,将 实际结果 传入到 actual 参数中
@Test
注解和断言语句都是 JUnit
下的
![](https://img.haomeiwen.com/i3155702/0a297a8b5d60bee7.png)
关于 JUnit
的更详细的的信息,请移步 官方文档
让我们开始运行一下这个 test,右击该方法,点击 Run
![](https://img.haomeiwen.com/i3155702/396622bebfd23c72.png)
紧接着,Run 窗口会出现
![](https://img.haomeiwen.com/i3155702/ee48360ed2fb8edd.png)
可以看到该窗口显示了 test 的信息,显示出 test 是否通过以及有多少 test 通过
下面我们尝试一个 test 不通过的情况,我们加入一个断言,如下所示
![](https://img.haomeiwen.com/i3155702/1eabdf2851b6da93.png)
这次我们点击 Run 窗口的绿色按钮来运行 test
![](https://img.haomeiwen.com/i3155702/f2f002065493d58a.gif)
我们可以看到,即使只有一个断言失败,整个 test 失败了
窗口指出了预期的结果为 5,而实际的结果为 4,并且下边标记处错误发生在第 15 行,可以看到这的确是个 bug
解决好 bug ,我们再次运行 test 。这次我们使用一个不一样的方式
![](https://img.haomeiwen.com/i3155702/3b14e1f8533837f4.gif)
下面介绍一些其他运行 test 的方式
可以右击类名选择 Run 选项
![](https://img.haomeiwen.com/i3155702/76f1a2a7c2728f60.gif)
也可以在左侧视图中右击 test source set 选择 Run 按钮,该方法会运行所有 test。在顶部可以切换要运行的 test ,点击绿色按钮可以运行。也可切换回 app
![](https://img.haomeiwen.com/i3155702/15bcb360ee4eadde.gif)
androidTest VS test
下面我们来对比一下 androidTest
和 test
test | androidTest |
---|---|
Local Tests | Instrumented Tests |
Local machine JVM | Real or emulated devices |
Faster | Slower |
我们来运行一个 androidTest
,可以看到启动了模拟器
![](https://img.haomeiwen.com/i3155702/7072c9abceea3aef.gif)
写一个 test
首先我们针对一个功能来创建 test,如图所示,存在一个 getForkAndOriginRepoStats()
方法用于获取 fork 仓库和原始仓库的数据并返回 StatsResult
,其中 StatsResult
第一个参数为 fork 项目的百分比,第二个参数为原始项目的百分比。我们调用 Generate
,选中 test 选项,在弹出框中选择 JUnit4
,点击 OK 并选择存放在 local test 中。这样我们就创建了一个 test
![](https://img.haomeiwen.com/i3155702/af387b9505f67dd8.gif)
接下来我们编写 test 。可以看到自动创建出的 test 路径与 app code 中的代码路径的包名是对应的。我们先测试项目列表只有一个 item,并且没有 fork ,然后计算 fork 项目的百分比和原始项目的百分比。理论上讲,fork 项目的百分比为 0 ,而原始项目的百分比为 100%,代码如下图所示,我们编写完毕后点击运行
![](https://img.haomeiwen.com/i3155702/37894f4c100ed3f8.gif)
可以看到测试通过
这是一个正常流程,我们还需要测试异常的流程,比如 repos 为 empty list 或者 repos 变量本身为 null
![](https://img.haomeiwen.com/i3155702/c4fd8df5f11517c2.gif)
可以看到我们的代码中没有针对 list 为 empty 或 null 做判断,所以导致了空指针,之后我们修改代码后即可通过测试
事实上,我们上面的编码流程叫做 Test Driven Development(TDD) 有关 TDD
的更多信息,可以移步 Test-Driven Development on Android with the Android Testing Support Library (Google I/O '17)
让你的 test 更具可读性
与写普通代码一样,您需要让您的 test 代码更具可读性,可以从三个方向入手
- 优秀的命名
- Given/When/Then
- 借助断言库
优秀的命名
首先我们来谈谈命名,我们知道 test 方法使用 @Test 注解标记,理论上方法名可以随意命名,但随意的命名会导致可读性的降低,因此需要一些特定的命名规范
测试模块_ 动作或输入_ 结果状态
例如上面的例子我们的命名为:getForkAndOriginRepoStats_noForked_returnHundredZero
第一部分显示我们要测试的是 getForkAndOriginRepoStats()
方法,第二部分代表我们需要的是没有 fork 仓库的数据源,第三部分是结果的状态,0%
Given/When/Then
说完了命名我们来谈谈 Given/When/Then
测试的基本结构是 Given X,When Y,Then Z
还是上面的例子
- Given 为你的测试逻辑提供数据源
- When 是你的实际操作
- Then 检查 test 是否通过
![](https://img.haomeiwen.com/i3155702/40fa09c20d0b6454.png)
借助断言库
上面示例最后的断言代码让人看着很别扭,我们可以借助断言库来提高这部分的可读性
// 之前
assertEquals(result.forkPercent, 0f)
// 之后
assertThat(result.forkPercent, `is`(0f))
下面的语句就像人类的一句话,翻译下来就是 断言 forkPercent 是 0f
这样的写法需要引入一个库 Hamcrest
testImplementation "org.hamcrest:hamcrest-all:1.3"
![](https://img.haomeiwen.com/i3155702/cdd517dd9f875924.png)
注意:由于
is
是 kotlin 中的关键字,因此使用 `is` 来转义
常用的断言库
测试范围
测试范围指一个 test 测试多少代码
例如自动化测试根据测试范围可以分为
- Unit Tests(单元测试)
- Integration Tests(组装测试)
- End to end Tests(端到端测试)
您的测试策略需要覆盖到所有的类型
Unit Tests
上面的示例我们已经写过了 Unit Tests
- 范围是单个方法或类
- 帮助查明失败原因
- 应该运行的很快,通常是本地测试
- 低保真度
他们的范围是单个的方法或类
如果 Unit Tests
失败了,您知道您的代码在哪里出了问题。因为它聚焦于很小一段代码
Unit Tests
也意味着可以快速运行,由于您频繁地修改代码会使得它会频繁的运行,因此需要速度。Unit Tests
通常是本地测试
它们有较低的保真度,因为现实世界您的 app 要执行很多代码而不仅仅是一个方法或者类
Unit Tests
就像检查一个链条的每个环节是否能够正常运行
![](https://img.haomeiwen.com/i3155702/d2516c6f293e9151.png)
但它不检查这些环节组合在一起是否能够运行,为此您需要 Integration Tests
Integration Tests
![](https://img.haomeiwen.com/i3155702/34635b69fe85e686.png)
Integration Tests
拥有更大的范围
- 范围是几个类或单个功能
- 确保几个类共同运行
- 可以使用本地测试或机器测试
就像 Integration
这个词一样,Integration Tests
整合一些类确保他们组合起来的表现符合预期
构建 Integration Tests
的方式是让他们测试单个功能,就像获取指定用户的 Github
仓库
与 Unit Tests
相比,Integration Tests
有着更大的范围,但他们仍运行的很快并且有着很好的保真度
根据具体情况来判断使用本地测试还是机器测试,例如如果您写的 Integration Tests
涉及到了 UI 组件,那么您需要使用真机来测试了
End to end Tests
第三种类型是 End to end Tests
,该测试将一些列功能组合起来一起运行
![](https://img.haomeiwen.com/i3155702/b998d63ed6ad6783.png)
- 范围是 app 的大部分
- 高保真度
- 将 app 作为整体来测试
- 接近真实地使用,应该使用设备测试
End to end Tests
测试 app 的大部分,它十分接近真实地使用,因此速度上会比较慢
它有着最高的保真度并确保您的应用作为一个整体运行
这些测试应该使用设备测试
测试比重
推荐的测试比例是 70% 的单元测试,20% 的组装测试,以及10% 的端到端测试
![](https://img.haomeiwen.com/i3155702/e9da45f01a9280c1.png)
您能否轻松地在各个部分测试您的 app 取决于您的 app 使用的结构
例如,您的应用将所有逻辑都放置在一个 activity 的大的方法中,您可能可以写出端到端测试,但单元测试和组装测试则写不出来
![](https://img.haomeiwen.com/i3155702/83becf06d7517a35.png)
一个更好的架构应该将应用的逻辑拆分为多个方法和类,这允许每部分可以独立的测试
![](https://img.haomeiwen.com/i3155702/21cc76f53b283530.png)
对于单元测试,您可以测试 ViewModel
,Repository
以及 DAO
![](https://img.haomeiwen.com/i3155702/a30041dd12f6fce8.png)
对于组装测试,您可以组合测试 fragment
和 ViewModel
,或者您可以测试整个数据库代码
![](https://img.haomeiwen.com/i3155702/8170b3c669e71123.png)
端到端测试会测试整个应用
![](https://img.haomeiwen.com/i3155702/11b9d6cb05164f57.png)
关于测试的原理,可移步 官方文档
test 的 codelab
关于我
我是 Fly_with24
网友评论