Android Unit Test学习
@(单元测试)[Android|Markdown]
- 单元测试 这几天接触了下android的单元测试,写了一些小DEMO,自己总结下来,觉得android的单元测试可以有以下优缺点:
- 优点
- 减少bug率
- 明确方法的输入输出
- 自测程序,使自己对代码更有自信
- 缺点
- 耗时
- 有些时候测试环境于真实环境有差异,导致做了很多适配的工作。
- 不适合一个硬性指标。如代码覆盖率等。
[TOC]
Android Test 框架
流行的android测试框架很多,可以参照这个网页:Android 测试工具。我主要调研过以下几种:
- 原生框架 :ActivityInstrumentationTestCase2
- Android Test Lib (谷歌官方提供)Espresso等
- Appium (可以编写测试脚本)
- Monkey 和Monkey Runner(随机测试,谷歌官方提供)
- Robolectric (第三方测试框架,不需要真机或者模拟器)
- 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数据。
网友评论