Android Unit Test学习

Android Unit Test学习

@(单元测试)[Android|Markdown]

  • 单元测试 这几天接触了下android的单元测试,写了一些小DEMO,自己总结下来,觉得android的单元测试可以有以下优缺点:
  • 优点
  1. 减少bug率
  2. 明确方法的输入输出
  3. 自测程序,使自己对代码更有自信
  • 缺点
  1. 耗时
  2. 有些时候测试环境于真实环境有差异,导致做了很多适配的工作。
  3. 不适合一个硬性指标。如代码覆盖率等。

[TOC]

Android Test 框架

流行的android测试框架很多,可以参照这个网页:Android 测试工具。我主要调研过以下几种:

  1. 原生框架 :ActivityInstrumentationTestCase2
  2. Android Test Lib (谷歌官方提供)Espresso等
  3. Appium (可以编写测试脚本)
  4. Monkey 和Monkey Runner(随机测试,谷歌官方提供)
  5. Robolectric (第三方测试框架,不需要真机或者模拟器)
  6. Robotium (封装官方框架)

上述几种也可以组合使用,基本上想对于一个项目做完整的单元测试,即包括UI,业务,数据,协议等,一般都要自己再<font color=red>封装一些适合项目的框架和工具类</font>。

原生框架

先上图~


  • AndroidTestCase类:
    提供系统对象(如Context)的方法。使用Context,你可以浏览资源,文件,数据库等等。不能测试涉及UI的方法。
  • InstrumentationTestCase类:
    继承TestCase类,并可以使用Instrumentation框架,用于测试Activity。使用Instrumentation,Android可以向程序发送事件进行自动UI测试,并可以精确控制Activity的启动,监测Activity生命周期的状态
    ActivityUnitTestCase设计用于单元测试,它在一个孤立的系统环境中测试Activity。换句话说,当你使用这个测试类时,Activity不能与其它Activity交互。每个测试用例都需要使用startActivity启动测试activity。

单元测试在Studio中的应用

Android Studio对单元测试完全支持,已经帮开发者搭建好了测试环境。测试开发者只需要关心测试用例编写,不用关心测试环境搭建,给测试开发带来极大便利。在Android Studio上进行JUnit单元测试步骤:
1 编写测试用例。
编写测试用例需要继承TestCase的子类。如果测试对象为普通java类,自定义测试用例需要继承AndroidTestCase;如果测试对象为Activity,可以继承ActivityUnitTestCase,也可以继承ActivityInstrumentationTestCase2。
继承ActivityUnitTestCase和ActivityInstrumentationTestCase2不同之处在于,前者需要自己使用startActivity启动被测试的activity,并且该activity在一个独立的环境中运行。继承自ActivityInstrumentationTestCase2的测试用例不需要开发者自己调用被测试的activity,测试框架会自动启动activity。
自定义测试用例类需要有构造函数,并且将测试目标类传递给父类。如:
public LoadActivityTest() {

super(LoadActivity.class);//LoadActivity为测试目标类

}

一般来说自定义测试用例需要重写setUp和tearDown,如果测试用例中有多个测试方法,setUp和tearDown会在每个测试方法前后被调用。setUp用来初始化测试环境,tearDown在每个测试方法结束后还原测试环境。
使用断言assertXXX来判断测试结果。如果断言失败,则测试用例测试不通过,所有断言都成功,则测试用例测试通过。 常见的断言有:
assertNotNull:断言测试对象不为NULL;
assertNull:断言测试对象为NULL;
assertEquals:断言测试的两个对象值相等;
assertTrue:断言Boolean值为true;
assertFalse:断言Boolean值为false。
所有测试方法必须以test开头,如testXXX。测试框架会自动调用所有以test为开头的测试方法。各个测试方法没有运行先后顺序。因此,各个测试方法不应该有前后依赖顺序。

UI测试。

1)UI测试应该在主线程中操作。
activity.runOnUiThread(new Runnable() {

@Override

public void run() {

    mPaymentButton.callOnClick();

}

});

2)模拟系统发送按键事件
this.sendKeys(KeyEvent.KEYCODE_HOME);

3)点击按键事件
mPaymentButton.callOnClick();

3 线程同步问题
1)测试线程和异步任务线程同步
在单元测试中,有很多异步任务。我们需要异步任务返回结果再测试,这就需要用到线程同步相关知识。比如,我们测试的单元需要发送一个网络请求,需要测试返回结果是否正确。这是一个典型的异步任务,这就需要用本节的方法去做单元测试。
CountDownLatch,一个同步辅助类,用信号量机制实现线程同步。主要方法有:
public CountDownLatch(int count);
构造函数,count指定了计数器的次数。可以理解为加的锁数量。
public void await() throws InterruptedException
调用此方法会一直阻塞当前线程,直到计数器的值为0。
public void countDown();
每次调用计数减少1,当计数为0时,线程被唤醒。
2)测试线程和UI主线程同步
每个测试方法都运行在子线程中,在某些情况,测试子线程需要等待主线程(UI线程)空闲才继续运行测试子线程。这就需要同步测试子线程和UI主线程。Instrumentation为我们提供了waitForIdleSync方法来满足这类同步需求。使用方法如下:

public void testExample(){

......

getInstrumentation().waitForIdleSync();//等待主线程返回
 .....

}

需要注意:waitForIdleSync只能在测试子线程中被调用。测试方法默认运行在子线程中,但也可以运行在UI线程中,只需要在测试方法前加上标注@UiThreadTest,这样整个测试方法都在UI线程中运行。

测试Fragment

由于JUnit没有提供对fragment测试的框架,所以只能在使用activity测试框架。通常的做法是使用ActivityInstrumentationTestCase2测试框架,先加载一个activity,然后在setup中启动被测试的fragment。这样在每个测试方法执行前都会启动被测试的fragment。
使用ActivityInstrumentationTestCase2框架对fragment进行单元测试的不足之处在于,测试任何一个单元(函数),都需要先启动该fragment,然后才能进行单元测试。

五 单元测试基础要点
1 方法名称必须以test开头。
2不能依赖测试方法顺序,每个测试方法都是在子线程中运行。
3 setUp方法和tearDown方法都是TestCase类的方法
1)setUp方法是在执行每个测试方法之前执行的
2)tearDown方法是在执行每个测试方法之后执行的
4 涉及UI操作的应该在主线程中执行。
5 waitForIdleSync和sendKeys不允许在UI线程里运行,只能运行在测试子线程中。

原理

需要说明的是,在Android系统中,测试程序也是应用程序,我们可以将其看成一个没有UI的应用。
其实现过程大致如下:如图,InstrumentationTestRunner通过调用Instrumentation杀除应用程序的进程,再用Instrumentation重启该应用。这时,测试应用和被测应用就运行在同一进程下。测试应用怎么知道该测试哪个应用呢?嗯,这是通过在测试工程的mainfest文件中添加元素来实现的。当测试应用和被测应用运行在同一个进程里,它们之间就可以通过Instrumentation来进行消息交互,从而达到测试效果。当Instrumentation与某个程序交互时,其大致采用如下步骤:(资料来源:
http://blog.csdn.net/fireworkburn/article/details/20144153)。
首先,启动时,初始化测试APK的配置文件AndroidManifest.xml文件中。该配置文件中标明了所使用的测试运行类、被测目标应用、包名等。然后,启动被测应用的Activity。同时,将测试ActivityThread做为一个引用进行初始化。此时,如果找不到目标应用则会报错。其次,执行测试脚本。测试时,测试工程中任何对目标应用进行的操作,都会用异步的方式,将消息体放在目标程序的MessageQueue中。这样,目标程序在查看到自己的MessageQueue中有内容时就会执行。

Robolectric

根据我的研究以及我个人的需求,我觉得<font color=red>Robolectric</font>更适合开发使用,因为其速度快,而且不需要在真机上看效果,这样可以把代码逻辑都写好,验证完毕在真机上一次运行。大大提高代码质量和效率。个人见解,不喜勿喷。

Robolectric简介

robolectric官网介绍,Robolectric是为android上的TDD开发而产生的一款第三方测试框架,按照官网说的:

Robolectric is a unit test framework that de-fangs the Android SDK jar so you can test-drive the development of your Android app. Tests run inside the JVM on your workstation in seconds. With Robolectric you can write tests like this:

@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {

  @Test
  public void clickingButton_shouldChangeResultsViewText() throws Exception {
    MyActivity activity = Robolectric.setupActivity(MyActivity.class);

    Button button = (Button) activity.findViewById(R.id.button);
    TextView results = (TextView) activity.findViewById(R.id.results);

    button.performClick();
    assertThat(results.getText().toString()).isEqualTo("Robolectric Rocks!");
  }
}

该框架测试起来是很方便的,但是同时由于不启动app,可能遇到一些问题,总体上使用起来还是方便的。

eclipse配置

新建 Test Project

这里我新建了一个 Maven Project

File> New > Other > Maven Project ,跟 Appium 里面使用 Java 写脚本,建立 Maven 项目是相同的。

我建立的测试项目 roboletrictest,pom.xml 的内容我这里贴下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>roboletrictest</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>roboletrictest</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.robolectric</groupId>
      <artifactId>robolectric</artifactId>
      <version>3.0</version>
      <scope>test</scope>
    </dependency>

  </dependencies>
</project>

配置 Test Project

配置 roboletrictest 的 Build Path

右键项目(roboletrictest) > Build Path > Configure Build Path
在 Project 选项中 add 被测项目(roboletric)
在 Libraries 中 Add External JARs,添加对应的 android.jar,android.jar 在 sdk 中(需要下载)
完成 Test Case

在 src/test/java 目录下 new 一个 package,对应被测项目(com.example.roboletric.test)
在包里面 new 一个 JUnit Test Case,使用 Junit 4,Class Name 为 MainActivityTest
Run Test Case

右键 MainActivityTest > Run As > JUnit Test

自己打包依赖

因为是maven项目,其实你也可以自己把roboletric的项目打包,使用maven-assembly-plugin把项目打成jar包,直接导入使用。以前的官网提供了依赖包的,但是现在找不着了。

Android studio配置

 repositories {
        maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
        mavenLocal()
        mavenCentral()
    }
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'
    testCompile "junit:junit:4.10"
    testCompile "org.assertj:assertj-core:1.7.0"
    testCompile "org.robolectric:robolectric:${robolectricVersion}"
}

Shadow类的使用

Android Test Kit

Android Test Kit 是一组 Google 开源测试工具,用于 Android 平台,包含 Espresso API 可用于编写简洁可靠的 Android UI 测试。它也可以跟其他的框架混合使用。
github Demo的地址:
https://github.com/googlesamples/android-testing

Monkey 和MonkeyRunner

Appium

框架基本原理

下次再看,我会开一篇代理设计模式的专栏

常见的测试方法

测试UI

一般的测试框架都提供了对UI的测试,具体可以参考谷歌源码对一些原生app做的测试。

测试业务

建议把业务方法的入口都放在manager里,这样测试的时候直接一调用就OK了。但是记住要测试一些边界情况,例如空指针之类的。

测试http请求

因为http请求是异步的,这块可能要对请求的框架做一些封装,以便支持mock数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Android单元测试介绍 处于高速迭代开发中的Android项目往往需要除黑盒测试外更加可靠的质量保障,这正是单...
    东经315度阅读 3,089评论 6 37
  • 标签(空格分隔): Android 单元测试的好处:Martin Fowler在《重构》里面还解释了为什么单元测试...
    背影杀手不太冷阅读 5,800评论 3 25
  • 一.基本介绍 背景: 目前处于高速迭代开发中的Android项目往往需要除黑盒测试外更加可靠的质量保障,这正是单元...
    anmi7阅读 2,014评论 0 6
  • @Author:彭海波 前言 单元测试(又称为模块测试, Unit Testing)是针对程序模块(软件设计的最小...
    海波笔记阅读 4,945评论 0 52
  • 前言 在博客Android单元测试之PowerMockito,主要介绍PowerMockito的使用和对Java测...
    水木飞雪阅读 10,811评论 14 15