Android给我们提供了一个完整的测试框架,使得我们可以从不同的层次对应用进行全方位的测试,包括单元测试,框架测试、ui自动化测试等等。Android测试架构如图所示:

转载请注明出处: http://blog.csdn.net/qinjunni2014/article/details/45854305

构建JUnit测试

所有的Android测试都是基于JUnit,我们完全可以构建不调用Android api的测试,比如下面这样:

import junit.framework.TestCase;/** * Created by Junli on 5/19/15. */public class UnitTest extends TestCase {    public void test() throws Exception{        final int expected = 1;        final int reality = 5;        assertEquals(expected, reality);    }}

我们只要继承自TestCase就可以构建一个基于JUnit的单元测试,只要是以test开头的函数都会被加入测试队列。在测试时,我们使用Assert库中的函数,assertEquals进行相等测试。但是,这段测试只能在device上进行测试,没法本地测试,如果尝试进行本地测试,就会抛出Stub! Not implemented” exception。实际上,Android中所有的测试都只能在device上跑,除了某些第三方测试框架比如Robolectric。

Instrumentation 介绍

通常情况下,Android 应用中所有的组件的生命周期都是系统决定的,比如Activity的onCreate, onResume, onPause等方法的调用都是系统内部决定的。开发者无法通过系统api来直接调用,但是通过Instrumentation我们却可以做到这一点。而且在Instrumentation中,我们还可以向应用发送事件。

Activity单元测试

在测试Activity时,我们需要继承自ActivityUnitTestCase,我们首先写一个简单的Activity

public class MainActivity extends Activity {    EditText edit;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        edit = (EditText) findViewById(R.id.edit);    }    public void onClick(View v){        Intent intent = new Intent(this, SecondActivity.class);        intent.putExtra("text", edit.getText());        startActivity(intent);    }}

布局文件如下:

<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:onClick="onClick"        android:text="Start"        android:layout_centerInParent="true"        android:id="@+id/btn"/>    <EditText        android:layout_width="250dp"        android:layout_height="50dp"        android:id="@+id/edit"        android:layout_marginTop="15dp"        android:layout_centerHorizontal="true"        android:layout_below="@id/btn"/>    <TextView        android:id="@+id/text"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerHorizontal="true"        android:layout_below="@id/btn"        android:layout_marginTop="10dp"/></RelativeLayout> 

布局文件包括一个button,一个EditText,一个TextView。我们来进行一些简单地测试

public class MainActivityUnitTest extends ActivityUnitTestCase<MainActivity> {    private int buttonId;    private MainActivity activity;    public MainActivityUnitTest() {        super(MainActivity.class);    }    @Override    protected void setUp() throws Exception {        super.setUp();        Intent intent = new Intent(getInstrumentation().getTargetContext(), MainActivity.class);        startActivity(intent, null, null);        activity = getActivity();    }    public void testLayout() {        buttonId = R.id.btn;        assertNotNull(activity.findViewById(buttonId));        Button view = (Button) activity.findViewById(buttonId);        assertEquals("Incorrect label of the button", "Start", view.getText());    }}

我们在setup函数中做一些简单的测试。我们首先要启动我们的MainActivity,然后通过getActivity方法拿到Activity的引用。在testLayout函数中,我们会先确定通过Activity的findViewById方法能拿到view,通过assertNotNull方法测试。然后调用assertEquals函数确定button的text是否正确。

ActivityUnitTestCase允许我们去检查activity的布局,检查启动其他activity时的intent,这个intent并不会被发送给Android系统,不过我们可以调用getStartedActivityIntent拿到它,并对它进行测试。ActivityUnitTestCase 中所有的测试方法都实在单独的Context中执行的,因此我们必须在setup中主动调用startActivity。

public void testIntent(){    buttonId = R.id.btn;    assertNotNull(activity.findViewById(buttonId));    Button view = (Button) activity.findViewById(buttonId);    view.performClick();    Intent intent = getStartedActivityIntent();    assertNotNull(intent);//测试intent是否null    String data = intent.getExtras().getString("URL");    //测试URL是否正确    assertEquals("http://blog.csdn.net/qinjunni2014", data);}

Activity功能测试

受限于ActivityUnitTestCase的功能,我们并不能在其中测试特别复杂的功能,比如与其他应用组件交互,发送事件处理等等。不过如果我们要实现这些功能的测试,只需要继承ActivityInstrumentationTestCase2即可,让我们通过实例来看看,这个类可以让我们做哪些测试。

//我们依然是对上面的MainActivity进行测试public class MainActivityFunctionalTest extends ActivityInstrumentationTestCase2<MainActivity> {    private MainActivity activity;    private EditText editText;    private TextView textView;    public MainActivityFunctionalTest() {        super(MainActivity.class);    }    @Override    protected void setUp() throws Exception {        super.setUp();        setActivityInitialTouchMode(true);        activity = getActivity();        editText = (EditText) activity.findViewById(R.id.edit);        textView = (TextView) activity.findViewById(R.id.text);    }    public void testStartSecondActivity(){        Instrumentation.ActivityMonitor monitor =                getInstrumentation().addMonitor(SecondActivity.class.getName(),null,false);        Button view = (Button) activity.findViewById(R.id.btn);        TouchUtils.clickView(this, view);        SecondActivity startedActivity = (SecondActivity) monitor.waitForActivityWithTimeout(2000);        assertNotNull("ReceiverActivity is null", startedActivity);        assertEquals("Monitor for ReceiverActivity has not been called",                1, monitor.getHits());        TextView textView = (TextView) startedActivity.findViewById(R.id.resultText);        ViewAsserts.assertOnScreen(startedActivity.getWindow().getDecorView(), textView);        assertEquals("Text incorrect", "Started", textView.getText().toString());        this.sendKeys(KeyEvent.KEYCODE_BACK);        TouchUtils.clickView(this, view);    }}

注意到我们在setup函数中,并没有主动去调用startActivity。因为和UnitTestCase不同的是,这个类会利用真正的Android系统架构,在getActivity中会去生成这个activity。

testStartSecondActivity函数主要是去测试启动SecondActivity的过程,这里用到了ActivityMonitor,它会对某个类型的Intent进行监测,当有activity启动时,会去检查monitor,如果intent匹配,就会将monitor的hit count增加。通过TouchUtis我们可以对view进行点击操作。monitor.waitForActivityWithTimeout会等待2000ms,直到超时或者符合monitor监测的目标activity已经被启动。ViewAsserts.assertOnScreen可以测试我们的view是否在屏幕上,不过我们要将我们activity的DecorView作为root传进去。我们还可以通过调用sendKeys向Activity传递keyEvent事件。

测试异步任务

我们以一个例子进行分析。

public class AsyncTaskActivity extends Activity implements View.OnClickListener {    Button button;    private IJobListener listener;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_async_task);        button = (Button) findViewById(R.id.btn);        button.setOnClickListener(this);    }    public static interface IJobListener {        void executionDone();    }    public void setListener(IJobListener listener) {        this.listener = listener;    }    @Override    public void onClick(View v) {        if(v == button){            myTask.execute();        }    }    final AsyncTask<String, Void, String> myTask = new AsyncTask<String, Void, String>() {        @Override        protected String doInBackground(String... arg0) {            return "Long running stuff";        }        @Override        protected void onPostExecute(String result) {            super.onPostExecute(result);            if (listener != null) {                listener.executionDone();            }        }    };}

在点击button的时候,会执行一个AsyncTask,任务执行完毕的时候。回去调用listener的executionDone,我们如何测试这个任务执行完了呢?

public class AsyncTaskTest extends ActivityUnitTestCase<AsyncTaskActivity> {    AsyncTaskActivity activity;    Button btn;    public AsyncTaskTest() {        super(AsyncTaskActivity.class);    }    @Override    protected void setUp() throws Exception {        super.setUp();        Intent intent = new Intent(getInstrumentation().getTargetContext(),                AsyncTaskActivity.class);        startActivity(intent,null, null);        activity = getActivity();        btn = (Button) activity.findViewById(R.id.btn);    }    @Override    protected void tearDown() throws Exception {        super.tearDown();    }    public void testAsyncTask() throws Throwable    {        //初始化一个count为1的CountDownLatch        final CountDownLatch latch = new CountDownLatch(1);        //任务执行完时,将latch的count减一        activity.setListener(new AsyncTaskActivity.IJobListener() {            @Override            public void executionDone() {                latch.countDown();            }        });        //点击button,启动任务        getInstrumentation().runOnMainSync(new Runnable() {            @Override            public void run() {                btn.performClick();            }        });        //阻塞等待任务执行完毕,或者超时        boolean await = latch.await(30, TimeUnit.SECONDS);        assertTrue(await);    }}

这样我们就可以通过latch来判断异步任务是否在指定时间内执行完毕。

使用Monkey进行压力测试

Monkey是一只可爱的猴子,我们可以让它为我们模仿用户的点击事件,随机生成一定数量的事件流,比如点击、滑动等其他手势操作。基本用法为:

$ adb shell monkey [options] < event-count >

Monkey提供了很多选项,但大致可以分为四类:

  • 基本配置选项,比如生成的event的数量
  • 操作类型限制选项, 比如限制对某个package的应用执行测试
  • 事件类型和比例
  • 调试选项

比如我们想,对com.junli.test这个app进行测试,事件数量为1000, 输出详细信息,包括activity的切换;而且不执行那些系统级别的controlkey事件,比如home ,back , volume control,我们可以这么做

adb shell monkey -p com.junli.test -v 1500 –pct-syskeys 1 1000

-p指定包名,-v 指定输出信息级别,-pct-syskeys 指定系统级别事件占百分比,最后的1000代表我们想派发1000个随机事件。

更多Monkey的信息,大家可以参考 http://developer.android.com/tools/help/monkey.html

更多相关文章

  1. Android:theme="@android:style/Theme.Dialog"运行程序报错
  2. Android(安卓)基础总结:(十一)ContentResolver与ContentProvider如
  3. android:configChanges
  4. android:debuggable="false"
  5. Android事件处理的两种模型
  6. 爱踢门之锤子自由截屏快捷键配置(中)
  7. Android消息机制分析
  8. Android(安卓)Keyboard/Touch Panel分析
  9. Android(安卓)监听软键盘弹起和收起事件

随机推荐

  1. 7个 Javascript 面试题及回答策略 [每日
  2. JavaScript 高阶函数快速入门 [每日前端
  3. 分享十张表的数据!大家一起玩!
  4. 2019年 Vue.js 报告中的亮点 [每日前端夜
  5. 用python重新定义【2019十大网络流行语】
  6. 用 Node.js 写一个多人游戏服务器引擎 [
  7. 三十天写三十个网站后,我学到的东西[每日
  8. 14个最好的 JavaScript 数据可视化库[每
  9. deno如何偿还Node.js的十大技术债 [每日
  10. 11个最好的JavaScript动态效果库[每日前