IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android 单元测试,从小白到入门开始 -> 正文阅读

[移动开发]Android 单元测试,从小白到入门开始

1 引言

1.1? 背景

????????随着 Android 应用越来越壮大,对应代码量显著增加,代码逻辑也日趋复杂,此时需要采取一定的行动去保证代码质量,减少逻辑漏洞等,于是严格地执行单元测试编写任务,将单元测试落实到平常开发和维护任务当中去,就是很重要的一环,不可忽视。

????????然而,很多应用开发者之前并未编写过单元测试代码,那么如果有一篇通俗易懂并带有操作步骤的文章,能帮助应用开发者完成从单元测试小白到入门的过渡,就再好不过了,于是本文就是在此情况写就的,如有不好之处,请多多包涵,谢谢。

1.2 术语和缩略语

缩略语/术语

全 称

说 明

Module模块本文指 Android Studio 项目中包涵的多个模块其中之一
TDDTest-Driven Development测试驱动开发,在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。
单测Android Unit Test本文为了行文方便,使用单测表示 Android 单元测试。
测开Test Development Engineer测试开发工程师,本文主要指进行白盒测试的测开。

1.3 内容大纲

  • 闲谈单测
  • 如何开始单测?
  • 如何统计覆盖率?

2 闲谈单测

2.1 说说我理解的单测

对测试金字塔的理解

????????本文所指的单测为金字塔最底层占70%的小型测试(单测) Unit tests不包括最顶层占10%的大型测试(UI 测试) UI Tests,中间层20%的集成测试(集测)看情况去做。

????????另外,集成测试更多是指验证一整个执行流程,而单测则验证某个行为或逻辑,可以这么理解:集成测试验证流程时,会走到单测过的某个行为或逻辑,即:单测可能是集成测试的一部分(不完全正确,但可以这么理解)。

????????比如,Android 中需要在 Activity 显示一张网络图片,单测需验证网络请求图片是否成功,集成测试需要验证从打开 Activity 到显示图片这一过程是否都按预期在执行。相应的,UI 测试验证 Activity 显示的图片是否如预期一样。

需要写 UI 测试吗?

????????Android 应用从广义来说,属于大前端,也就意味着 UI 变动会很频繁。且对于 UI 问题,应用开发者是很容易就能发现的,一般也比较容易解决。那么,针对 UI 进行测试其实是需要的,但没那么必要,可以不用写 UI 测试。

????????不用写 UI 测试,除了上述提到的 2 个理由,另外还有这 2 个理由:Espresso 单测比较简单;测开会做 UI 测试。将重心放在单测上才是王道。

单测与重构

????????没写过单测的童鞋可能不知道,其实重构也是单测的一部分,切记不要在本就不优雅的代码上写单测,请先重构。单测为代码质量保驾护航,重构提升代码质量和自我编程能力。

TDD

????????项目如果走的敏捷开发,会涉及到测试驱动开发这一设计方法论,但说实话,在写代码前先写好测试代码,这对开发者能力要求很高,且目前国内开发环境其实对这块的理解并不如理想之见,暂无需考虑。

单测写在哪?

????????首先,我们暂不写 UI 测试,只做单测,那么只需要在?src/test/java/包名/ 下写即可,一般来说可以不用在 src/androidTest/java/包名/ 下写单测代码。

????????其次,针对于 Android 应用,你可以在每个 Module 下都为其编写单测代码,然后统计各个 Module 的覆盖率,最后求一个平均覆盖率,即为整个应用的覆盖率。其实这是比较麻烦的,每次统计时都需要去计算每个 Module 的覆盖率然后求平均,当然如果用 jacoco 可以优雅自动统计的话就另当别论。

????????所以,为了方便统计单测覆盖率,本文暂推荐在指定 Module 的?src/test/java/包名/ 下写所有 Module 的单测代码,这个指定 Module 可以是 app 模块,也可以是新建的专为写单测的 unit_test 模块。

2.2 单测针对于哪些代码进行

  • 复杂依赖低:适合写单测
  • 复杂依赖高:重构减少依赖,变成复杂依赖低,然后写单测
  • 简单依赖低:看情况写
  • 简单依赖高:不用重构,不用单测

针对代码复杂性和依赖性,有如上图和原则描述可参考。

2.3 单测框架

Junit4?+ Mockito(powerMock)+ robolectric

简单单测 + 模拟难以实例化的类 + 实现 Android 框架(更多内容请参考相应框架学习)

3 如何开始单测?

????????这部分可能是刚开始学写单测的童鞋比较感兴趣的,不过本文此处可能会让你失望了,由于本人能力有限,这里主要分享单测大体的步骤与建议等,实际的例子还需参考优秀单测模板代码去编写。

3.1 项目中引入单测框架

????????单测依赖介绍如下:

// JUnit4:本地单元测试
'junit:junit:4.13.2',
'androidx.test:core:1.4.0',
// Robolectric:本地单元测试依赖 Android 框架
'org.robolectric:robolectric:4.4',
// Mockito:本地单元测试模拟框架
"org.mockito:mockito-core:3.12.4",
// mock final类时出现错误:Mockito cannot mock/spy because : - final class,增加如下模拟框架
'org.mockito:mockito-inline:3.12.4',
// PowerMock:Mockito的一种扩展(以实现完成对private/static/final方法的Mock)
'org.powermock:powermock-module-junit4:2.0.9',
'org.powermock:powermock-api-mockito2:2.0.9'

????????比如在?app Module 的 build.gradle 的 dependencies 下,依赖 JUnit4 如下:testImplementation 'junit:junit:4.13.2'

????????UI 测试暂不做,但为了区分依赖,也罗列如下:

// AndroidJUnitRunner and JUnit Rules:插桩单元测试
'androidx.test:runner:1.4.0',
'androidx.test:rules:1.4.0',
// runner 和 rules 的扩展包:@RunWith(AndroidJUnit4.class) 在此扩展包的 runners 下
'androidx.test.ext:junit:1.1.3',
// Espresso:Android 界面测试
'androidx.test.espresso:espresso-core:3.4.0'

????????UI 测试添加:testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

defaultConfig {
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

????????UI 测试覆盖率统计开关打开:testCoverageEnabled true

android {
    buildTypes {
        debug {
            testCoverageEnabled true
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

????????单测可以访问编译版本的资源:includeAndroidResources = true

android {
    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }
}

3.2 参考单测模板代码和优秀文章

????????Mockito使用:

Mockito的使用 - 简书

????????Robolectric使用:

Robolectric使用教程 - HansChen - 博客园

Android单元测试之Robolectric - 简书

Android单元测试框架Robolectric的学习使用_川峰的博客-CSDN博客_robolectric

????????单测介绍:

https://segmentfault.com/a/1190000006811141

Android单元测试只看这一篇就够了 - 简书

3.3 编写单测代码

????????其实编写单测和写代码是一样的,只是使用不同工具完成功能或测试。具体操作的话,在 app/src/test/java/包名/ 下创建和代码一样的包结构,然后新建测试文件,编写单测代码即可。有如下注意事项:

  • 测试文件命名:一般是文件名加上Test后缀,比如针对 TimeUtils.kt 这个文件测试,那么测试文件可命名为TimeUtilsTest。
  • 测试文件存放路径:细心的童鞋应该看到了,上一条的 TimeUtils.kt 这个文件是 kotlin 语言编写的,那么测试文件应该放在与 kotlin 相关的目录下,简单来说就是在包名和包结构中添加一层 kotlin 文件夹,即:app/src/test/java/包名/kotlin/ 。为保持统一测试代码也建议用 kotlin 书写。
  • 测试方法命名:期望输出_测试场景,如 fiveMethodsShouldBeInvoked_WhenInitData

3.4 单元测试代码分析

????????为了给应用开发者一个直观的印象,这里还是决定贴出一份单测代码,如有不足之处,还请海涵:

@Config(shadows = [ShadowLog::class, MockCA::class, MockDoExerciseApi::class, MockPortalApi::class], sdk = [23], application = BaseTestApplication::class)
class ProfilePresenterTest : BaseTestRobolectricClass() {
 
    @Spy
    lateinit var v: ProfileContract.V
 
    lateinit var p: ProfilePresenter
 
    @Before
    fun setUp() {
        MockitoAnnotations.openMocks(this)
        p = spy(ProfilePresenter::class.java)
        p.attachToView(v)
    }
 
    /**
     * 命名规则:期望输出_测试场景
     */
    @Test
    fun fiveMethodsShouldBeInvoked_WhenInitData() {
        p.initData()
        verify(v).updateWeight("")
        verify(v, never()).finishActivity()
        verify(v, atLeastOnce()).updateHeight("")
        verify(v, atLeast(1)).updateExerciseGoal("")
        verify(v, times(1)).updateExerciseFrequency("")
        verify(v, atMost(1)).updateExerciseTime("")
        // 检查是否所有的用例都涵盖了,如果没有将测试失败。放在所有的测试后面
        verifyNoMoreInteractions(v)
    }
 
    @Ignore("Mock CA 框架有问题")
    fun resetProfile() {
        // 问题1:java.lang.NullPointerException
        //  at com.tcl.component.arch.CA.of(CA.java:271)
        //  at com.tcl.healthhub.doexercise.profile.presenter.ProfilePresenter.resetProfile
        // 解决:使用shadows去Mock CA
        // 问题2:java.lang.RuntimeException: failed to bind null.of       java.lang.BootstrapMethodError: call site initialization exception
        // 未解决
        Log.d(TAG, "resetProfile: 1")
        p.resetProfile()
        Log.d(TAG, "resetProfile: 2")
    }
 
    @Ignore("Mock CA 框架有问题")
    fun deleteProfile() {
        Log.d(TAG, "deleteProfile: 1")
        p.deleteProfile()
        Log.d(TAG, "deleteProfile: 2")
    }
 
    @Test
    fun finishActivityMethodShouldBeInvoked_WhenResetUserInfo() {
        PrivateAccessor.invoke<ProfilePresenter>(p, "resetUserInfo")
        verify(v).finishActivity()
    }
 
    companion object {
        private val TAG = ProfilePresenterTest::class.java.simpleName
    }
 
}
  1. 第一行的 @Config 部分可参考 Robolectric 框架
  2. 第二行继承了?BaseTestRobolectricClass 文件,它是作为单测代码的基类,稍后贴出源码
  3. @Spy 与 Mockito.spy() 方法相同,只是一个使用注解方便些
  4. fiveMethodsShouldBeInvoked_WhenInitData 为测试方法,verify 验证 initData 方法执行后,有5个方法会执行一次,never() 与?times() 等都是限定验证时方法的调用次数的
  5. 最后一个方法用到了?PrivateAccessor 类,它可以通过反射的方式支持验证私有方法和属性。

BaseTestRobolectricClass 源码参考:

@RunWith(RobolectricTestRunner::class)
@Config(shadows = [ShadowLog::class], sdk = [23], application = BaseTestApplication::class)
abstract class BaseTestRobolectricClass {
 
    protected val mContext: Context = ApplicationProvider.getApplicationContext()
 
    companion object {
        @JvmStatic
        protected val TAG: String = this::class.java.simpleName
 
        @BeforeClass
        @JvmStatic
        fun setup() {
            ShadowLog.stream = System.out
        }
    }
 
}

4 如何统计覆盖率?

4.1 统计覆盖率

????????在 src/test/java 上右键选择如图 Run...,会跑整体单测代码。跑完后还会在写过单测代码的文件后显示单测覆盖率。也可导出覆盖率为 HTML 文件,但不比 AS 准确。

?

?

4.2 覆盖率统计 AS 中以及导出 HTML 文件的差异

????????现象:AS中总代码行高于生成的HTML文件,所以显示的代码行覆盖率低于生成的HTML文件

????????原因:见截图。可知,HTML文件代码行中,并未包含activity、fragment和view相关的代码行(不知道是因为没写UI测试导致的,或是AS导HTML时导致的)

????????解决:目前开发时,以AS为准。

?

?

5 总结

????????一般来说,统计出覆盖率后,行覆盖率达到25%或更高指标时,就算差不多了。但写单测的路也不应就此停下,在维护代码过程中会涉及对单测的修改;在后面新增功能代码时也许新增单测代码。

????????好了,本文到此也差不多该收尾了,希望能给单测小白一些收获或感悟,文后还附上了官方文档供参考。

6 参考

????????【1】Google官方测试文档:https://developer.android.com/training/testing/fundamentals

????????【2】Mockito官方文档:Mockito (Mockito 3.5.10 API)

????????【3】Robolectric官方文档:Robolectric

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-15 11:54:32  更:2021-10-15 11:55:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:35:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码