测试你的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
更多相关文章
- Android:theme="@android:style/Theme.Dialog"运行程序报错
- Android(安卓)基础总结:(十一)ContentResolver与ContentProvider如
- android:configChanges
- android:debuggable="false"
- Android事件处理的两种模型
- 爱踢门之锤子自由截屏快捷键配置(中)
- Android消息机制分析
- Android(安卓)Keyboard/Touch Panel分析
- Android(安卓)监听软键盘弹起和收起事件