测试一直都是Android程序员忽视的重要一环
/ 今日科技快讯 /
近日,谷歌母公司Alphabet发布了截至3月31日的2020年第一季度财报。财报显示,Alphabet第一季度营收为412亿美元,较上年同期的363亿美元增长13%;净利润为68亿美元,较上年同期67亿美元增长3%;每股收益为9.87美元,高于去年同期的9.5美元。财报发布后,Alphabet股价盘后暴涨逾8%。
/ 作者简介 /
明天就是五一小长假了,祝大家玩的开心。另外,听说跨省旅游归来可能会面临隔离,建议大家还是谨慎出行。我们节后再见。
本篇文章转自Flywith24的博客,文章主要分享了他对优达学城资料中关于Android Test相关内容的翻译,希望对大家有所帮助。
Flywith24的博客地址:
https://juejin.im/user/57c7f6870a2b58006b1cfd6c
/ 前言 /
一直以来,关于如何写测试代码的相关内容资源都比较少,之前在优达学城看到了这部分的视频,但由于没有中文字幕,对有些小伙伴可能不太友好。因此我决定将其整理成系列文章,那么就从认识test开始吧。
本文内容来自(需要有账号登录之后才能进行查看):
https://classroom.udacity.com/courses/ud940/lessons/fa3a54ab-2da2-46b9-af73-e7f05b20f90f/concepts/374e1e3c-7018-4607-97b3-c50e1061ceda
/ 结构 /
作为Android开发者我们知道在Android Studio的Android视图中有三部分代码:
app的逻辑代码(main source set)
androidTest代码
local test代码
test代码知道所有的main source set中的代码,因此可以测试这些类。但是app代码不知道test中的代码,并且androidTest和test都不知道对方的存在。事实上,当你构建出apk并提交应用市场时,测试代码并没有包含在内。
/ 依赖引用 /
下面标记的依赖 使用了test的引用方式testImplementation和androidTestImplementation。
注意:这些test代码不会打包到最终的apk文件中。
testImplementation引用的JUnit依赖只能在test source set中使用,这种依赖范围的限制是Gradle实现的。简单总结下:
三种source sets:main,test,androidTest
测试代码能访问app代码
app 代码 不能 访问测试代码
测试不会被打入到Apk中
依赖范围包括:testImplementation和androidTestImplementation
/ 运行第一个test /
我们打开test source set ,看到其中有一个ExampleUnitTest。
可以看到其内部只有一个addition_isCorrect()方法。有两个要素使它成为一个test:
使用了@Test注解
它存在于两个test source set之一
有了这两个要素,这个方法就可以独立的作为test运行。本示例test测试的内容在第15行,它被称之为断言(assertion)。断言是test的核心内容,它检查你的代码或者app行为是否符合你的预期。本示例中,断言检查 4 是否等于 2 + 2。按照规定,您需要将你的预期结果传入到expected参数中,将实际结果传入到actual参数中。@Test注解和断言语句都是JUnit下的。
关于JUnit的更详细的的信息,请移步官方文档:
https://junit.org/junit4/
让我们开始运行一下这个test,右击该方法,点击Run。
紧接着,Run窗口会出现:
可以看到该窗口显示了test的信息,显示出test是否通过以及有多少test通过。下面我们尝试一个test不通过的情况,我们加入一个断言,如下所示:
这次我们点击Run窗口的绿色按钮来运行test:
我们可以看到,即使只有一个断言失败,整个test失败了。窗口指出了预期的结果为5,而实际的结果为4,并且下边标记处错误发生在第15行,可以看到这的确是个bug。解决好bug ,我们再次运行test 。这次我们使用一个不一样的方式
下面介绍一些其他运行 test 的方式。
可以右击类名选择Run选项:
也可以在左侧视图中右击test source set选择Run按钮,该方法会运行所有test。在顶部可以切换要运行的test ,点击绿色按钮可以运行。也可切换回 app。
/ Android Test VS Test /
下面我们来对比一下androidTest和test:
test | androidTest |
Local Tests | Instrumented Tests |
Local machine JVM | Real or emulated devices |
Faster | Slower |
我们来运行一个androidTest,可以看到启动了模拟器:
/ 写一个test /
首先我们针对一个功能来创建test,如图所示,存在一个getForkAndOriginRepoStats()方法用于获取fork仓库和原始仓库的数据并返回StatsResult,其中StatsResult第一个参数为fork项目的百分比,第二个参数为原始项目的百分比。我们调用Generate ,选中test选项,在弹出框中选择JUnit4,点击OK并选择存放在local test中。这样我们就创建了一个test。
接下来我们编写test 。可以看到自动创建出的test路径与app code中的代码路径的包名是对应的。我们先测试项目列表只有一个item,并且没有fork ,然后计算fork项目的百分比和原始项目的百分比。理论上讲,fork项目的百分比为0 ,而原始项目的百分比为100%,代码如下图所示,我们编写完毕后点击运行。
可以看到测试通过这是一个正常流程,我们还需要测试异常的流程,比如repos为empty list或者repos变量本身为null。
可以看到我们的代码中没有针对list为empty或null做判断,所以导致了空指针,之后我们修改代码后即可通过测试。
事实上,我们上面的编码流程叫做Test Driven Development(TDD)。
/ 让你的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 是否通过
借助断言库
上面示例最后的断言代码让人看着很别扭,我们可以借助断言库来提高这部分的可读性。
// 之前
assertEquals(result.forkPercent, 0f)
// 之后
assertThat(result.forkPercent, `is`(0f))
下面的语句就像人类的一句话,翻译下来就是断言forkPercent是0f。这样的写法需要引入一个库Hamcrest。
testImplementation "org.hamcrest:hamcrest-all:1.3"
/ 测试范围 /
测试范围指一个test测试多少代码。例如自动化测试根据测试范围可以分为:
Unit Tests(单元测试)
Integration Tests(集成测试)
End to end Tests(端到端测试)
您的测试策略需要覆盖到所有的类型。
Unit Tests
上面的示例我们已经写过了Unit Tests。
范围是单个方法或类
帮助查明失败原因
应该运行的很快,通常是本地测试
低保真度
他们的范围是单个的方法或类。如果Unit Tests失败了,您知道您的代码在哪里出了问题。因为它聚焦于很小一段代码。Unit Tests也意味着可以快速运行,由于您频繁地修改代码会使得它会频繁的运行,因此需要速度。Unit Tests通常是本地测试。它们有较低的保真度,因为现实世界您的app要执行很多代码而不仅仅是一个方法或者类。Unit Tests就像检查一个链条的每个环节是否能够正常运行。
但它不检查这些环节组合在一起是否能够运行,为此您需要Integration Tests。
Integration Tests
Integration Tests拥有更大的范围:
范围是几个类或单个功能
确保几个类共同运行
可以使用本地测试或机器测试
就像Integration这个词一样,Integration Tests整合一些类确保他们组合起来的表现符合预期。构建Integration Tests的方式是让他们测试单个功能,就像获取指定用户的Github仓库。
与Unit Tests 相比,Integration Tests有着更大的范围,但他们仍运行的很快并且有着很好的保真度。根据具体情况来判断使用本地测试还是机器测试,例如如果您写的Integration Tests涉及到了UI组件,那么您需要使用真机来测试了。
End to end Tests
第三种类型是End to end Tests,该测试将一些列功能组合起来一起运行。
范围是app的大部分
高保真度
将app作为整体来测试
接近真实地使用,应该使用设备测试
End to end Tests测试app的大部分,它十分接近真实地使用,因此速度上会比较慢,它有着最高的保真度并确保您的应用作为一个整体运行。这些测试应该使用设备测试。
/ 测试比重 /
推荐的测试比例是70%的单元测试,20%的集成测试,以及10%的端到端测试。
您能否轻松地在各个部分测试您的app取决于您的app使用的结构。例如,您的应用将所有逻辑都放置在一个activity的大的方法中,您可能可以写出端到端测试,但单元测试和集成测试则写不出来。
一个更好的架构应该将应用的逻辑拆分为多个方法和类,这允许每部分可以独立的测试。
对于单元测试,您可以测试ViewModel,Repository以及DAO。
对于集成测试,您可以组合测试fragment和ViewModel ,或者您可以测试整个数据库代码。
端到端测试会测试整个应用。
关于测试的原理,可移步。官方文档:
https://developer.android.com/training/testing/fundamentals
test的codelab:
https://codelabs.developers.google.com/codelabs/advanced-android-kotlin-training-testing-basics/index.html?index=..%2F..index#0
推荐阅读:
欢迎关注我的公众号
学习技术或投稿
长按上图,识别图中二维码即可关注