转:http://archive.cnblogs.com/a/2220315/


android之单元测试——上

9月份接手公司一个android的项目HYB,我主要负责了HYB中单元测试用例的编写.单元测试的编写保证了应用程序的健壮性以及可维护性.做了近一个月的android单元测试,有些心得体会和大家分享一下.

单元测试是在软件开发过程中,最低级别的测试活动,在该活动中软件的独立单元将在与程序的其他部分相隔离的情况下进行测试.android的单元测试其实也是一样的.

android中的测试框架是扩展的junit3,所以在学习android的单元测试之前,可以先学习junit3的使用.junit3要学习的东西其实也不多.

junit3的入门可以参考:http://android.blog.51cto.com/268543/49994

文档:http://wenku.baidu.com/view/87a176abd1f34693daef3e54.html

我在android的单元测试中所涉及到的相关类:

ActivityInstrumentationTestCase:初次做测试的话可以暂时不用考虑该类.

ActivityInstrumentationTestCase2:该类主要进行activity的功能测试和activity的交互测试.例如:Activity的跳转,UI的交互等.我用的最多的就是这个类.

SingleLaunchActivityTestCase:该测试用例仅调用setUp()和tearDown()一次,与其他测试用例不一样的是每调用一次测试方法,就会重新调用setUp()和tearDown().所以该类是为了测试activity是否能够正确处理多次调用.

ActivityUnitTestCase:主要用于测试Activity,因为它允许注入MockContext和MockApplicaton,所以可以测试Activity在不同资源和应用的情况.

具体的还可以参考sdk文档:dev guide和api reference.

下图就是android中与测试相关类的uml图:

android之单元测试——上_第1张图片

下面就拿一个具体的案例和大家讲解一下android中的单元测试是怎么实现的.首先需要说明的是这个案例是我在网上搜集来的,所以非常感谢博文:http://yuanzhifei89.iteye.com/blog/1122104

对该测试案例做一个简单的介绍:

android之单元测试——上_第2张图片包含三个Activity:MainActivityHomeActivityLoginActivity.

MainActivity:很简单就是一个Button,点击该Button进入到LoginActivity.

LoginActivity:四个可交互控件分别是:username、password、submit、reset

HomeActivity:显示LoginActivity提交过来的username、password

首先是demo这个android app的编写.

MainActivity的核心代码如下:

复制代码
toLoginView.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(),LoginActivity.class);
startActivity(intent);
}
});
复制代码

LoginActivity的核心代码如下:

复制代码
 1     //declare widget
2 private EditText mUsernameView;
3 private EditText mPasswordView;
4
5 /** Called when the activity is first created. */
6 @Override
7 protected void onCreate(Bundle savedInstanceState) {
8 if(DEBUG) {
9 Log.i(TAG,"LoginActivity's onCreate");
10 }
11 // TODO Auto-generated method stub
12 super.onCreate(savedInstanceState);
13 setContentView(R.layout.act_login);
14
15 mUsernameView = (EditText)findViewById(R.id.username);
16 mPasswordView = (EditText)findViewById(R.id.password);
17
18 View submitView = findViewById(R.id.submit);
19 submitView.setOnClickListener(new View.OnClickListener() {
20 @Override
21 public void onClick(View v) {
22 // TODO Auto-generated method stub
23 if(DEBUG) {
24 Log.i(TAG,"submitView Clicked");
25 }
26
27 Intent intent = new Intent(getApplicationContext(), HomeActivity.class);
28 intent.putExtra(HomeActivity.EXTRA_USERNAME, mUsernameView.getText().toString());
29 intent.putExtra(HomeActivity.EXTRA_PASSWORD, mPasswordView.getText().toString());
30
31 startActivity(intent);
32 }
33 });
34
35 View resetView = findViewById(R.id.reset);
36 resetView.setOnClickListener(new View.OnClickListener() {
37
38 @Override
39 public void onClick(View v) {
40 // TODO Auto-generated method stub
41 if(DEBUG) {
42 Log.i(TAG,"resetView Clicked");
43 }
44 mUsernameView.setText("");
45 mPasswordView.setText("");
46 mUsernameView.requestFocus();
47 }
48 });
复制代码

为了方便大家,把LoginActivity的布局文件的代码也贴出来:

复制代码
 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 android:layout_width="fill_parent" android:layout_height="fill_parent"
3 android:orientation="vertical">
4 <TextView
5 android:id="@+id/label_username"
6 android:layout_width="fill_parent"
7 android:layout_height="wrap_content"
8 android:text="username:"
9 />
10 <EditText
11 android:id="@+id/username"
12 android:layout_width="fill_parent"
13 android:layout_height="wrap_content"
14 android:inputType="text"
15 />
16 <TextView
17 android:id="@+id/label_password"
18 android:layout_width="fill_parent"
19 android:layout_height="wrap_content"
20 android:text="password:"
21 />
22 <EditText
23 android:id="@+id/password"
24 android:layout_width="fill_parent"
25 android:layout_height="wrap_content"
26 android:inputType="textPassword"
27 />
28 <Button
29 android:id="@+id/submit"
30 android:layout_width="fill_parent"
31 android:layout_height="wrap_content"
32 android:text="submit"
33 />
34 <Button
35 android:id="@+id/reset"
36 android:layout_width="fill_parent"
37 android:layout_height="wrap_content"
38 android:text="reset"
39 />
40 </LinearLayout>
复制代码

最后就是HomeActivity了,很简单显示username和password就可以了,代码如下:

复制代码
 1 public class HomeActivity extends Activity {
2
3 private static final boolean DEBUG = true;
4 private static final String TAG = "--HomeActivity--";
5
6 public static final String EXTRA_USERNAME = "com.ceo.demo.activity.username";
7 public static final String EXTRA_PASSWORD = "com.ceo.demo.activity.password";
8
9 @Override
10 protected void onCreate(Bundle savedInstanceState) {
11 if(DEBUG) {
12 Log.i(TAG,"HomeActivity's onCreate");
13 }
14
15 // TODO Auto-generated method stub
16 super.onCreate(savedInstanceState);
17 setContentView(R.layout.act_home);
18
19 Intent intent = getIntent();
20 StringBuilder sb = new StringBuilder();
21 sb.append("username:").append(intent.getStringExtra(EXTRA_USERNAME)).append("\n");
22 sb.append("password:").append(intent.getStringExtra(EXTRA_PASSWORD));
23
24 TextView loginContentView = (TextView) findViewById(R.id.login_content);
25 loginContentView.setText(sb.toString());
26
27 }
28 }
复制代码

至此,demo应用程序编写好了,下面就是demo_unittest的编写了.

第一步:新建demo_unittest项目,和新建一个android项目的步骤基本类似.

android之单元测试——上_第3张图片

单击之后,弹出New Android Unit Test的对话框

Test Project Name:填写demo_unittest application name 也是这个

An existing Android Project:选择需要编写测试用例的项目----->这里我们选择demo

版本的选择,最好是和原项目的版本一致,避免不必要的错误.

这里需要注意一点就是包名的取名,最好是在原有项目的包名之后加上.test

点击finish之后,系统会自动为我们创建所需要的目录等信息.非常方便.

接下来可以选择AndroidManifest.xml文件,查看一下里面的构造,你会发现:

复制代码
 1 <?xml version="1.0" encoding="utf-8"?>
2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 package="com.ceo.demo.activity.test"
4 android:versionCode="1"
5 android:versionName="1.0">
6 <application android:icon="@drawable/icon" android:label="@string/app_name">
7
8 <uses-library android:name="android.test.runner" />
9 </application>
10 <uses-sdk android:minSdkVersion="8" />
11 <instrumentation android:targetPackage="com.ceo.demo.activity" android:name="android.test.InstrumentationTestRunner" />
12 </manifest>
复制代码

系统自动帮我们添加了所需属性.

接下来就是测试类的编写:首先是MainActivityTest:

复制代码
 1 public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
2 //private static final String TAG = "=== MainActivityTest";
3
4 @SuppressWarnings("unused")
5 private Instrumentation mInstrument;
6 private MainActivity mActivity;
7 private View mToLoginView;
8
9 public MainActivityTest() {
10 super("com.ceo.demo.activity", MainActivity.class);
11 }
12
13 @Override
14 protected void setUp() throws Exception {
15 super.setUp();
16
17 mInstrument = getInstrumentation();
18 // 启动被测试的Activity
19 mActivity = getActivity();
20 mToLoginView = mActivity.findViewById(com.ceo.demo.activity.R.id.to_login);
21 }
22
23 public void testPreConditions() {
24 // 在执行测试之前,确保程序的重要对象已被初始化
25 assertTrue(mToLoginView != null);
26 }
27
28 //@UiThreadTest 可以用在方法上,这样该方法就会在程序的ui线程上执行
29 @UiThreadTest
30 public void testToLogin() {
31 // @UiThreadTest注解使整个方法在UI线程上执行
32 mToLoginView.requestFocus();
33 mToLoginView.performClick();
34 }
35
36 @Override
37 protected void tearDown() throws Exception {
38
39 super.tearDown();
40 }
41
42 }
复制代码

最主要的还是LoginActivityTest的编写,比较有代表性.

复制代码
 1 public class LoginActivityTest extends ActivityInstrumentationTestCase2<LoginActivity> {
2 private static final String TAG = "=== LoginActivityTest";
3
4 private Instrumentation mInstrument;
5 private LoginActivity mActivity;
6 private EditText mUsernameView;
7 private EditText mPasswordView;
8 private View mSubmitView;
9 private View mResetView;
10
11 public LoginActivityTest() {
12 super("com.ceo.demo.activity", LoginActivity.class);
13 }
14
15 @Override
16 protected void setUp() throws Exception {
17 super.setUp();
18 /*
19 * 要向程序发送key事件的话,必须在getActivity之前调用该方法来关闭touch模式
20 * 否则key事件会被忽略
21 */
22 setActivityInitialTouchMode(false);
23 mInstrument = getInstrumentation();
24 mActivity = getActivity();
25 Log.i(TAG, "current activity: " + mActivity.getClass().getName());
26
27 mUsernameView = (EditText) mActivity.findViewById(com.ceo.demo.activity.R.id.username);
28 mPasswordView = (EditText) mActivity.findViewById(com.ceo.demo.activity.R.id.password);
29 mSubmitView = mActivity.findViewById(com.ceo.demo.activity.R.id.submit);
30 mResetView = mActivity.findViewById(com.ceo.demo.activity.R.id.reset);
31
32 }
33
34 public void testPreConditions() {
35 assertTrue(mUsernameView != null);
36 assertTrue(mPasswordView != null);
37 assertTrue(mSubmitView != null);
38 assertTrue(mResetView != null);
39 }
40
41 public void testInput() {
42 input();
43 assertEquals("yuan", mUsernameView.getText().toString());
44 assertEquals("1123", mPasswordView.getText().toString());
45 }
46
47 public void testSubmit() {
48 input();
49 mInstrument.runOnMainSync(new Runnable() {
50 public void run() {
51 mSubmitView.requestFocus();
52 mSubmitView.performClick();
53 }
54 });
55 }
56
57 public void testReset() {
58 input();
59 mInstrument.runOnMainSync(new Runnable() {
60 public void run() {
61 mResetView.requestFocus();
62 mResetView.performClick();
63 }
64 });
65 assertEquals("", mUsernameView.getText().toString());
66 assertEquals("", mPasswordView.getText().toString());
67 }
68
69 @Override
70 public void tearDown() throws Exception {
71 super.tearDown();
72 }
73
74 private void input() {
75 mActivity.runOnUiThread(new Runnable() {
76 public void run() {
77 mUsernameView.requestFocus();
78 }
79 });
80 // 因为测试用例运行在单独的线程上,这里最好要
81 // 同步application,等待其执行完后再运行
82 mInstrument.waitForIdleSync();
83 sendKeys(KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_U,
84 KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_N);
85
86 // 效果同上面sendKeys之前的代码
87 mInstrument.runOnMainSync(new Runnable() {
88 public void run() {
89 mPasswordView.requestFocus();
90 }
91 });
92 sendKeys(KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_1,
93 KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3);
94 }
95 }
复制代码

HomeActivity的编写:

复制代码
public class HomeActivityTest extends ActivityUnitTestCase<HomeActivity> {
//private static final String TAG = "=== HomeActivityTest";

private static final String LOGIN_CONTENT = "username:yuan\npassword:1123";

private HomeActivity mHomeActivity;
private TextView mLoginContentView;

public HomeActivityTest() {
super(HomeActivity.class);
}

@Override
public void setUp() throws Exception {
super.setUp();
Intent intent = new Intent();
intent.putExtra(HomeActivity.EXTRA_USERNAME, "yuan");
intent.putExtra(HomeActivity.EXTRA_PASSWORD, "1123");
mHomeActivity = launchActivityWithIntent("com.ceo.demo.activity", HomeActivity.class, intent);
mLoginContentView = (TextView) mHomeActivity.findViewById(com.ceo.demo.activity.R.id.login_content);
}

public void testLoginContent() {
assertEquals(LOGIN_CONTENT, mLoginContentView.getText().toString());
}

@Override
public void tearDown() throws Exception {
super.tearDown();
}
}
复制代码

最后就是检验成果的时候了:

右击项目的包名,run as Android Junit Test

android之单元测试——上_第4张图片

绿色全线通过.

再次感谢博客:http://yuanzhifei89.iteye.com/blog/1122104案例的提供.

注意事项:

#1.如果没有预先安装项目的话,在做单元测试的时候会先安装项目到您的模拟器或者真机上,然后再执行单元测试.

#2.对Activity的测试,不一定非要从程序的Launch Activity开始,一步一步往下执行,可以从任意一个需要测试的Activity开始执行.

#3.在测试类中怎样获取控件:toLoginView是一个Button,View toLoginView = findViewById(R.id.to_login);

#4.从一个Activity启动另一个Activity,Intent intent = new Intent(getApplicationContext(),LoginActivity.class);

#5.获得当前的Activity再调用Activity的add方法--->最后进行预期值和实际值的判断,assertEquals(5, getActivity().add(2, 3));

#6.直接在UI线程中执行模拟事件,容易造成UI线程堵塞,解决方案在后面的博文中给出.

#7.protected void setUp()----->Sets up the fixture, for example, open a network connection. This method is called before a test is executed.

#8.protected void tearDown()----->Make sure all resources are cleaned up and garbage collected before moving on to the next test. Subclasses that override this method should make sure they call super.tearDown() at the end of the overriding method.

#9.setUp()用来初始设置。例如:启动Activity、初始化资源等,tearDown()用来垃圾的清理以及资源的回收等。例如:关闭数据库、finish Activity等

扩展阅读:

开发自动化:http://www.ibm.com/developerworks/cn/java/j-ap01117/index.html
代码覆盖率:http://www.cnblogs.com/coderzh/archive/2009/03/29/1424344.html
单元测试汇总:http://www.51testing.com/html/94/category-catid-94.html
集成测试和单元测试的关系:http://www.outsourcing.org.cn/newsshow.asp?newsid=162&classid=17
单元测试和单元测试框架:http://www.51testing.com/zhuanti/unit/unit.html


2011.10.21.pm

jack.li

QQ:523072842
Email:523072842@qq.com


更多相关文章

  1. Android API之Typeface代码演示
  2. Android开发者应该深入学习的10个开源应用项目
  3. 基于Eclipse的Android开发(一)——创建第一个Android项目
  4. 将androd项目作为moudle引入配置信息
  5. Android studio制作计算器源代码
  6. 已有项目导入他人创建的flutter项目(android studio)
  7. Android Studio 单刷《第一行代码》系列 03 —— Activity 基础

随机推荐

  1. android直接读取项目中的sqlite数据库
  2. Android系列教程(二)
  3. Android中的资源与国际化!
  4. 如何降低android应用程序的耗电量
  5. Android(安卓)JIT带来的虚拟机崩溃问题及
  6. Android(安卓)匿名共享内存C++接口分析
  7. 开始在Windows上开发Android
  8. Android中日期操作总结
  9. 转载 Android(安卓)脚本设计之 SL4A
  10. conversion to dalvik format failed wit