Android單元測試

什麼是單元測試?(摘自百度百科)

單元測試(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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章