什麼是單元測試?(摘自百度百科)
單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。對於單元測試中單元的含義,一般來說,要根據實際情況去判定其具體含義,如C語言中單元指一個函數,Java裏單元指一個類,圖形化的軟件中可以指一個窗口或一個菜單等。總的來說,單元就是人爲規定的最小的被測功能模塊。單元測試是在軟件開發過程中要進行的最低級別的測試活動,軟件的獨立單元將在與程序的其他部分相隔離的情況下進行測試。
單元測試的好處?
- 大幅度提高後期測試和維護成本。
- 可以寫出高質量的代碼,而且還能提高編程水平。
- 最重要的是思維上和意識上的提升。
針對Android:
每次修改代碼後需要重新編譯,對於複雜的App,可能花費幾分鐘。極大浪費了時間,因此爲了減少bug,提高代碼質量,單元測試是十分被需要的。
單元測試要點
- 單元測試是編寫測試代碼,用來檢測特定的、明確的、細顆粒的功能。單元測試並不一定保證程序功能是正確的,更不保證整體業務是準備的。
- 單元測試不僅僅用來保證當前代碼的正確性,更重要的是用來保證代碼修復、改進或重構之後的正確性。
1. 接口功能測試
- 用來保證接口功能的正確性。
2. 局部數據結構測試(不常用)
- 用來保證接口中的數據結構是正確的
- 比如變量有無初始值
3. 邊界條件測試
- 變量是否溢出
- 變量沒有賦值(即爲NULL)
- 變量是數值(或字符)
- 主要邊界:最小值,最大值,無窮大(對於DOUBLE等)
- 溢出邊界(期望異常或拒絕服務):最小值-1,最大值+1
- 臨近邊界:最小值+1,最大值-1
一些例子
- 空字符串
- 空集合
- 調整次序:升序、降序
- 變量有規律,比如對於Math.sqrt,給出n^2-1,和n^2+1的邊界
4. 獨立執行通路測試
- 保證每一條代碼,每個分支都經過測試
代碼覆蓋率
- 語句覆蓋:保證每一個語句都執行到了
- 判定覆蓋(分支覆蓋):保證每一個分支都執行到
- 條件覆蓋:保證每一個條件都覆蓋到true和false(即if、while中的條件語句)
- 路徑覆蓋:保證每一個路徑都覆蓋到
Android中的單元測試
舉例,來測試下面的Activity,在EditText中輸入字符,然後點擊按鈕,在TextView顯示輸入的字符。
public class FirstActivity extends Activity {
private static final String TAG = "FirstActivity";
@Bind(R.id.first_et)
EditText mEt;
@Bind(R.id.first_tv)
TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
ButterKnife.bind(this);
Log.d(TAG,"onCreate First");
}
@OnClick(R.id.first_btn)
public void setTextToView() {
mTv.setText(mEt.getText().toString());
}
//....
}
1.搭建測試環境
AndroidManifest.xml中添加權限與配置
在Application外加入
<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.zero.androidtranningdemo" />
在Application內加入
<uses-library android:name="android.test.runner" />
之後所使用的測試庫是自帶的。
2. 編寫測試用例(androidTest裏)
建議與上面文件夾一一對應
package com.zero.androidtranningdemo.activities;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.test.TouchUtils;
import android.test.UiThreadTest;
import android.test.ViewAsserts;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.zero.androidtranningdemo.R;
/**
* Created by zero on 15-9-21.
*/
public class FirstActivityTest extends ActivityInstrumentationTestCase2<FirstActivity> {
// 構造函數是由測試用的Runner調用,用於初始化測試類的。
public FirstActivityTest() {
super(FirstActivity.class);
}
private Instrumentation mInstrumentation; // 我們可以將它理解爲一種沒有圖形界面的,具有啓動能力的,用於監控其他類(用Target Package聲明)的工具類
private FirstActivity mActivity;
private TextView mTv;
private EditText mEt;
private Button mBtn;
/**
* setUp()方法是由測試Runner在其他測試方法開始前運行的。(必要)
* 1.調用父類構造函數,這是JUnit要求的。
* 2.初始化測試數據集的狀態,具體而言:
* a.定義保存測試數據及狀態的實例變量
* b.創建並保存正在測試的Activity的引用實例。
* c.獲得想要測試的Activity中任何UI組件的引用。
*
* @throws Exception
*/
@Override
protected void setUp() throws Exception {
super.setUp();
// 把touch mode設置爲真可以防止在執行編寫的測試方法時,人爲的UI操作獲取到控件的焦點
// 確保在調用getActivity()方法前調用了setActivityInitialTouchMode
// setActivityInitialTouchMode(true);
mInstrumentation = getInstrumentation();
mActivity = getActivity();
mTv = (TextView) mActivity.findViewById(R.id.first_tv);
mEt = (EditText) mActivity.findViewById(R.id.first_et);
mBtn = (Button) mActivity.findViewById(R.id.first_btn);
}
/**
* 測試前提,檢查測試數據集的設置是否正確,以及我們想要測試的對象是否已經正確地初始化。
*/
public void testPreconditions() {
assertNotNull("mActivity is null", mActivity);
assertNotNull("mTv is null", mTv);
assertNotNull("mEt is null", mEt);
assertNotNull("mBtn is null", mBtn);
}
/**
* 編寫的測試方法
*/
// @UiThreadTest 可用於子線程處理
@MediumTest
public void testClickBtn() {
final String str = "Hello";
// 同步處理
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
mEt.setText(str); // 設置測試數據
mBtn.requestFocus(); // 得到焦點
mBtn.performClick(); // 模擬點擊事件
assertEquals(str, mTv.getText().toString()); // 判斷結果
}
});
// 如果不是Btn,其它View的點擊可使用TouchUtils
// TouchUtils.clickView(this, mBtn);
}
/**
* 注意:TouchUtils輔助類提供與應用程序交互的方法可以方便進行模擬觸摸操作。
* 我們可以使用這些方法來模擬點擊,輕敲,或應用程序屏幕拖動View。
* <p/>
* 警告:TouchUtils方法的目的是將事件安全地從測試線程發送到UI線程。
* 我們不可以直接在UI線程或任何標註@UIThread的測試方法中使用TouchUtils這樣做可能會增加錯誤線程異常。
*/
@SmallTest
public void testBtnLayout() {
final View decorView = mActivity.getWindow().getDecorView(); // 獲取最上層的ViewGroup(FrameLayout)的引用
ViewAsserts.assertOnScreen(decorView, mBtn); // 檢測一個view是否包含在Activity的根視圖View中
}
/**
* @SmallTest 標誌該測試方法是小型測試的一部分。
* @MediumTest 標誌該測試方法是中等測試的一部分。
* @LargeTest 標誌該測試方法是大型測試的一部分。
* 通常情況下,如果測試方法只需要幾毫秒的時間,那麼它應該被標記爲@SmallTest,
* 長時間運行的測試(100毫秒或更多)通常被標記爲@MediumTest或@LargeTest,
* 這主要取決於測試訪問資源在網絡上或在本地系統
*/
/**
* 垃圾清理與資源回收(非必要)
* @throws Exception
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
}
}
3.跑起來試試吧
選定該文件,右鍵 -》 Run “FirstAvtivityTest”
參考閱讀:
http://developer.android.com/intl/zh-cn/training/activity-testing/preparing-activity-testing.html (官網文檔-英)
http://hukai.me/android-training-course-in-chinese/testing/activity-testing/activity-basic-testing.html (文檔中文翻譯)
http://baike.baidu.com/link?url=AQZm7w1QiYYnV64-oPjmi1xS9PyPhI3WAJW8j080EawuhFGbuWbLeWKmU9MLYflGsryjsRNe8ETf1xUwGGnyXK
http://www.cnblogs.com/AloneSword/p/4109407.html
http://www.cnblogs.com/tianzhijiexian/p/4296055.html
http://yelinsen.iteye.com/blog/977736
http://www.oschina.net/question/54100_27061