0%

重构(二):详解单元测试

1. 什么是单元测试

  • 开发人员编写的、用于检测在特定条件下目标代码正确性的代码

2. 单元测试的优点

  • 便于后期重构:用单元测试尽量覆盖程序中的每一项功能的正确性,这样就算是开发后期,也可以有保障地增加功能或更改程序结构,而不用担心这个过程会破坏原来的功能。单元测试为代码的重构提供了保障,只要重构代码之后单元测试全部运行通过,那么,在很大程度上表示这次重构没有引入新的 Bug,当然这是建立在完整、有效的单元测试覆盖率的基础上
  • 优化设计:编写单元测试将使用户从调用者的角度观察、思考,特别是 TDD 的开发方式,迫使设计者把程序设计成易于调用和可测试的,并且解除软件中的耦合
  • 文档记录:单元测试是一种无价的文档,是展示类或方法如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步
  • 具有回归性:自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地快速运行测试,而不是将代码部署到设备上,然后再手动地覆盖各种执行路径,这样的操作效率很低

3. 单元测试具体要测试哪些内容

  • 一致性(Conformance):值是否和预期一致。可以理解为当输入并不是预期的标准数据时,被测试方法是否可以正确地输出预期结果或抛出异常。例如,要实现一个加法功能,有两个 EditText 分别输入两个整型数字,但当用户输入的不是整型而是文字时如何处理
  • 有序性(Ordering):值是否像期望的那样是无序或有序的
  • 区间性(Range):值是否位于合理的最小值和最大值之间。例如,圆的角度为 1~360 度,当用户设置进来的角度为 400 度时如何处理
  • 依赖性(Reference):代码是否引用了一些不在代码本身控制范围之内的外部资源,当这些外部资源存在或不存在时代码是否可以产生相应的预期结果。例如需要将图片缓存到 SD 卡中,如果 SD 卡被移除或者没有 SD 卡我们做和处理
  • 存在性(Existence):值是否存在。测试方法是否可以处理值不存在的情况,例如对象为 null 的情况下得到的结果是什么
  • 基数性(Cardinatity):是否恰好有足够的值。这里的基数指的是计数,测试方法是否可以正确计数,并检查最后的计数值
  • 时间性(Time):所有事情的发生是否是有序的、是否在正确的时刻、是否恰好及时。与时间相关的问题有:相对时间(时间上的顺序)、绝对时间(消耗的时间和时钟上的时间)、并发问题。例如,方法调用的时间顺序、代码超时、不同的本地时间、多线程同步等
  • 覆盖执行路径:软件是由做种执行路径组成的,对于单元测试而言,覆盖各执行路径也是保证软件正确性运行的一个重要方面

4. JUnit 概述

  • JUnit 是一个基于 Java 语言的单元测试框架
  • 它由敏捷开发先驱 Kent Beck 和 GOF 之一的 Erich Gamma 创建,逐渐成为源于 Kent Beck 的 sUnit 的 xUnit 家族中最为成功的一个

5. JUnit 的执行流程

  • 开发人员需要新建一个继承自 TestCase 的类,然后在该测试类中添加测试函数
  • 测试函数必须以 test 开头,并且是 public
  • 在每个 TestCase 被执行之前都会调用 setup() 方法进行初始化
  • 在 TestCase 结束之前会调用 tearDown() 函数进行一些扫尾工作
  • 需要注意的是,每个测试方法、TestCase 之间并没有什么关联,它们的执行顺序也不一定是代码中的顺序。因此,测试方法之间不要存在依赖性

6. JUnit 中的断言和失败提示有哪些

  • 断言函数

    • assertEquals():判断两个值或者两个对象是否相等。该函数有两个参数,参数 1 为预期的值,参数 2 为计算得到的值。通过判断两个值是否相等得出结果,如果相等 那么测试通过,否则测试失败
    • assertTrue() 与 assertFalse():验证真与假,只有一个 boolean 类型的参数。当参数与 assert 后面的 true 或者 false 对应时则表示通过,否则失败
    • assertNull() 与 assertNotNull():只有一个 Object 类型的参数,用于对对象判空或者非空
    • assertSame() 与 assertNotSame():前者用于判断两个对象是否是同一个对象;后者则是判断两个对象不是同一个。与 assertEquals() 不同的是,assertSame() 强调的是两个对象为同一个对象,而 assertEquals() 只有两个对象相等即可,即调用 equals() 方法时返回 true
  • 失败提示函数

    • failNotEquals():该函数有 3 个参数,第一个参数为失败时的提示信息;参数 2 为期望值,参数 3 为实际值。当两个对象不相等时测试抛出参数 1 的错误信息,否则测试通过
    • failNotSame() 与 failSame()failNotSame()failNotEquals() 参数一致,不同的是 failNotSame() 两个对象不是同一个对象时抛出参数 1 的错误信息,否则测试通过
    • fail(String) 与 fail()fail(String) 直接抛出当前测试用例参数 1 中的错误信息,而 fail() 则会给出默认的错误信息

7. JUnit 中怎样运行多个测试类

  • 同时运行多个测试类可以使用 TestSuite,TestSuite 是用来执行多个测试类的集合。使用 JUnit 4 有两种实现方式:
    • 通过 JUnit4TestAdapter 包装测试类,并且将 JUnit4TestAdapter 对象添加到 TestSuite 中
    • 使用注解,包括 @RunWith(Suite.class)@Suite.SuiteClasses

8. Android 平台中的单元测试框架是

  • JUnit 是针对 Java 平台的测试框架,Google 在 JUnit 的基础上进行了扩展,使之能够在 Android 上运行测试用例
  • Android 平台下所有的测试类都是 InstrumentationTestCase 的子类,它的内部封装了 Instrumentation 对四大组件进行操作
  • InstrumentationTestCase 继承自 JUnit 的 TestCase

9. 怎样使用 Instrumentation 进行 Android 平台上的单元测试

  • 需要 Context 的测试用例:Android 提供了一个 AndroidTestCase 来运行与 Android 平台相关的测试类。它有一个 mContext 字段,可以通过这个 Context 来完成测试工作
  • 测试 Activity:Android 提供了一个 ActivityUnitTestCase 类用来对 Activity 进行单元测试。通过 ActivityUnitTestCase 用户可以启动某个 Activity、测试布局、Intent 参数等内容,使得我们对 Activity 能够做一些简单的单元测试
  • 测试 Service:Android 提供了 ServiceTestCase 类来测试 Service。它提供了一个 startService() 方法来启动 Service,在 startService() 方法中又会调用 Service 的 onStartCommand() 方法。此外,它还提供了启动 Service 的另外一种方式,也就是 bindService()
  • 测试 ContentProvider:Android 提供了 ProviderTestCase2,它提供了一个用于测试的 ContentResolver,可以通过 getMockContentResolver() 方法得到。获取 ContentResolver 之后,就可以根据指定的 Uri 对表进行操作
-------------------- 本文结束感谢您的阅读 --------------------