您的位置:首页 > 移动开发 > Android开发

android单元测试

2016-01-25 20:31 519 查看
测试相关资源 
让开发自动化: 用 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代码  


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的布局文件 

Xml代码  


<LinearLayout  

    xmlns:android="http://schemas.android.com/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代码 

Java代码  


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的布局文件 

Xml代码  


<LinearLayout  

    xmlns:android="http://schemas.android.com/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代码 

Java代码  


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的布局文件 

Xml代码  


<LinearLayout  

    xmlns:android="http://schemas.android.com/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>  


 

程序非常简单,接下来我们为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代码  


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() {  

        // 在执行测试之前,确保程序的重要对象已被初始化  

        assertTrue(mToLoginView != null);  

    }  

  

  

    //mInstrument.runOnMainSync(new Runnable() {  

    //  public void run() {  

    //      mToLoginView.requestFocus();  

    //      mToLoginView.performClick();  

    //  }  

    //});  

    @UiThreadTest  

    public void testToLogin() {  

        // @UiThreadTest注解使整个方法在UI线程上执行,等同于上面注解掉的代码  

        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测试用例 

Java代码  


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();  

        /* 

         *  要向程序发送key事件的话,必须在getActivity之前调用该方法来关闭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,等待其执行完后再运行  

        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测试用例 

Java代码  


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参数,所以我们需要以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();  

    }  

}  

接下来是运行测试用例,首先我们需要把要测试的程序安装到模拟器或真机上 





运行测试用例,查看运行结果 




 

这里仅仅讲了使用eclipse来进行单元测试,当然也是可以在命令行中进行单元测试的,但既然有eclipse这种图形界面的工具,就不再折腾什么命令行了。 

还有就是测试用例也可以直接创建在源程序中(即源代码和测试代码放在一个项目中),具体怎么做的话google一些吧,就是把测试时涉及的一些Manifest元素移到源码工程的Manifest中等
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: