转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 接下来我们以demo project来讲解如何使用android中的单元测试 主要包括了三个activity: MainActivity:仅包含一个button,点击后就可以进入LoginActivity LoginActivity:可以输入username, password,然后点击submit的话可进入HomeActivity,如果点击reset的话,输入的内容就会被清空 HomeActivity:在TextView中显示LoginActivity输入的内容 首先我们创建要测试的项目demo(使用了2.1) MainActivity代码
Java代码  收藏代码
  1. public class MainActivity extends Activity {
  2.     private static final boolean DEBUG = true;
  3.     private static final String TAG = “– MainActivity”;
  4.     @Override
  5.     protected void onCreate(Bundle savedInstanceState) {
  6.         if (DEBUG) {
  7.             Log.i(TAG, “onCreate”);
  8.         }
  9.         super.onCreate(savedInstanceState);
  10.         setContentView(R.layout.act_main);
  11.         View toLoginView = findViewById(R.id.to_login);
  12.         toLoginView.setOnClickListener(new View.OnClickListener() {
  13.             public void onClick(View view) {
  14.                 if (DEBUG) {
  15.                     Log.i(TAG, “toLoginView clicked”);
  16.                 }
  17.                 Intent intent = new Intent(getApplicationContext(), LoginActivity.class);
  18.                 startActivity(intent);
  19.             }
  20.         });
  21.     }
  22. }
MainActivity的布局文件
Xml代码  收藏代码
  1. <LinearLayout
  2.     xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:layout_width=“fill_parent”
  4.     android:layout_height=“fill_parent”
  5. >
  6.     <Button
  7.         android:id=“@+id/to_login”
  8.         android:layout_width=“fill_parent”
  9.         android:layout_height=“wrap_content”
  10.         android:layout_gravity=“bottom”
  11.         android:text=“to login” />
  12. </LinearLayout>
LoginActivity代码
Java代码  收藏代码
  1. public class LoginActivity extends Activity {
  2.     private static final boolean DEBUG = true;
  3.     private static final String TAG = “– LoginActivity”;
  4.     private EditText mUsernameView;
  5.     private EditText mPasswordView;
  6.     @Override
  7.     protected void onCreate(Bundle savedInstanceState) {
  8.         if (DEBUG) {
  9.             Log.i(TAG, “onCreate”);
  10.         }
  11.         super.onCreate(savedInstanceState);
  12.         setContentView(R.layout.act_login);
  13.         mUsernameView = (EditText) findViewById(R.id.username);
  14.         mPasswordView = (EditText) findViewById(R.id.password);
  15.         View submitView = findViewById(R.id.submit);
  16.         submitView.setOnClickListener(new View.OnClickListener() {
  17.             public void onClick(View view) {
  18.                 if (DEBUG) {
  19.                     Log.i(TAG, “submitView clicked”);
  20.                 }
  21.                 Intent intent = new Intent(getApplicationContext(), HomeActivity.class);
  22.                 intent.putExtra(HomeActivity.EXTRA_USERNAME, mUsernameView.getText().toString());
  23.                 intent.putExtra(HomeActivity.EXTRA_PASSWORD, mPasswordView.getText().toString());
  24.                 startActivity(intent);
  25.             }
  26.         });
  27.         View resetView = findViewById(R.id.reset);
  28.         resetView.setOnClickListener(new View.OnClickListener() {
  29.             public void onClick(View view) {
  30.                 if (DEBUG) {
  31.                     Log.i(TAG, “resetView clicked”);
  32.                 }
  33.                 mUsernameView.setText(“”);
  34.                 mPasswordView.setText(“”);
  35.                 mUsernameView.requestFocus();
  36.             }
  37.         });
  38.     }
  39. }
LoginActivity的布局文件
Xml代码  收藏代码
  1. <LinearLayout
  2.     xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:layout_width=“fill_parent”
  4.     android:layout_height=“fill_parent”
  5.     android:orientation=“vertical”
  6. >
  7.     <TextView
  8.         android:id=“@+id/label_username”
  9.         android:layout_width=“fill_parent”
  10.         android:layout_height=“wrap_content”
  11.         android:text=“username:” />
  12.     <EditText
  13.         android:id=“@+id/username”
  14.         android:layout_width=“fill_parent”
  15.         android:layout_height=“wrap_content”
  16.         android:inputType=“text” />
  17.     <TextView
  18.         android:id=“@+id/label_password”
  19.         android:layout_width=“fill_parent”
  20.         android:layout_height=“wrap_content”
  21.         android:text=“password:” />
  22.     <EditText
  23.         android:id=“@+id/password”
  24.         android:layout_width=“fill_parent”
  25.         android:layout_height=“wrap_content”
  26.         android:inputType=“textPassword” />
  27.     <Button
  28.         android:id=“@+id/submit”
  29.         android:layout_width=“fill_parent”
  30.         android:layout_height=“wrap_content”
  31.         android:text=“submit” />
  32.     <Button
  33.         android:id=“@+id/reset”
  34.         android:layout_width=“fill_parent”
  35.         android:layout_height=“wrap_content”
  36.         android:text=“reset” />
  37. </LinearLayout>
HomeActivity代码
Java代码  收藏代码
  1. public class HomeActivity extends Activity {
  2.     private static final boolean DEBUG = true;
  3.     private static final String TAG = “– HomeActivity”;
  4.     public static final String EXTRA_USERNAME = “yuan.activity.username”;
  5.     public static final String EXTRA_PASSWORD = “yuan.activity.password”;
  6.     @Override
  7.     protected void onCreate(Bundle savedInstanceState) {
  8.         if (DEBUG) {
  9.             Log.i(TAG, “onCreate”);
  10.         }
  11.         super.onCreate(savedInstanceState);
  12.         Intent intent = getIntent();
  13.         StringBuilder sb = new StringBuilder();
  14.         sb.append(“username:”).append(intent.getStringExtra(EXTRA_USERNAME)).append(“\n”);
  15.         sb.append(“password:”).append(intent.getStringExtra(EXTRA_PASSWORD));
  16.         setContentView(R.layout.act_home);
  17.         TextView loginContentView = (TextView) findViewById(R.id.login_content);
  18.         loginContentView.setText(sb.toString());
  19.     }
  20. }
HomeActivity的布局文件
Xml代码  收藏代码
  1. <LinearLayout
  2.     xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:layout_width=“fill_parent”
  4.     android:layout_height=“fill_parent”
  5. >
  6.     <TextView
  7.         android:id=“@+id/login_content”
  8.         android:layout_width=“fill_parent”
  9.         android:layout_height=“wrap_content”
  10.         android:layout_gravity=“center_vertical”
  11.         android:gravity=“center”
  12.         android:textColor=“#EEE”
  13.         android:textStyle=“bold”
  14.         android:textSize=“25sp” />
  15. </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测试用例
Java代码  收藏代码
  1. public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
  2.     private static final String TAG = “=== MainActivityTest”;
  3.     private Instrumentation mInstrument;
  4.     private MainActivity mActivity;
  5.     private View mToLoginView;
  6.   //必不可少的东西, 不要忘记
  7.     public MainActivityTest() {
  8.         super(“yuan.activity”, MainActivity.class);
  9.     }
  10.     @Override
  11.     public void setUp() throws Exception {
  12.         super.setUp();
  13.         mInstrument = getInstrumentation();
  14.         // 启动被测试的Activity
  15.         mActivity = getActivity();
  16.         mToLoginView = mActivity.findViewById(yuan.activity.R.id.to_login);
  17.     }
  18.     public void testPreConditions() {
  19.         // 在执行测试之前,确保程序的重要对象已被初始化
  20.         assertTrue(mToLoginView != null);
  21.     }
  22.     //mInstrument.runOnMainSync(new Runnable() {
  23.     //  public void run() {
  24.     //      mToLoginView.requestFocus();
  25.     //      mToLoginView.performClick();
  26.     //  }
  27.     //});
  28.     @UiThreadTest
  29.     public void testToLogin() {
  30.         // @UiThreadTest注解使整个方法在UI线程上执行,等同于上面注解掉的代码
  31.         mToLoginView.requestFocus();
  32.         mToLoginView.performClick();
  33.     }
  34.     @Suppress
  35.     public void testNotCalled() {
  36.         // 使用了@Suppress注解的方法不会被测试
  37.         Log.i(TAG, “method ‘testNotCalled’ is called”);
  38.     }
  39.     @Override
  40.     public void tearDown() throws Exception {
  41.         super.tearDown();
  42.     }
  43. }
LoginActivityTest测试用例
Java代码  收藏代码
  1. public class LoginActivityTest extends ActivityInstrumentationTestCase2<LoginActivity> {
  2.     private static final String TAG = “=== LoginActivityTest”;
  3.     private Instrumentation mInstrument;
  4.     private LoginActivity mActivity;
  5.     private EditText mUsernameView;
  6.     private EditText mPasswordView;
  7.     private View mSubmitView;
  8.     private View mResetView;
  9.     public LoginActivityTest() {
  10.         super(“yuan.activity”, LoginActivity.class);
  11.     }
  12.     @Override
  13.     public void setUp() throws Exception {
  14.         super.setUp();
  15.         /*
  16.          *  要向程序发送key事件的话,必须在getActivity之前调用该方法来关闭touch模式
  17.          * 否则key事件会被忽略
  18.          */
  19.         setActivityInitialTouchMode(false);
  20.         mInstrument = getInstrumentation();
  21.         mActivity = getActivity();
  22.         Log.i(TAG, “current activity: “ + mActivity.getClass().getName());
  23.         mUsernameView = (EditText) mActivity.findViewById(yuan.activity.R.id.username);
  24.         mPasswordView = (EditText) mActivity.findViewById(yuan.activity.R.id.password);
  25.         mSubmitView = mActivity.findViewById(yuan.activity.R.id.submit);
  26.         mResetView = mActivity.findViewById(yuan.activity.R.id.reset);
  27.     }
  28.     public void testPreConditions() {
  29.         assertTrue(mUsernameView != null);
  30.         assertTrue(mPasswordView != null);
  31.         assertTrue(mSubmitView != null);
  32.         assertTrue(mResetView != null);
  33.     }
  34.     public void testInput() {
  35.         input();
  36.         assertEquals(“yuan”, mUsernameView.getText().toString());
  37.         assertEquals(“1123”, mPasswordView.getText().toString());
  38.     }
  39.     public void testSubmit() {
  40.         input();
  41.         mInstrument.runOnMainSync(new Runnable() {
  42.             public void run() {
  43.                 mSubmitView.requestFocus();
  44.                 mSubmitView.performClick();
  45.             }
  46.         });
  47.     }
  48.     public void testReset() {
  49.         input();
  50.         mInstrument.runOnMainSync(new Runnable() {
  51.             public void run() {
  52.                 mResetView.requestFocus();
  53.                 mResetView.performClick();
  54.             }
  55.         });
  56.         assertEquals(“”, mUsernameView.getText().toString());
  57.         assertEquals(“”, mPasswordView.getText().toString());
  58.     }
  59.     @Override
  60.     public void tearDown() throws Exception {
  61.         super.tearDown();
  62.     }
  63.     private void input() {
  64.         mActivity.runOnUiThread(new Runnable() {
  65.             public void run() {
  66.                 mUsernameView.requestFocus();
  67.             }
  68.         });
  69.         // 因为测试用例运行在单独的线程上,这里最好要
  70.         // 同步application,等待其执行完后再运行
  71.         mInstrument.waitForIdleSync();
  72.         sendKeys(KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_U,
  73.                 KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_N);
  74.         // 效果同上面sendKeys之前的代码
  75.         mInstrument.runOnMainSync(new Runnable() {
  76.             public void run() {
  77.                 mPasswordView.requestFocus();
  78.             }
  79.         });
  80.         sendKeys(KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_1,
  81.                 KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3);
  82.     }
  83. }
HomeActivityTest测试用例
Java代码  收藏代码
  1. public class HomeActivityTest extends ActivityUnitTestCase<HomeActivity> {
  2.     private static final String TAG = “=== HomeActivityTest”;
  3.     private static final String LOGIN_CONTENT = “username:yuan\npassword:1123”;
  4.     private HomeActivity mHomeActivity;
  5.     private TextView mLoginContentView;
  6.     public HomeActivityTest() {
  7.         super(HomeActivity.class);
  8.     }
  9.     @Override
  10.     public void setUp() throws Exception {
  11.         super.setUp();
  12.         Intent intent = new Intent();
  13.         intent.putExtra(HomeActivity.EXTRA_USERNAME, “yuan”);
  14.         intent.putExtra(HomeActivity.EXTRA_PASSWORD, “1123”);
  15.         // HomeActivity有extra参数,所以我们需要以intent来启动它
  16.         mHomeActivity = launchActivityWithIntent(“yuan.activity”, HomeActivity.class, intent);
  17.         mLoginContentView = (TextView) mHomeActivity.findViewById(yuan.activity.R.id.login_content);
  18.     }
  19.     public void testLoginContent() {
  20.         assertEquals(LOGIN_CONTENT, mLoginContentView.getText().toString());
  21.     }
  22.     @Override
  23.     public void tearDown() throws Exception {
  24.         super.tearDown();
  25.     }
  26. }
接下来是运行测试用例,首先我们需要把要测试的程序安装到模拟器或真机上 运行测试用例,查看运行结果 这里仅仅讲了使用eclipse来进行单元测试,当然也是可以在命令行中进行单元测试的,但既然有eclipse这种图形界面的工具,就不再折腾什么命令行了。 还有就是测试用例也可以直接创建在源程序中(即源代码和测试代码放在一个项目中),具体怎么做的话google一些吧,就是把测试时涉及的一些Manifest元素移到源码工程的Manifest中等
]]>