单元测试概述

概念

单元测试是为了验证代码中某个类或者方法能否按照正常输入得到预期的输出。一个情况下我们会测试共有方法即public方法,如果需要测试私有方法,需要通过mokc的方式,才能测试,在实际开发中我们已经会编写一些工具类,如果没有单元测试的助力,我们在实际测试过程中,可能对边界值的测试不足,导致线上出现问题。

通过单元测试,可以准确的保证应用的稳定性,和功能的正确性。随着应用模块的不断跟新迭代,测试单元也要跟着不断变化,保证应用模块的单元测试覆盖率以及迭代的稳定性。单元测试测试的主要范围是类的共有方法,保障公有方法有预期的行为。

优点:

  1. 快速验证方法的正确性,主要是边界值的测试,能否根据输入值,获取正确的输出。
  2. 推动代码架构的更新迭代。在单元测试过程中,如果类的职责不够单一,不利于单元测试,这样可以反推代码架构的更新迭代。
  3. 单元测试,保障了应用的稳定和更新迭代的效率。在Android开发过程中,虽然Gradle做了大量的编译优化工作,但是随着代码量的增加,每次编译程序的时间都会增加。但是基于JVM本地的单元测试可以省略编辑环节,直接测试修改的具体方法。
  4. 加深对于业务代码的理解,在单元测试的时候,需要明确业务方法的功能和职责单一性,因此编写单元测试用例时,可以不断的review、重构某个业务代码的实现。

单元测试划分

根据测试目录分类:

Android Studio 中的典型项目包含两个用于放置测试的目录。请按以下方式组织整理您的测试:

  1. androidTest 目录应包含在真实或虚拟设备上运行的测试。此类测试包括集成测试、端到端测试,以及仅靠 JVM 无法完成应用功能验证的其他测试。
  2. test 目录应包含在JVM本地计算机上运行的测试,如单元测试。

test目录下运行在JVM本地环境,运行速度较快,但是没发全面测试依赖于Android环境的方法,但是使用Robolectric可以模拟部分Android环境。

androidTest运行在虚拟设备、真实设备上,可以模拟Android运行环境,但是运行速度较慢,可提供最高的保真度。

按照运行环境划分

  1. JVM本地运行环境
  2. 模拟运行环境(Robolectric)
  3. 真机运行环境、虚拟器运行环境

测试与迭代

  1. 在软件迭代开发新功能时,设计新功能需要编写相应的单元测试程序,单元测试需要包含新功能的所有可能的case,保证最小颗粒度(方法)符合预期的效果。
  2. 在代码重构的时候,需要进行集成、UI测试,根据集成、UI测试的结果,持续迭代代码。

与由测试驱动的迭代开发关联的两个周期。图片来自官网:

在这里插入图片描述

测试金字塔

测试金字塔说明了应用应如何包含三类测试(即小型、中型和大型测试):

小型测试是指单元测试,用于验证应用的行为,一次验证一个类或者一个方法。

中型测试是指集成测试,用于验证模块内部的逻辑单元,没有涉及到多模块之间的数据交互。

大型测试是指端到端测试,用于验证跨越了应用的多个模块的用户操作流程。

沿着金字塔逐级向上,从小型测试到大型测试,各类测试的保真度逐级提高,但维护和调试工作所需的执行时间和工作量也逐级增加。因此,您编写的单元测试应多于集成测试,集成测试应多于端到端测试。虽然各类测试的比例可能会因应用的用例不同而异,但我们通常建议各类测试所占比例如下:小型测试占 70%,中型测试占 20%,大型测试占 10%。

在这里插入图片描述

单元测试Demo

google官方资料:

https://developer.android.com/training/testing/fundamentals?hl=zh-cn

Android的测试是基于junit。 从测试运行环境分成:基于JVM进行本地的单元测试(Local unit tests), 基于Android设备进行设备化的测试(instrumented tests)。

下面主要介绍基于JVM的本地单元测试:

(1)创建单元测试package

一般在 module-name/src/包目录下会有test/java/包名,我们可以在test/java下编写单元测试代码。(一般情况下新建android项目时,该目录已经存在,不需要单独新建)


在这里插入图片描述

Junit常见API

在JUnit框架测试中,你可以安装(初始化操作), 卸载(释放资源), 和断言操作。测试方法需要由@Test注解,目的是为了测试我们目标代码能否按照我们预期输出正确结果。

class ExampleUnitTest {
    @Before
    fun setUp(){

    }
    @Test
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }

}
  1. @Before: 使用这个注解可以指定一段包含测试设置操作的代码。测试类在每个测试执行前调用这段代码。你可以定义多个带有@Before注解的方法, 但是在测试类中这个方法执行的顺序是不确的。
  2. @After: 这个注解指定一段代码包含测试卸载操作。在每个测试方法执行后调用这段代码。你可以定义多个@After操作的代码。使用这个注解来释放内存中的资源。
  3. @Test: 使用这个注解标识一个测试方法。 一个单独的测试类可以有多个测试方法
  4. @Rule: @Rule通过复用的方法,可以允许你可以灵活的添加和修改每个测试方法。 在Android测试中, 此注解需要和Android测试支持库中提供的测试规则
  5. @BeforeClass: 使用此注解来指定一个测试类的静态方法只能调用一次。这种测试步骤对于耗时操作非常有用, 例如连接数据库操作。
  6. @AfterClass: 使用这个注解来指定一个静态方法, 当类中所有的测试方法都已经运行完成的时候调用。 这个步骤对释放@BeforeClass块中占用的资源非常有用。
  7. @Test(timeout=): 一些注解支持在注解中设置变量值。 例如, 你可以指定一个测试的超时时间,如果一个测试开始并且没有在指定的超时时间内完成, 它自动认为校验失败。超时时间的单位是毫秒, 如 @Test(timeout=5000)

下面举例说明:

获取三个数中最大的那个数:

public class MathUtil {
   public static int max(int a, int b, int c){
       if(a > b){
           if(a > c){
               return a;
           }else{
               return c;
           }
        }else{
           if(b > c){
               return b;
           }else{
               return c;
           }
        }
    }
}

编写的单元测试代码如下:

public class MathUtilTest {
    @Test
    public void testMax() {
          c(3, MathUtil.max(1, 2, 3));
    }
}

MathUtilTest就是一个测试类,testMax方法就是一个测试方法,assertEquals是junit4中提供的一个断言方法,如果MathUtil.max的输出和3是一致的,那么该单元测试pass。

junit的断言可以使用truth库来替换。

testImplementation 'com.google.truth:truth:1.0.1'

运行单元测试代码

(1)运行单个测试方法:

找到我们需要运行的测试类,例如:MathUtilTest,点击被@Test注解的方法即可。


在这里插入图片描述

(2)运行某个测试类:


在这里插入图片描述

(3)运行整个测试模块:
在这里插入图片描述

以上方式的运行结果,可以直接在AS的控制面板查看:


在这里插入图片描述

(4)gradle命名:
./gradlew testDebugUnitTest
在这里插入图片描述

(5)复制运行结果到浏览器,就可以查看最终结果了:

在这里插入图片描述

参考文档:https://developer.android.com/training/testing/fundamentals?hl=zh-cn

你可能感兴趣的:(单元测试概述)