[b][size=medium]测试相关资源[/size][/b]
让开发自动化: 用 Eclipse 插件提高代码质量[url]http://www.ibm.com/developerworks/cn/java/j-ap01117/index.html[/url]
代码测试覆盖率介绍:[url]http://www.cnblogs.com/coderzh/archive/2009/03/29/1424344.html[/url]
[b]学习android单元测试时遇到的一些问题:[/b]
1.开始以为单元测试一定要从程序的launch Activity,一步一步的跳转到所要测试的Activity能对其进行测试。
但实际上,我们可以从任意一个activity开始,对任意一个activity进行测试。
2.在运行单元测试之前,一定要先将要测试的程序安装到模拟器或真机上。
[b]junit相关[/b]
android中的测试框架是扩展的junit3,所以在学习android中的单元测试签,可以先熟悉下junit3的使用,junit3要学习的东西应该并不多,就几页纸的东西。入门可以参考这个:[url]http://android.blog.51cto.com/268543/49994[/url]
[b]android单元测试框架中涉及的注解[/b]
@Suppress 可以用在类或这方法上,这样该类或者该方法就不会被执行
@UiThreadTest 可以用在方法上,这样该方法就会在程序的ui线程上执行
@LargeTest, @MediumTest, @SmallTest 用在方法上,标记所属的测试类型,主要是用于单独执行其中的某一类测试时使用。具体参考InstrumentationTestRunner类的文档。
@Smoke 具体用法还不清楚
[b]android单元测试框架中涉及的一些类的uml[/b]
[img]http://dl.iteye.com/upload/attachment/513766/4db7f7f4-80a0-3831-9013-0f23e79b69ba.png[/img]
[b]接下来我们以demo project来讲解如何使用android中的单元测试[/b]
主要包括了三个activity:
MainActivity:仅包含一个button,点击后就可以进入LoginActivity
LoginActivity:可以输入username, password,然后点击submit的话可进入HomeActivity,如果点击reset的话,输入的内容就会被清空
HomeActivity:在TextView中显示LoginActivity输入的内容
[b]首先我们创建要测试的项目demo(使用了2.1)[/b]
[img]http://dl.iteye.com/upload/attachment/513754/799321ed-ed41-344d-a9dd-4dd0e7579cb3.png[/img]
[img]http://dl.iteye.com/upload/attachment/513756/c176e312-28fa-33c9-8370-347aa4298890.png[/img]
[img]http://dl.iteye.com/upload/attachment/513758/807f08b9-02be-3a49-bb49-248db3ccc4b8.png[/img]
[b]MainActivity代码[/b]
[b]MainActivity的布局文件[/b]
[img]http://dl.iteye.com/upload/attachment/513760/0011a959-1bbd-391b-9ef2-05453b68531e.png[/img]
[b]LoginActivity代码[/b]
[b]LoginActivity的布局文件[/b]
[img]http://dl.iteye.com/upload/attachment/513764/f2410f24-991a-34a4-9cdc-b56bf0fe9148.png[/img]
[b]HomeActivity代码[/b]
[b]HomeActivity的布局文件[/b]
[img]http://dl.iteye.com/upload/attachment/513762/27e986b0-fe63-349e-86b3-05a7560dc568.png[/img]
程序非常简单,接下来我们为demo创建单元测试工程demo_unittest
[img]http://dl.iteye.com/upload/attachment/513748/326f401f-3546-3dfd-a100-b51eb6054304.png[/img]
[b]选择之前创建的工程demo,然后eclipse会自动帮我们设定api level,包名等。(测试用例的包名一般就是在要测试类的包名后加上test)[/b]
[img]http://dl.iteye.com/upload/attachment/513750/2aab55e5-f020-3ad8-b565-b9520cdb5bce.png[/img]
[img]http://dl.iteye.com/upload/attachment/513752/4dd2ec86-e033-3669-87d6-0206d127feba.png[/img]
[b]创建完后eclipse会自动为我们创建好所需的目录,Manifest.xml文件[/b]
[img]http://dl.iteye.com/upload/attachment/513768/f47260f5-ef91-3aec-9478-9b7f4cb013bd.png[/img]
[b]接下来就是为要测试的类编写测试用例。关于要用哪个测试用例类,在第一张UML图中也做了简要的说明。[/b]
ActivityInstrumentationTestCase2:主要是用于进行activity的功能测试,和activity的交互测试,如activity间的跳转,ui的交互等。
ActivityInstrumentationTestCase:这个类现在已deprecated了,所以不许考虑。
SingleLaunchActivityTestCase:该测试用例仅掉用setUp和tearDown一次,而不像其它测试用例类一样,没调用一次测试方法就会重新调用一次setUp和tearDown。所以主要测试activity是否能够正确处理多次调用。
ActivityUnitTestCase:主要用于测试Activity,因为它允许注入MockContext和MockApplicaton,所以可以测试Activity在不同资源和应用下的情况。
还有Application等的测试用例比较简单,看uml图。如果觉得不够详细,可以参考sdk文档的dev guide和api reference。
[b]MainActivityTest测试用例[/b]
[b]LoginActivityTest测试用例[/b]
[b]HomeActivityTest测试用例[/b]
[b]接下来是运行测试用例,首先我们需要把要测试的程序安装到模拟器或真机上[/b]
[img]http://dl.iteye.com/upload/attachment/513770/19f8fa10-06cf-3055-8b2a-9dac7dd9dd6c.png[/img]
[img]http://dl.iteye.com/upload/attachment/513772/b74099a9-6415-3784-9175-1bbaebc1477f.png[/img]
[b]运行测试用例,查看运行结果[/b]
[img]http://dl.iteye.com/upload/attachment/513774/6047f3b1-3044-3d5f-b3a7-509b3aebf909.png[/img]
[img]http://dl.iteye.com/upload/attachment/513776/96525d0f-c157-36b3-9c4a-da1796b40e6e.png[/img]
[b]这里仅仅讲了使用eclipse来进行单元测试,当然也是可以在命令行中进行单元测试的,但既然有eclipse这种图形界面的工具,就不再折腾什么命令行了。
还有就是测试用例也可以直接创建在源程序中(即源代码和测试代码放在一个项目中),具体怎么做的话google一些吧,就是把测试时涉及的一些Manifest元素移到源码工程的Manifest中等[/b]
让开发自动化: 用 Eclipse 插件提高代码质量[url]http://www.ibm.com/developerworks/cn/java/j-ap01117/index.html[/url]
代码测试覆盖率介绍:[url]http://www.cnblogs.com/coderzh/archive/2009/03/29/1424344.html[/url]
[b]学习android单元测试时遇到的一些问题:[/b]
1.开始以为单元测试一定要从程序的launch Activity,一步一步的跳转到所要测试的Activity能对其进行测试。
但实际上,我们可以从任意一个activity开始,对任意一个activity进行测试。
2.在运行单元测试之前,一定要先将要测试的程序安装到模拟器或真机上。
[b]junit相关[/b]
android中的测试框架是扩展的junit3,所以在学习android中的单元测试签,可以先熟悉下junit3的使用,junit3要学习的东西应该并不多,就几页纸的东西。入门可以参考这个:[url]http://android.blog.51cto.com/268543/49994[/url]
[b]android单元测试框架中涉及的注解[/b]
@Suppress 可以用在类或这方法上,这样该类或者该方法就不会被执行
@UiThreadTest 可以用在方法上,这样该方法就会在程序的ui线程上执行
@LargeTest, @MediumTest, @SmallTest 用在方法上,标记所属的测试类型,主要是用于单独执行其中的某一类测试时使用。具体参考InstrumentationTestRunner类的文档。
@Smoke 具体用法还不清楚
[b]android单元测试框架中涉及的一些类的uml[/b]
[img]http://dl.iteye.com/upload/attachment/513766/4db7f7f4-80a0-3831-9013-0f23e79b69ba.png[/img]
[b]接下来我们以demo project来讲解如何使用android中的单元测试[/b]
主要包括了三个activity:
MainActivity:仅包含一个button,点击后就可以进入LoginActivity
LoginActivity:可以输入username, password,然后点击submit的话可进入HomeActivity,如果点击reset的话,输入的内容就会被清空
HomeActivity:在TextView中显示LoginActivity输入的内容
[b]首先我们创建要测试的项目demo(使用了2.1)[/b]
[img]http://dl.iteye.com/upload/attachment/513754/799321ed-ed41-344d-a9dd-4dd0e7579cb3.png[/img]
[img]http://dl.iteye.com/upload/attachment/513756/c176e312-28fa-33c9-8370-347aa4298890.png[/img]
[img]http://dl.iteye.com/upload/attachment/513758/807f08b9-02be-3a49-bb49-248db3ccc4b8.png[/img]
[b]MainActivity代码[/b]
public class MainActivity extends Activity {
private static final boolean DEBUG = true;
private static final String TAG = "-- MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG) {
Log.i(TAG, "onCreate");
}
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
View toLoginView = findViewById(R.id.to_login);
toLoginView.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (DEBUG) {
Log.i(TAG, "toLoginView clicked");
}
Intent intent = new Intent(getApplicationContext(), LoginActivity.class);
startActivity(intent);
}
});
}
}
[b]MainActivity的布局文件[/b]
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="@+id/to_login"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="to login" />
</LinearLayout>
[img]http://dl.iteye.com/upload/attachment/513760/0011a959-1bbd-391b-9ef2-05453b68531e.png[/img]
[b]LoginActivity代码[/b]
public class LoginActivity extends Activity {
private static final boolean DEBUG = true;
private static final String TAG = "-- LoginActivity";
private EditText mUsernameView;
private EditText mPasswordView;
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG) {
Log.i(TAG, "onCreate");
}
super.onCreate(savedInstanceState);
setContentView(R.layout.act_login);
mUsernameView = (EditText) findViewById(R.id.username);
mPasswordView = (EditText) findViewById(R.id.password);
View submitView = findViewById(R.id.submit);
submitView.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (DEBUG) {
Log.i(TAG, "submitView clicked");
}
Intent intent = new Intent(getApplicationContext(), HomeActivity.class);
intent.putExtra(HomeActivity.EXTRA_USERNAME, mUsernameView.getText().toString());
intent.putExtra(HomeActivity.EXTRA_PASSWORD, mPasswordView.getText().toString());
startActivity(intent);
}
});
View resetView = findViewById(R.id.reset);
resetView.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (DEBUG) {
Log.i(TAG, "resetView clicked");
}
mUsernameView.setText("");
mPasswordView.setText("");
mUsernameView.requestFocus();
}
});
}
}
[b]LoginActivity的布局文件[/b]
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<TextView
android:id="@+id/label_username"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="username:" />
<EditText
android:id="@+id/username"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="text" />
<TextView
android:id="@+id/label_password"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="password:" />
<EditText
android:id="@+id/password"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
<Button
android:id="@+id/submit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="submit" />
<Button
android:id="@+id/reset"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="reset" />
</LinearLayout>
[img]http://dl.iteye.com/upload/attachment/513764/f2410f24-991a-34a4-9cdc-b56bf0fe9148.png[/img]
[b]HomeActivity代码[/b]
public class HomeActivity extends Activity {
private static final boolean DEBUG = true;
private static final String TAG = "-- HomeActivity";
public static final String EXTRA_USERNAME = "yuan.activity.username";
public static final String EXTRA_PASSWORD = "yuan.activity.password";
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG) {
Log.i(TAG, "onCreate");
}
super.onCreate(savedInstanceState);
Intent intent = getIntent();
StringBuilder sb = new StringBuilder();
sb.append("username:").append(intent.getStringExtra(EXTRA_USERNAME)).append("\n");
sb.append("password:").append(intent.getStringExtra(EXTRA_PASSWORD));
setContentView(R.layout.act_home);
TextView loginContentView = (TextView) findViewById(R.id.login_content);
loginContentView.setText(sb.toString());
}
}
[b]HomeActivity的布局文件[/b]
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/login_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
android:textColor="#EEE"
android:textStyle="bold"
android:textSize="25sp" />
</LinearLayout>
[img]http://dl.iteye.com/upload/attachment/513762/27e986b0-fe63-349e-86b3-05a7560dc568.png[/img]
程序非常简单,接下来我们为demo创建单元测试工程demo_unittest
[img]http://dl.iteye.com/upload/attachment/513748/326f401f-3546-3dfd-a100-b51eb6054304.png[/img]
[b]选择之前创建的工程demo,然后eclipse会自动帮我们设定api level,包名等。(测试用例的包名一般就是在要测试类的包名后加上test)[/b]
[img]http://dl.iteye.com/upload/attachment/513750/2aab55e5-f020-3ad8-b565-b9520cdb5bce.png[/img]
[img]http://dl.iteye.com/upload/attachment/513752/4dd2ec86-e033-3669-87d6-0206d127feba.png[/img]
[b]创建完后eclipse会自动为我们创建好所需的目录,Manifest.xml文件[/b]
[img]http://dl.iteye.com/upload/attachment/513768/f47260f5-ef91-3aec-9478-9b7f4cb013bd.png[/img]
[b]接下来就是为要测试的类编写测试用例。关于要用哪个测试用例类,在第一张UML图中也做了简要的说明。[/b]
ActivityInstrumentationTestCase2:主要是用于进行activity的功能测试,和activity的交互测试,如activity间的跳转,ui的交互等。
ActivityInstrumentationTestCase:这个类现在已deprecated了,所以不许考虑。
SingleLaunchActivityTestCase:该测试用例仅掉用setUp和tearDown一次,而不像其它测试用例类一样,没调用一次测试方法就会重新调用一次setUp和tearDown。所以主要测试activity是否能够正确处理多次调用。
ActivityUnitTestCase:主要用于测试Activity,因为它允许注入MockContext和MockApplicaton,所以可以测试Activity在不同资源和应用下的情况。
还有Application等的测试用例比较简单,看uml图。如果觉得不够详细,可以参考sdk文档的dev guide和api reference。
[b]MainActivityTest测试用例[/b]
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private static final String TAG = "=== MainActivityTest";
private Instrumentation mInstrument;
private MainActivity mActivity;
private View mToLoginView;
public MainActivityTest() {
super("yuan.activity", MainActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
mInstrument = getInstrumentation();
// 启动被测试的Activity
mActivity = getActivity();
mToLoginView = mActivity.findViewById(yuan.activity.R.id.to_login);
}
public void testPreConditions() {
// 在执行测试之前,确保程序的重要对象已被初始化
assertTrue(mToLoginView != null);
}
//mInstrument.runOnMainSync(new Runnable() {
// public void run() {
// mToLoginView.requestFocus();
// mToLoginView.performClick();
// }
//});
@UiThreadTest
public void testToLogin() {
// @UiThreadTest注解使整个方法在UI线程上执行,等同于上面注解掉的代码
mToLoginView.requestFocus();
mToLoginView.performClick();
}
@Suppress
public void testNotCalled() {
// 使用了@Suppress注解的方法不会被测试
Log.i(TAG, "method 'testNotCalled' is called");
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
}
[b]LoginActivityTest测试用例[/b]
public class LoginActivityTest extends ActivityInstrumentationTestCase2<LoginActivity> {
private static final String TAG = "=== LoginActivityTest";
private Instrumentation mInstrument;
private LoginActivity mActivity;
private EditText mUsernameView;
private EditText mPasswordView;
private View mSubmitView;
private View mResetView;
public LoginActivityTest() {
super("yuan.activity", LoginActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
/*
* 要向程序发送key事件的话,必须在getActivity之前调用该方法来关闭touch模式
* 否则key事件会被忽略
*/
setActivityInitialTouchMode(false);
mInstrument = getInstrumentation();
mActivity = getActivity();
Log.i(TAG, "current activity: " + mActivity.getClass().getName());
mUsernameView = (EditText) mActivity.findViewById(yuan.activity.R.id.username);
mPasswordView = (EditText) mActivity.findViewById(yuan.activity.R.id.password);
mSubmitView = mActivity.findViewById(yuan.activity.R.id.submit);
mResetView = mActivity.findViewById(yuan.activity.R.id.reset);
}
public void testPreConditions() {
assertTrue(mUsernameView != null);
assertTrue(mPasswordView != null);
assertTrue(mSubmitView != null);
assertTrue(mResetView != null);
}
public void testInput() {
input();
assertEquals("yuan", mUsernameView.getText().toString());
assertEquals("1123", mPasswordView.getText().toString());
}
public void testSubmit() {
input();
mInstrument.runOnMainSync(new Runnable() {
public void run() {
mSubmitView.requestFocus();
mSubmitView.performClick();
}
});
}
public void testReset() {
input();
mInstrument.runOnMainSync(new Runnable() {
public void run() {
mResetView.requestFocus();
mResetView.performClick();
}
});
assertEquals("", mUsernameView.getText().toString());
assertEquals("", mPasswordView.getText().toString());
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
private void input() {
mActivity.runOnUiThread(new Runnable() {
public void run() {
mUsernameView.requestFocus();
}
});
// 因为测试用例运行在单独的线程上,这里最好要
// 同步application,等待其执行完后再运行
mInstrument.waitForIdleSync();
sendKeys(KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_U,
KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_N);
// 效果同上面sendKeys之前的代码
mInstrument.runOnMainSync(new Runnable() {
public void run() {
mPasswordView.requestFocus();
}
});
sendKeys(KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_1,
KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3);
}
}
[b]HomeActivityTest测试用例[/b]
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");
// HomeActivity有extra参数,所以我们需要以intent来启动它
mHomeActivity = launchActivityWithIntent("yuan.activity", HomeActivity.class, intent);
mLoginContentView = (TextView) mHomeActivity.findViewById(yuan.activity.R.id.login_content);
}
public void testLoginContent() {
assertEquals(LOGIN_CONTENT, mLoginContentView.getText().toString());
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
}
[b]接下来是运行测试用例,首先我们需要把要测试的程序安装到模拟器或真机上[/b]
[img]http://dl.iteye.com/upload/attachment/513770/19f8fa10-06cf-3055-8b2a-9dac7dd9dd6c.png[/img]
[img]http://dl.iteye.com/upload/attachment/513772/b74099a9-6415-3784-9175-1bbaebc1477f.png[/img]
[b]运行测试用例,查看运行结果[/b]
[img]http://dl.iteye.com/upload/attachment/513774/6047f3b1-3044-3d5f-b3a7-509b3aebf909.png[/img]
[img]http://dl.iteye.com/upload/attachment/513776/96525d0f-c157-36b3-9c4a-da1796b40e6e.png[/img]
[b]这里仅仅讲了使用eclipse来进行单元测试,当然也是可以在命令行中进行单元测试的,但既然有eclipse这种图形界面的工具,就不再折腾什么命令行了。
还有就是测试用例也可以直接创建在源程序中(即源代码和测试代码放在一个项目中),具体怎么做的话google一些吧,就是把测试时涉及的一些Manifest元素移到源码工程的Manifest中等[/b]