【Android测试】【第十六节】Instrumentation——初识+实战

时间:2023-03-09 00:27:57
【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:

  【Android测试】【第十六节】Instrumentation——初识+实战
ActivityInstrumentationTestCase2:

  【Android测试】【第十六节】Instrumentation——初识+实战  

  从类结构不难看出,下面的类是继承自上面的类,简单说明下是怎么回事,因为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。

【Android测试】【第十六节】Instrumentation——初识+实战

  这个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”

【Android测试】【第十六节】Instrumentation——初识+实战

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

【Android测试】【第十六节】Instrumentation——初识+实战

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

【Android测试】【第十六节】Instrumentation——初识+实战

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

【Android测试】【第十六节】Instrumentation——初识+实战

  建立完成之后有两点需要注意一下,这两点也是我们如果需要手动建立工程的时候,需要做的事情:

  1、测试的包名是被测的包名+test:

    【Android测试】【第十六节】Instrumentation——初识+实战

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

    【Android测试】【第十六节】Instrumentation——初识+实战

测试代码


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

【Android测试】【第十六节】Instrumentation——初识+实战

  新建的类需要继承 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当中扮演的角色。

  【Android测试】【第十六节】Instrumentation——初识+实战

  二、获取控件

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

  【Android测试】【第十六节】Instrumentation——初识+实战

  三、控件操作

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

   【Android测试】【第十六节】Instrumentation——初识+实战

  四、获取新出现的Activity

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

    【Android测试】【第十六节】Instrumentation——初识+实战

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

    【Android测试】【第十六节】Instrumentation——初识+实战

  五、断言

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

    【Android测试】【第十六节】Instrumentation——初识+实战

    断言的结果会直接影响到这条case的成功与否,下面的"运行结果" 会详细解释的。

运行结果


  我们写完代码之后,我们有两种方式可以触发它执行。

  IDE中触发:

  一种是直接在IDE中进行。这里以eclipse为例,就是需要右击测试工程,然后选择 "Run as" -> "Android JUnit Test"

    【Android测试】【第十六节】Instrumentation——初识+实战

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

    【Android测试】【第十六节】Instrumentation——初识+实战

  Runs 代表一共有多少个测试用例,现在执行了多少。

  Errors 代表目前执行过的case有多少是错误的,这个错误并不是说case没有通过,一般指的是代码当中出现了异常,也就是说你代码写的有问题

  Failures 代表就是断言失败的次数。这里假如我把期望值写成和真实值不一样的,运行后就可以看到Failures的个数是1,并且下发条也变成红色的了:

【Android测试】【第十六节】Instrumentation——初识+实战

  命令行触发: 

  am instrument -e com.bryan.calc.test.calctest -w com.bryan.calc.test/android.test.InstrumentationTestRunner
  运行的是整个testcase的用例。
  am instrument -e com.bryan.calc.test.calctest#testActivity -w com.bryan.calc.test/android.test.InstrumentationTestRunner
  运行的是testActivity这一个case。
【Android测试】【第十六节】Instrumentation——初识+实战
  上图是在模拟器上试了一下,可以看到命令运行后可以触发执行,但是在cmd的缓冲区里看不到运行结果,所以命令行的方式一般会用在当我们不关心JUint系统本身给出的结果而且想进行脱机自动化的时候来使用。