【Android测试】【第十六节】Instrumentation——初识+实战
◆版权声明:本文出自胖喵~的博客,转载必须注明出处。
转载请注明出处:http://www.cnblogs.com/by-dream/p/5503645.html
前言
有朋友给我留言说,能否介绍一下Robotium这款框架。相信很多的朋友都听过这个框架的名字吧,没错它也是国外的一款Android自动化框架,功能比较强大,但是我个人比较钟爱谷歌原生的自动化框架,一方面是因为原生的自动化框架比较稳定,并且一直认为"谷歌出品,必是精品",另一方面很多的自动化框架都是对谷歌测试框架的再封装,比如Robotium就是对Instrumentation的封装。因此当对谷歌原生的框架有了了解后,其他框架你也就无师自通了。
这里我想说明一下,本节以及后面介绍的Instrumentation相关的内容,对学习者的要求比较高,要求学习者必须对Android开发的知识有所了解,否则理解起来会相当的困难。所以建议在学习本节之前,可以学习一下我写的Android开发相关的前几篇文章。
简介
在Android2.3或者更早的版本中就已经有Instrumentation这个框架了,因此在那个时间段做过Android自动化测试的同学一定对这款框架特别的熟悉。看过上一节Test Concept的我们对基本概念和框架应该已经有了简单的认识。接下来我会直接先用一个小例子来教大家如果使用这个框架。
首先认识两个类,这两个类就是我们后面会用到的两个类:
InstrumentationTestcase:

ActivityInstrumentationTestCase2:
从类结构不难看出,下面的类是继承自上面的类,简单说明下是怎么回事,因为Android当中有四大组件:Activity、service、Content Provider和Broadcast Receiver ,而四大组件的特性都很分明,而Activity做为我们可见的与我们接触最多的一个组件,我们在自动化的时候难免和它打交道比较多,因此将它单拉出来说明一下,我在最开始的例子中会首先使用它们的父类InstrumentationTestcase来进行讲解。
源码地址:http://124.16.141.157/lxr-0101/source/frameworks/base/core/java/android/test/InstrumentationTestCase.java?v=android-5.1
源码地址:http://124.16.141.157/lxr-0101/source/frameworks/base/test-runner/src/android/test/?v=android-5.1
源程序
这里的源程序指的就是被测程序,也就是我们要测试App,这里我自己写了一个简单App,用来作为我们的测试demo。

这个App一共两页面。第一个页面中我们可以输入两个整数,然后按下查看结果按钮,就会跳到第二个页面,第二个页面就会显示出结果。
第一个页面的代码和布局:
package com.bryan.calc; import com.bryan.calc.R; import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText; public class MainActivity extends Activity
{
// 定义变量
EditText num1 = null;
EditText num2 = null;
Button btn = null; @Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 初始化控件
num1 = (EditText)findViewById(R.id.num1);
num2 = (EditText)findViewById(R.id.num2);
btn = (Button)findViewById(R.id.btn); btn.setOnClickListener( new getRes());
} // 按钮的事件响应
class getRes implements OnClickListener
{
@Override
public void onClick(View arg0)
{
int n1 = Integer.parseInt(num1.getText().toString());
int n2 = Integer.parseInt(num2.getText().toString());
GetResult(n1, n2);
}
} // 得到结果,并且调起计算结果页面
public void GetResult(int num1, int num2 )
{
Intent intent = new Intent();
intent.putExtra("res", num1+num2);
intent.setClass(MainActivity.this, ResultActivity.class);
startActivity(intent);
}
}
MainActivity.java
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"> <EditText
android:id="@+id/num1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" + " /> <EditText
android:id="@+id/num2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number" >
</EditText> <Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查看结果" /> </LinearLayout>
activity_main.xml
第二个页面的代码和布局:
package com.bryan.calc; import com.bryan.calc.R; import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView; public class ResultActivity extends Activity
{
TextView res = null; @Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_result); // 获得上一个页面传递来的数据
Intent intent = getIntent();
int resnum = intent.getIntExtra("res", 0); res = (TextView)findViewById(R.id.res);
res.setText(resnum+"");
}
}
ResultActivity.java
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="结果是:" /> <TextView
android:id="@+id/res"
android:layout_width="wrap_content"
android:layout_height="wrap_content" /> </LinearLayout>
activity_reault.xml
首先我们要确立要验证什么?这个App中我们可以模拟输入两个假数据,然后去让它计算结果然后在结果页面验证计算的结果对不对。
一般拿到被测程序的源代码之后,需要关注的也就是和我们要验证的内容相关的控件的信息和代码的实现。
测试工程
被测工程可以直接在源代码中添加,也可以独立建立一个工程。一般情况下我们我们不会直接在开发的代码中添加我们的测试代码,因此这里我们介绍独立工程的方法。我们来看下步骤:
因为我的源代码是使用Eclipse的ADT开发的Android程序,因此这里我们也使用Eclipse来建立测试工程。选择“New”-“Other”-“Android Test Project”

接着输入测试工程的名称:

选择测试工程,这里需要将刚才我们的源程序给选进来。

点击finish之后,我们的工程就建立好了。

建立完成之后有两点需要注意一下,这两点也是我们如果需要手动建立工程的时候,需要做的事情:
1、测试的包名是被测的包名+test:

2、Manifest文件当中引入的内容:

测试代码
建立完工程之后,我们新建一个测试类。

新建的类需要继承 InstrumentationTestCase,然后重写 setUp() 方法和 tearDown() 方法。一般测试代码需要的初始化数据都写在setUp中,而测试结束后要释放处理的一些逻辑写到tearDown中,另外测试的脚本的函数以test来命名开头即可。所以测试代码的格式为:
public class calctest extends InstrumentationTestCase
{
@Override
protected void setUp() throws Exception
{
super.setUp();
// 初始化代码
} @Override
protected void tearDown() throws Exception
{
super.tearDown();
// 释放代码
}// 测试代码
public void test****()
{
}
}
这里我直接贴出我的测试代码吧,然后着重说说需要注意的地方
package com.bryan.calc.test; import com.bryan.calc.MainActivity;
import com.bryan.calc.ResultActivity; import android.app.Instrumentation.ActivityMonitor;
import android.content.Intent;
import android.os.SystemClock;
import android.test.InstrumentationTestCase;
import android.view.KeyEvent;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView; public class calctest extends InstrumentationTestCase
{
MainActivity mainActivity;
EditText num1 = null;
EditText num2 = null;
Button button = null; @Override
protected void tearDown() throws Exception
{
SystemClock.sleep(4000);
mainActivity.finish();
super.tearDown();
} @Override
protected void setUp() throws Exception
{
super.setUp();
Intent intent = new Intent();
intent.setClassName("com.bryan.calc", MainActivity.class.getName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mainActivity = (MainActivity) getInstrumentation().startActivitySync(intent); num1 = (EditText) mainActivity.findViewById(com.bryan.calc.R.id.num1);
num2 = (EditText) mainActivity.findViewById(com.bryan.calc.R.id.num2);
button = (Button) mainActivity.findViewById(com.bryan.calc.R.id.btn); } public void testActivity()
{
mainActivity.runOnUiThread(new Runnable()
{
@Override
public void run()
{
num1.setText("22");
num2.setText("33");
}
});
ActivityMonitor am = getInstrumentation().addMonitor("com.bryan.calc.ResultActivity", null, false);
getInstrumentation().runOnMainSync(new Runnable()
{
@Override
public void run()
{
button.performClick();
}
});
ResultActivity resultActivity = (ResultActivity) getInstrumentation().waitForMonitorWithTimeout(am, 2000);
getInstrumentation().removeMonitor(am);
assertNotNull("Get the instance of ResultActivity is failed.", resultActivity); TextView textView = (TextView) resultActivity.findViewById(com.bryan.calc.R.id.res);
assertEquals("8", textView.getText().toString());
}
}
一、启动App
无论我们做什么测试之前,第一步都是启动App,之前monkeyrunner和uiautomator的启动方法都是根据包名+Activity名使用外部的start来唤起这个App,而我们使用Instrumentation的就可以直接通过Intent来启动,如果有对Intent不熟悉的同学,建议自己去看看Intent在Android当中扮演的角色。

二、获取控件
当我们启动了App之后,接下来做的就是对控件进行操作,这里获取控件的方法和实际Android开发当中的获取方法是一致的,因为第一步我们已经拿到了当前Activity的实例。这里需要注意的就是,资源文件R我们需要引入的是原工程当中的R文件,因此我们在代码中,可以像下面这样的写法来完成。

三、控件操作
上面的例子当中,我首先是给EditText赋值,然后去点击Button,这两个刚好具有代表性,因为Android是非线程安全的,因此如果不在主线程(也就是UI线程)当中对控件进行操作,那么就会发生异常,因此我们的测试程序在操作、使用这些控件的时候需要注意必须在UI线程中完成。非UI线程如果需要在UI线程操作的时候,一般有两种写法,这里我都列了出来:

四、获取新出现的Activity
从上面可以看出当我们拿到了当前页面的Activity的对象后,就可以通过findviewbyid来拿到控件的对象,一旦拿到控件的实例,就可以进行自动化了,但是呢?第一个Activity是通过startActivitySync()的返回值拿到的,那后面新出现的Activity并不是我们自己startActivity起来的,而是源程序当中代码调用起来的,那么我们怎么拿到它的实例呢?这里就需要介绍一下下面的Monitor了。

拿到了新页面的Activity的对象后,就可以用同样方法再获取新页面上的空间了:

五、断言
前面做了那么多的操作步骤,那么我们究竟要怎么验证呢?当然是通过断言

断言的结果会直接影响到这条case的成功与否,下面的"运行结果" 会详细解释的。
运行结果
我们写完代码之后,我们有两种方式可以触发它执行。
IDE中触发:
一种是直接在IDE中进行。这里以eclipse为例,就是需要右击测试工程,然后选择 "Run as" -> "Android JUnit Test"

这时候左边区域出现一个JUint的区域,显示这个结果,如下图:

Runs 代表一共有多少个测试用例,现在执行了多少。
Errors 代表目前执行过的case有多少是错误的,这个错误并不是说case没有通过,一般指的是代码当中出现了异常,也就是说你代码写的有问题
Failures 代表就是断言失败的次数。这里假如我把期望值写成和真实值不一样的,运行后就可以看到Failures的个数是1,并且下发条也变成红色的了:

命令行触发:

【Android测试】【第十六节】Instrumentation——初识+实战的更多相关文章
- 第十六节、基于ORB的特征检测和特征匹配
之前我们已经介绍了SIFT算法,以及SURF算法,但是由于计算速度较慢的原因.人们提出了使用ORB来替代SIFT和SURF.与前两者相比,ORB有更快的速度.ORB在2011年才首次发布.在前面小节中 ...
- 第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询
第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询 bool查询说明 filter:[],字段的过滤,不参与打分must:[] ...
- centos shell脚本编程2 if 判断 case判断 shell脚本中的循环 for while shell中的函数 break continue test 命令 第三十六节课
centos shell脚本编程2 if 判断 case判断 shell脚本中的循环 for while shell中的函数 break continue test 命令 ...
- centos linux系统日常管理3 服务管理ntsysv,chkconfig,系统日志rsyslog,last ,lastb ,exec,xargs,dmesg,screen,nohup,curl,ping ,telnet,traceroute ,dig ,nc,nmap,host,nethogs 第十六节课
centos linux系统日常管理3 服务管理ntsysv,chkconfig,系统日志rsyslog,last ,lastb ,exec,xargs,dmesg,screen,nohup,cur ...
- ASP.NET MVC深入浅出系列(持续更新) ORM系列之Entity FrameWork详解(持续更新) 第十六节:语法总结(3)(C#6.0和C#7.0新语法) 第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字 各种通讯连接方式 设计模式篇 第十二节: 总结Quartz.Net几种部署模式(IIS、Exe、服务部署【借
ASP.NET MVC深入浅出系列(持续更新) 一. ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态模 ...
- 大白话5分钟带你走进人工智能-第二十六节决策树系列之Cart回归树及其参数(5)
第二十六节决策树系列之Cart回归树及其参数(5) 上一节我们讲了不同的决策树对应的计算纯度的计算方法, ...
- 风炫安全web安全学习第三十六节课-15种上传漏洞讲解(一)
风炫安全web安全学习第三十六节课 15种上传漏洞讲解(一) 文件上传漏洞 0x01 漏洞描述和原理 文件上传漏洞可以说是日常渗透测试用得最多的一个漏洞,因为用它获得服务器权限最快最直接.但是想真正把 ...
- 第一百二十六节,JavaScript,XPath操作xml节点
第一百二十六节,JavaScript,XPath操作xml节点 学习要点: 1.IE中的XPath 2.W3C中的XPath 3.XPath跨浏览器兼容 XPath是一种节点查找手段,对比之前使用标准 ...
- 第四百一十六节,Tensorflow简介与安装
第四百一十六节,Tensorflow简介与安装 TensorFlow是什么 Tensorflow是一个Google开发的第二代机器学习系统,克服了第一代系统DistBelief仅能开发神经网络算法.难 ...
- 第三百八十六节,Django+Xadmin打造上线标准的在线教育平台—HTML母版继承
第三百八十六节,Django+Xadmin打造上线标准的在线教育平台—HTML母版继承 母板-子板-母板继承 母板继承就是访问的页面继承一个母板,将访问页面的内容引入到母板里指定的地方,组合成一个新页 ...
随机推荐
- Codeforces 682B New Skateboard(DP)
题目大概说给一个数字组成的字符串问有几个子串其代表的数字(可以有前导0)能被4整除. dp[i][m]表示字符串0...i中mod 4为m的后缀的个数 通过在i-1添加str[i]字符转移,或者以st ...
- 怎样测试TCP&UDP端口
TCP端口大家都知道,比如80端口,可以使用 telnet ip 80,来验证端口是否正常监听,那UDP端口是否可以同样测试呢?详细如下: 下面我们来进行测试,123端口是服务器42.11.12.13 ...
- 多表头固定demo--html Table
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- BZOJ 2626 & KDtree
题意: 二维平面n个点 每次给出一个点询问距离第k小的点. SOL: kdtree裸题,抄了一发别人的模板...二维割起来还是非常显然的.膜rzz的论文. 不多说了吧.... Code: /*==== ...
- 将类似 12.56MB 36.89KB 转成 以K为单位的数字【备忘】
select case RIGHT(RESOURCE_SIZE,2) when 'MB' THEN SUBSTRING_INDEX(RESOURCE_SIZE,'MB',1)*1024 ELSE SU ...
- IOS关于UIViewController之间的切换
IOS关于UIViewController之间的切换 1.NavigationController切换UIViewController的两种方式 方法一右侧进入 1 SecondViewControl ...
- BZOJ 1798 题解
1798: [Ahoi2009]Seq 维护序列seq Time Limit: 30 Sec Memory Limit: 64 MBSubmit: 5531 Solved: 1946[Submit ...
- 【BZOJ】3676: [Apio2014]回文串
http://www.lydsy.com/JudgeOnline/problem.php?id=3676 题意:给一个串求回文串×出现次数的最大值.(|S|<=300000) #include ...
- CF 2B.The least round way
题目链接 很久以前就见过此题,以前看了题解,然后今天写了写,写的真搓. #include <cstdio> #include <cstring> #include <st ...
- BZOJ4503: 两个串
Description 兔子们在玩两个串的游戏.给定两个字符串S和T,兔子们想知道T在S中出现了几次, 分别在哪些位置出现.注意T中可能有“?”字符,这个字符可以匹配任何字符. Input 两行两个字 ...