android测试分类,android单元测试

测试相关资源

让开发自动化: 用 Eclipse 插件提高代码质量http://www.ibm.com/developerworks/cn/java/j-ap01117/index.html

代码测试覆盖率介绍:http://www.cnblogs.com/coderzh/archive/2009/03/29/1424344.html

学习android单元测试时遇到的一些问题:

1.开始以为单元测试一定要从程序的launch Activity,一步一步的跳转到所要测试的Activity能对其进行测试。

但实际上,我们可以从任意一个activity开始,对任意一个activity进行测试。

2.在运行单元测试之前,一定要先将要测试的程序安装到模拟器或真机上。

junit相关

android中的测试框架是扩展的junit3,所以在学习android中的单元测试签,可以先熟悉下junit3的使用,junit3要学习的东西应该并不多,就几页纸的东西。入门可以参考这个:http://android.blog.51cto.com/268543/49994

android单元测试框架中涉及的注解

@Suppress 可以用在类或这方法上,这样该类或者该方法就不会被执行

@UiThreadTest 可以用在方法上,这样该方法就会在程序的ui线程上执行

@LargeTest, @MediumTest, @SmallTest 用在方法上,标记所属的测试类型,主要是用于单独执行其中的某一类测试时使用。具体参考InstrumentationTestRunner类的文档。

@Smoke 具体用法还不清楚

android单元测试框架中涉及的一些类的uml

20622988_1.png

接下来我们以demo project来讲解如何使用android中的单元测试

主要包括了三个activity:

MainActivity:仅包含一个button,点击后就可以进入LoginActivity

LoginActivity:可以输入username, password,然后点击submit的话可进入HomeActivity,如果点击reset的话,输入的内容就会被清空

HomeActivity:在TextView中显示LoginActivity输入的内容

首先我们创建要测试的项目demo(使用了2.1)

20622988_2.png

20622988_3.png

20622988_4.png

MainActivity代码

Java代码 20622988_5.png

20622988_6.gifpublicclassMainActivityextendsActivity {

privatestaticfinalbooleanDEBUG =true;

privatestaticfinalString TAG ="-- MainActivity";

@Override

protectedvoidonCreate(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(newView.OnClickListener() {

publicvoidonClick(View view) {

if(DEBUG) {

Log.i(TAG, "toLoginView clicked");

}

Intent intent = newIntent(getApplicationContext(), LoginActivity.class);

startActivity(intent);

}

});

}

}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);

}

});

}

}

MainActivity的布局文件

Xml代码 20622988_5.png

20622988_6.gif

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

android:id="@+id/to_login"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:layout_gravity="bottom"

android:text="to login"/>

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

android:id="@+id/to_login"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:layout_gravity="bottom"

android:text="to login" />

20622988_7.png

LoginActivity代码

Java代码 20622988_5.png

20622988_6.gifpublicclassLoginActivityextendsActivity {

privatestaticfinalbooleanDEBUG =true;

privatestaticfinalString TAG ="-- LoginActivity";

privateEditText mUsernameView;

privateEditText mPasswordView;

@Override

protectedvoidonCreate(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(newView.OnClickListener() {

publicvoidonClick(View view) {

if(DEBUG) {

Log.i(TAG, "submitView clicked");

}

Intent intent = newIntent(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(newView.OnClickListener() {

publicvoidonClick(View view) {

if(DEBUG) {

Log.i(TAG, "resetView clicked");

}

mUsernameView.setText("");

mPasswordView.setText("");

mUsernameView.requestFocus();

}

});

}

}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();

}

});

}

}

LoginActivity的布局文件

Xml代码 20622988_5.png

20622988_6.gif

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:orientation="vertical"

>

android:id="@+id/label_username"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="username:"/>

android:id="@+id/username"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:inputType="text"/>

android:id="@+id/label_password"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="password:"/>

android:id="@+id/password"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:inputType="textPassword"/>

android:id="@+id/submit"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="submit"/>

android:id="@+id/reset"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="reset"/>

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:orientation="vertical"

>

android:id="@+id/label_username"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="username:" />

android:id="@+id/username"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:inputType="text" />

android:id="@+id/label_password"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="password:" />

android:id="@+id/password"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:inputType="textPassword" />

android:id="@+id/submit"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="submit" />

android:id="@+id/reset"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="reset" />

20622988_8.png

HomeActivity代码

Java代码 20622988_5.png

20622988_6.gifpublicclassHomeActivityextendsActivity {

privatestaticfinalbooleanDEBUG =true;

privatestaticfinalString TAG ="-- HomeActivity";

publicstaticfinalString EXTRA_USERNAME ="yuan.activity.username";

publicstaticfinalString EXTRA_PASSWORD ="yuan.activity.password";

@Override

protectedvoidonCreate(Bundle savedInstanceState) {

if(DEBUG) {

Log.i(TAG, "onCreate");

}

super.onCreate(savedInstanceState);

Intent intent = getIntent();

StringBuilder sb = newStringBuilder();

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());

}

}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());

}

}

HomeActivity的布局文件

Xml代码 20622988_5.png

20622988_6.gif

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

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"/>

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

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" />

20622988_9.png

程序非常简单,接下来我们为demo创建单元测试工程demo_unittest

20622988_10.png

选择之前创建的工程demo,然后eclipse会自动帮我们设定api level,包名等。(测试用例的包名一般就是在要测试类的包名后加上test)

20622988_11.png

20622988_12.png

创建完后eclipse会自动为我们创建好所需的目录,Manifest.xml文件

20622988_13.png

接下来就是为要测试的类编写测试用例。关于要用哪个测试用例类,在第一张UML图中也做了简要的说明。

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。

MainActivityTest测试用例

Java代码 20622988_5.png

20622988_6.gifpublicclassMainActivityTestextendsActivityInstrumentationTestCase2 {

privatestaticfinalString TAG ="=== MainActivityTest";

privateInstrumentation mInstrument;

privateMainActivity mActivity;

privateView mToLoginView;

//必不可少的东西, 不要忘记

publicMainActivityTest() {

super("yuan.activity", MainActivity.class);

}

@Override

publicvoidsetUp()throwsException {

super.setUp();

mInstrument = getInstrumentation();

// 启动被测试的Activity

mActivity = getActivity();

mToLoginView = mActivity.findViewById(yuan.activity.R.id.to_login);

}

publicvoidtestPreConditions() {

// 在执行测试之前,确保程序的重要对象已被初始化

assertTrue(mToLoginView != null);

}

//mInstrument.runOnMainSync(new Runnable() {

//  public void run() {

//      mToLoginView.requestFocus();

//      mToLoginView.performClick();

//  }

//});

@UiThreadTest

publicvoidtestToLogin() {

// @UiThreadTest注解使整个方法在UI线程上执行,等同于上面注解掉的代码

mToLoginView.requestFocus();

mToLoginView.performClick();

}

@Suppress

publicvoidtestNotCalled() {

// 使用了@Suppress注解的方法不会被测试

Log.i(TAG, "method 'testNotCalled' is called");

}

@Override

publicvoidtearDown()throwsException {

super.tearDown();

}

}public class MainActivityTest extends ActivityInstrumentationTestCase2 {

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();

}

}

LoginActivityTest测试用例

Java代码 20622988_5.png

20622988_6.gifpublicclassLoginActivityTestextendsActivityInstrumentationTestCase2 {

privatestaticfinalString TAG ="=== LoginActivityTest";

privateInstrumentation mInstrument;

privateLoginActivity mActivity;

privateEditText mUsernameView;

privateEditText mPasswordView;

privateView mSubmitView;

privateView mResetView;

publicLoginActivityTest() {

super("yuan.activity", LoginActivity.class);

}

@Override

publicvoidsetUp()throwsException {

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);

}

publicvoidtestPreConditions() {

assertTrue(mUsernameView != null);

assertTrue(mPasswordView != null);

assertTrue(mSubmitView != null);

assertTrue(mResetView != null);

}

publicvoidtestInput() {

input();

assertEquals("yuan", mUsernameView.getText().toString());

assertEquals("1123", mPasswordView.getText().toString());

}

publicvoidtestSubmit() {

input();

mInstrument.runOnMainSync(newRunnable() {

publicvoidrun() {

mSubmitView.requestFocus();

mSubmitView.performClick();

}

});

}

publicvoidtestReset() {

input();

mInstrument.runOnMainSync(newRunnable() {

publicvoidrun() {

mResetView.requestFocus();

mResetView.performClick();

}

});

assertEquals("", mUsernameView.getText().toString());

assertEquals("", mPasswordView.getText().toString());

}

@Override

publicvoidtearDown()throwsException {

super.tearDown();

}

privatevoidinput() {

mActivity.runOnUiThread(newRunnable() {

publicvoidrun() {

mUsernameView.requestFocus();

}

});

// 因为测试用例运行在单独的线程上,这里最好要

// 同步application,等待其执行完后再运行

mInstrument.waitForIdleSync();

sendKeys(KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_U,

KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_N);

// 效果同上面sendKeys之前的代码

mInstrument.runOnMainSync(newRunnable() {

publicvoidrun() {

mPasswordView.requestFocus();

}

});

sendKeys(KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_1,

KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3);

}

}public class LoginActivityTest extends ActivityInstrumentationTestCase2 {

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);

}

}

HomeActivityTest测试用例

Java代码 20622988_5.png

20622988_6.gifpublicclassHomeActivityTestextendsActivityUnitTestCase {

privatestaticfinalString TAG ="=== HomeActivityTest";

privatestaticfinalString LOGIN_CONTENT ="username:yuan\npassword:1123";

privateHomeActivity mHomeActivity;

privateTextView mLoginContentView;

publicHomeActivityTest() {

super(HomeActivity.class);

}

@Override

publicvoidsetUp()throwsException {

super.setUp();

Intent intent = newIntent();

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);

}

publicvoidtestLoginContent() {

assertEquals(LOGIN_CONTENT, mLoginContentView.getText().toString());

}

@Override

publicvoidtearDown()throwsException {

super.tearDown();

}

}public class HomeActivityTest extends ActivityUnitTestCase {

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();

}

}

接下来是运行测试用例,首先我们需要把要测试的程序安装到模拟器或真机上

20622988_14.png

20622988_15.png

运行测试用例,查看运行结果

20622988_16.png

20622988_17.png

这里仅仅讲了使用eclipse来进行单元测试,当然也是可以在命令行中进行单元测试的,但既然有eclipse这种图形界面的工具,就不再折腾什么命令行了。

还有就是测试用例也可以直接创建在源程序中(即源代码和测试代码放在一个项目中),具体怎么做的话google一些吧,就是把测试时涉及的一些Manifest元素移到源码工程的Manifest中等

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值