`

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


接下来我们以demo project来讲解如何使用android中的单元测试
主要包括了三个activity:
MainActivity:仅包含一个button,点击后就可以进入LoginActivity

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

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

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





MainActivity代码
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的布局文件
<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>



LoginActivity代码
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的布局文件
<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>



HomeActivity代码
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的布局文件
<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>



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

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




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


接下来就是为要测试的类编写测试用例。关于要用哪个测试用例类,在第一张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测试用例
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();
	}
}


LoginActivityTest测试用例
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);
	}
}


HomeActivityTest测试用例
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();
	}
}


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




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




这里仅仅讲了使用eclipse来进行单元测试,当然也是可以在命令行中进行单元测试的,但既然有eclipse这种图形界面的工具,就不再折腾什么命令行了。
还有就是测试用例也可以直接创建在源程序中(即源代码和测试代码放在一个项目中),具体怎么做的话google一些吧,就是把测试时涉及的一些Manifest元素移到源码工程的Manifest中等
  • 大小: 33.9 KB
  • 大小: 52.2 KB
  • 大小: 51.6 KB
  • 大小: 49.2 KB
  • 大小: 49.4 KB
  • 大小: 89.3 KB
  • 大小: 4.4 KB
  • 大小: 6 KB
  • 大小: 7.5 KB
  • 大小: 23.3 KB
  • 大小: 80.4 KB
  • 大小: 61.1 KB
  • 大小: 55.8 KB
  • 大小: 53.7 KB
  • 大小: 13 KB
分享到:
评论
1 楼 nuist320 2013-12-02  
不错。mark一下

相关推荐

    Android单元测试的小例子

    2. **Mockito**:在Android单元测试中,经常需要模拟(Mock)依赖项以隔离被测试的代码。Mockito是一个流行的Java库,允许我们创建和配置mock对象。例如,如果一个函数依赖于网络请求,我们可以用Mockito模拟网络...

    Android单元测试与Volley单元测试

    本话题主要关注Android单元测试,特别是结合了Volley网络库的测试实践,以及如何利用Mockito这样的模拟工具进行测试。 **Android单元测试** Android单元测试通常是指在没有实际设备或模拟器的情况下,对应用程序的...

    Android单元测试-对Activity的测试

    上一篇文章已经介绍了单元测试的作用和简单示例,如果不了解的读者可以先阅读上一篇[ Android单元测试-作用以及简单示例](http://blog.csdn.net/double2hao/article/details/77159380)。 这篇文章主要介绍常见的...

    android 单元测试

    下面将详细介绍Android单元测试的相关知识点,以及如何利用JUnit进行测试。 首先,我们需要了解单元测试的基本概念。单元测试是对软件中的最小可测试单元进行检查和验证,如函数、方法或类。它的目标是确保代码的每...

    android单元测试实例二

    在这个"android单元测试实例二"中,我们将探讨如何对一个简单的加法函数进行单元测试,以及如何在同一个项目中组织测试代码。 首先,我们要理解单元测试的基本概念。单元测试是对软件中的最小可测试单元进行检查和...

    Android单元测试初探——Instrumentation

    学习Android有一段时间了,虽然前段时间对软件测试有了一些了解,不过接触android的单元测试却是头一次。这几天在物流大赛上也用了不少时间,所以对于android的单元测试没有太深入的研究,所以先写个基本入门吧!...

    Android单元测试框架搭建手册

    ### Android单元测试框架搭建手册 #### 一、前言 随着移动互联网的快速发展,Android应用的质量成为了用户体验的关键因素之一。为了提高应用的质量,确保代码的可靠性和稳定性,单元测试成为了必不可少的一部分。...

    android单元测试实例一

    通过这个实例,你可以深入理解Android单元测试的工作原理,学会如何编写和执行测试用例,以及如何利用工具和框架优化测试流程。记住,良好的测试实践不仅能提升代码质量,还能降低维护成本,提高开发效率。

    Android单元测试框架Robolectric.zip

    Robolectric 是一款Android单元测试框架,示例代码: @RunWith(RobolectricTestRunner.class) public class MyActivityTest { @Test public void clickingButton_shouldChangeResultsViewText() throws ...

    Android单元测试Demo

    **Android单元测试Demo** 在Android应用开发中,单元测试是一种重要的质量保证手段,它能够帮助开发者验证代码的各个模块是否按预期工作。本Demo旨在提供一个基础的Android单元测试实践,通过实例化和运行测试用例...

    Android-节省时间并在Android上清除您的单元测试

    首先,了解Android单元测试的基础至关重要。Android提供了JUnit框架,这是一个广泛用于Java的单元测试库,可以用于编写和运行测试用例。此外,Android Studio集成了TestNG,它提供了更多的功能和灵活性,如测试套件...

    Android 单元测试资料

    这篇资料主要涵盖的是Android单元测试的相关知识,通过两个Word文档——"android单元测试2.docx"和"Android单元测试.docx"进行了详尽的阐述。 首先,Android单元测试通常使用JUnit框架进行,这是一个广泛应用于Java...

    Android单元测试源码.zip

    "Android单元测试源码.zip"可能包含了一个或多个Android项目的单元测试代码示例,这些示例展示了如何在Android环境中有效地编写和执行单元测试。 在Android中,我们通常使用JUnit作为基础测试框架,配合Mockito进行...

    android单元测试翻译

    本文将围绕“Android单元测试”这一主题,详细讲解相关知识点,包括内容提供商测试、服务测试以及动画测试。 一、内容提供商测试 内容提供商(Content Provider)是Android系统中用于数据共享的机制,它允许应用...

    Android单元测试入门详解1

    本篇文章将介绍如何开始进行Android单元测试,特别是如何使用JUnit以及Robolectric框架。 首先,让我们从JUnit开始。JUnit是一个流行的Java测试框架,它允许开发者编写针对类或方法的测试用例。在Android项目中,你...

    android 单元测试 附

    本文将深入探讨Android单元测试的相关知识点,包括其重要性、如何进行单元测试、常用工具以及如何结合实际项目进行实践。 首先,理解单元测试的基本概念至关重要。单元测试是对软件中的最小可测试单元进行检查和...

Global site tag (gtag.js) - Google Analytics