写给新手的 Android 单元测试指南

社区移动开发Android

picture.image

Android的单元测试: 新手指南

单元测试是软件开发的关键阶段. 它带来了一种称为测试驱动开发(TDD)的开发范式.

即使只有一个测试, 也胜过没有测试.

如果不编写测试, 就等于在编写历史遗留代码.

为什么要编写单元测试?

  1. 我们会犯错.
  2. 我们希望代码能正常工作.
  3. 我们希望开发速度更快, 信心更足, 回归更少.

说到 Android 以及各种移动平台, 应用程序测试可能是一项挑战. 实施单元测试并遵循测试驱动开发原则或类似原则, 至少常常会让人感觉不直观. 不过, 测试非常重要, 不应被视为理所当然或被忽视.

Mockito基础

让我们跳转到单元测试的一些基本原理.

包结构

创建一个新的 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.

picture.image

点击Test后, 你就可以创建测试类了.

picture.image

让我们来看看测试类中会用到的一些注解.

  • @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!

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论