測試相關(guān)資源
讓開發(fā)自動化: 用 Eclipse 插件提高代碼質(zhì)量
http://www.ibm.com/developerworks/cn/java/j-ap01117/index.html
代碼測試覆蓋率介紹:
http://www.cnblogs.com/coderzh/archive/2009/03/29/1424344.html
學(xué)習(xí)android單元測試時遇到的一些問題:
1.開始以為單元測試一定要從程序的launch Activity,一步一步的跳轉(zhuǎn)到所要測試的Activity能對其進(jìn)行測試。
但實際上,我們可以從任意一個activity開始,對任意一個activity進(jìn)行測試。
2.在運行單元測試之前,一定要先將要測試的程序安裝到模擬器或真機上。
junit相關(guān)
android中的測試框架是擴展的junit3,所以在學(xué)習(xí)android中的單元測試簽,可以先熟悉下junit3的使用,junit3要學(xué)習(xí)的東西應(yīng)該并不多,就幾頁紙的東西。入門可以參考這個:
http://android.blog.51cto.com/268543/49994
android單元測試框架中涉及的注解
@Suppress 可以用在類或這方法上,這樣該類或者該方法就不會被執(zhí)行
@UiThreadTest 可以用在方法上,這樣該方法就會在程序的ui線程上執(zhí)行
@LargeTest, @MediumTest, @SmallTest 用在方法上,標(biāo)記所屬的測試類型,主要是用于單獨執(zhí)行其中的某一類測試時使用。具體參考InstrumentationTestRunner類的文檔。
@Smoke 具體用法還不清楚
android單元測試框架中涉及的一些類的uml
接下來我們以demo project來講解如何使用android中的單元測試
主要包括了三個activity:
MainActivity:僅包含一個button,點擊后就可以進(jìn)入LoginActivity
LoginActivity:可以輸入username, password,然后點擊submit的話可進(jìn)入HomeActivity,如果點擊reset的話,輸入的內(nèi)容就會被清空
HomeActivity:在TextView中顯示LoginActivity輸入的內(nèi)容
首先我們創(chuàng)建要測試的項目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);
- }
- });
- }
- }
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./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>
<LinearLayout
xmlns:android="http://schemas./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();
- }
- });
- }
- }
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./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>
<LinearLayout
xmlns:android="http://schemas./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());
- }
- }
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./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>
<LinearLayout
xmlns:android="http://schemas./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>
程序非常簡單,接下來我們?yōu)閐emo創(chuàng)建單元測試工程demo_unittest
選擇之前創(chuàng)建的工程demo,然后eclipse會自動幫我們設(shè)定api level,包名等。(測試用例的包名一般就是在要測試類的包名后加上test)
創(chuàng)建完后eclipse會自動為我們創(chuàng)建好所需的目錄,Manifest.xml文件
接下來就是為要測試的類編寫測試用例。關(guān)于要用哪個測試用例類,在第一張UML圖中也做了簡要的說明。
ActivityInstrumentationTestCase2:主要是用于進(jìn)行activity的功能測試,和activity的交互測試,如activity間的跳轉(zhuǎn),ui的交互等。
ActivityInstrumentationTestCase:這個類現(xiàn)在已deprecated了,所以不許考慮。
SingleLaunchActivityTestCase:該測試用例僅掉用setUp和tearDown一次,而不像其它測試用例類一樣,沒調(diào)用一次測試方法就會重新調(diào)用一次setUp和tearDown。所以主要測試activity是否能夠正確處理多次調(diào)用。
ActivityUnitTestCase:主要用于測試Activity,因為它允許注入MockContext和MockApplicaton,所以可以測試Activity在不同資源和應(yīng)用下的情況。
還有Application等的測試用例比較簡單,看uml圖。如果覺得不夠詳細(xì),可以參考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();
-
- mActivity = getActivity();
- mToLoginView = mActivity.findViewById(yuan.activity.R.id.to_login);
- }
-
- public void testPreConditions() {
-
- assertTrue(mToLoginView != null);
- }
-
-
-
-
-
-
-
-
- @UiThreadTest
- public void testToLogin() {
-
- mToLoginView.requestFocus();
- mToLoginView.performClick();
- }
-
- @Suppress
- public void testNotCalled() {
-
- Log.i(TAG, "method 'testNotCalled' is called");
- }
-
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
- }
- }
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() {
// 在執(zhí)行測試之前,確保程序的重要對象已被初始化
assertTrue(mToLoginView != null);
}
//mInstrument.runOnMainSync(new Runnable() {
// public void run() {
// mToLoginView.requestFocus();
// mToLoginView.performClick();
// }
//});
@UiThreadTest
public void testToLogin() {
// @UiThreadTest注解使整個方法在UI線程上執(zhí)行,等同于上面注解掉的代碼
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();
-
-
-
-
- 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();
- }
- });
-
-
- mInstrument.waitForIdleSync();
- sendKeys(KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_U,
- KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_N);
-
-
- mInstrument.runOnMainSync(new Runnable() {
- public void run() {
- mPasswordView.requestFocus();
- }
- });
- sendKeys(KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_1,
- KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3);
- }
- }
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();
/*
* 要向程序發(fā)送key事件的話,必須在getActivity之前調(diào)用該方法來關(guān)閉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,等待其執(zhí)行完后再運行
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");
-
- 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();
- }
- }
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參數(shù),所以我們需要以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();
}
}
接下來是運行測試用例,首先我們需要把要測試的程序安裝到模擬器或真機上
運行測試用例,查看運行結(jié)果
這里僅僅講了使用eclipse來進(jìn)行單元測試,當(dāng)然也是可以在命令行中進(jìn)行單元測試的,但既然有eclipse這種圖形界面的工具,就不再折騰什么命令行了。
還有就是測試用例也可以直接創(chuàng)建在源程序中(即源代碼和測試代碼放在一個項目中),具體怎么做的話google一些吧,就是把測試時涉及的一些Manifest元素移到源碼工程的Manifest中等