Android单元测试

来源:互联网 发布:淘宝客服名字大全 编辑:程序博客网 时间:2024/05/29 16:33

什么是单元测试?(摘自百度百科)

单元测试(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

0 0
原创粉丝点击