测试你的Android应用程序

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值