Android单元测试(四):Robolectric框架的使用

来源:互联网 发布:淘宝助理5.8使用教程 编辑:程序博客网 时间:2024/05/22 06:26

学习了前三篇有关单元测试的内容,你会发现好像和Android没有什么关系,都是Java。因为文章中的例子并没有使用到Android中所特有的组件和方法。之所以没有涉及,是因为我们使用的Android的方法依赖android.jar包。而这些方法都是stub的,没有具体的实现。比如常用的方法TextUtils.isEmpty()

这里写图片描述

开发工具提供了我们开发和编译的JVM环境,要运行app需要Dalvik或ART环境。而单元测试的是一个运行过程,我们没有相应的环境,所以运行依赖android.jar包的方法时将会抛出RuntimeException("stub!")。那么怎么办呢?使用Robolectric可以解决此类问题,它通过实现一套JVM能运行的Android代码,从而做到脱离Android运行环境进行测试。

1.Robolectric配置

build.gradle配置:

testCompile "org.robolectric:robolectric:3.5.1"testCompile 'org.robolectric:shadows-support-v4:3.4-rc2'

这里有几个注意点:

1.最近Studio升级到3.0后,执行单元测试时会有Resources$NotFoundException问题。 暂时的办法是使用gradle版本为3.0 以下。

dependencies {        classpath 'com.android.tools.build:gradle:2.3.3'    }

2.如果在运行测试方法过程中遇见找不到AndroidManifest文件时,可以设置Working directory的值为$MODULE_DIR$

这里写图片描述

这里写图片描述

2.Robolectric使用

@RunWith(RobolectricTestRunner.class)@Config(constants = BuildConfig.class, sdk = 23)public class MainActivityTest {}

指定测试运行器为RobolectricTestRunner。通过@Config指定constants = BuildConfig.class。这样默认的配置以我们项目中的为准,当然你可以通过@Config单独去设置以下配置。

这里写图片描述

目前Robolectric最高支持sdk版本为23。

1.日志输出

之前几篇文章中,我们的日志输出都是采用System.out.print(),但是在Android开发中,我们使用Log会更多。但是单元测试中无法输出Log信息。这时我们可以使用ShadowLog

    @Before    public void setUp(){        //输出日志        ShadowLog.stream = System.out;    }

这样 Log日志都将输出在控制面板中。

2.创建Activity

@RunWith(RobolectricTestRunner.class)@Config(constants = BuildConfig.class, sdk = 23)public class MainActivityTest {    private MainActivity mainActivity;    @Before    public void setUp(){        mainActivity = Robolectric.setupActivity(MainActivity.class);    }    /**     * 创建Activity测试     */    @Test    public void testMainActivity() {        assertNotNull(mainActivity);    }

Robolectric.setupActivity返回的时候,默认会调用Activity的生命周期: onCreate -> onStart -> onResume。

3.Activity跳转验证

功能代码:

    public void jump(View view){        startActivity(new Intent(this, LoginActivity.class));    }

测试代码:

    @Test    public void testJump() throws Exception {        // 触发按钮点击        mJumpBtn.performClick();        // 获取对应的Shadow类        ShadowActivity shadowActivity = Shadows.shadowOf(mainActivity);        // 借助Shadow类获取启动下一Activity的Intent        Intent nextIntent = shadowActivity.getNextStartedActivity();        // 校验Intent的正确性        assertEquals(nextIntent.getComponent().getClassName(), LoginActivity.class.getName());    }

4.验证Toast

功能代码:

    public void showToast(View view){        Toast.makeText(this,"Hello UT!",Toast.LENGTH_LONG).show();    }

测试代码:

    @Test    public void testToast() throws Exception {        Toast toast = ShadowToast.getLatestToast();        // 判断Toast尚未弹出        assertNull(toast);        mToastBtn.performClick();        toast = ShadowToast.getLatestToast();        // 判断Toast已经弹出        assertNotNull(toast);        // 获取Shadow类进行验证        ShadowToast shadowToast = Shadows.shadowOf(toast);        assertEquals(Toast.LENGTH_LONG, shadowToast.getDuration());        assertEquals("Hello UT!", ShadowToast.getTextOfLatestToast());    }

5. 验证Dialog

功能代码:

    public void showDialog(View view){        AlertDialog alertDialog = new AlertDialog.Builder(this)                .setMessage("Hello UT!")                .setTitle("提示")                .create();        alertDialog.show();    }

测试代码:

    @Test    public void testDialog() throws Exception {        AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();        // 判断Dialog尚未弹出        assertNull(dialog);        mDialogBtn.performClick();        dialog = ShadowAlertDialog.getLatestAlertDialog();        // 判断Dialog已经弹出        assertNotNull(dialog);        // 获取Shadow类进行验证        ShadowAlertDialog shadowDialog = Shadows.shadowOf(dialog);        assertEquals("Hello UT!", shadowDialog.getMessage());    }

6.UI组件状态验证

功能代码:点击button改变CheckBox状态。

    public void inverse(View view){        checkbox.setChecked(!checkbox.isChecked());    }

测试代码:

    @Test    public void testCheckBoxState() throws Exception {        // 验证CheckBox初始状态        assertFalse(checkBox.isChecked());        // 点击按钮反转CheckBox状态        mInverseBtn.performClick();        // 验证状态是否正确        assertTrue(checkBox.isChecked());        // 点击按钮反转CheckBox状态        mInverseBtn.performClick();        // 验证状态是否正确        assertFalse(checkBox.isChecked());    }

7.验证Fragment

    @Test    public void testFragment() {        SampleFragment sampleFragment = new SampleFragment();        //添加Fragment到Activity中,会触发Fragment的onCreateView()        SupportFragmentTestUtil.startFragment(sampleFragment);        assertNotNull(sampleFragment.getView());    }

8.访问资源文件

使用RuntimeEnvironment.application可以获取到Application,方便我们使用。比如访问资源文件。

    @Test    public void testResources() {        Application application = RuntimeEnvironment.application;        String appName = application.getString(R.string.app_name);        assertEquals("AndroidUT", appName);    }

9.Activity生命周期

利用ActivityController我们可以让Activity执行相应的生命周期方法,如:

    public String getLifecycleState(){        return lifecycle;    }    @Override    protected void onStart() {        super.onStart();        lifecycle = "onStart";    }    @Override    protected void onResume() {        super.onResume();        lifecycle = "onResume";    }    @Override    protected void onPause() {        super.onPause();        lifecycle = "onPause";    }    @Override    protected void onStop() {        super.onStop();        lifecycle = "onStop";    }    @Override    protected void onRestart() {        super.onRestart();        lifecycle = "onRestart";    }    @Override    protected void onDestroy() {        super.onDestroy();        lifecycle = "onDestroy";    }

测试代码:

    @Test    public void testLifecycle() throws Exception {        // 创建Activity控制器        ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class);        MainActivity activity = controller.get();        assertNull(activity.getLifecycleState());        // 调用Activity的performCreate方法        controller.create();        assertEquals("onCreate", activity.getLifecycleState());        // 调用Activity的performStart方法        controller.start();        assertEquals("onStart", activity.getLifecycleState());        // 调用Activity的performResume方法        controller.resume();        assertEquals("onResume", activity.getLifecycleState());        // 调用Activity的performPause方法        controller.pause();        assertEquals("onPause", activity.getLifecycleState());        // 调用Activity的performStop方法        controller.stop();        assertEquals("onStop", activity.getLifecycleState());        // 调用Activity的performRestart方法        controller.restart();        // 注意此处应该是onStart,因为performRestart不仅会调用restart,还会调用onStart        assertEquals("onStart", activity.getLifecycleState());        // 调用Activity的performDestroy方法        controller.destroy();        assertEquals("onDestroy", activity.getLifecycleState());    }

10.验证BroadcastReceiver

首先是广播的实现,收到广播后我们接收name,并保存至SharedPreferences

public class MyReceiver extends BroadcastReceiver {    public static final String NAME = "name";    @Override    public void onReceive(Context context, Intent intent) {        SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();        String name = intent.getStringExtra(NAME);        editor.putString(NAME, name);        editor.apply();    }}

AndroidManifest.xml中注册:

     <receiver          android:name=".broadcast.MyReceiver"          android:enabled="true"          android:exported="false">          <intent-filter>              <action android:name="com.zl.weilu.androidut" />          </intent-filter>     </receiver>

测试代码:

@RunWith(RobolectricTestRunner.class)@Config(constants = BuildConfig.class, sdk = 23)public class MyReceiverTest{    private final String action = "com.zl.weilu.androidut";    @Test    public void testRegister() throws Exception {        ShadowApplication shadowApplication = ShadowApplication.getInstance();        Intent intent = new Intent(action);        // 验证是否注册了相应的Receiver        assertTrue(shadowApplication.hasReceiverForIntent(intent));    }    @Test    public void testReceive() throws Exception {        //发送广播        Intent intent = new Intent(action);        intent.putExtra(MyReceiver.NAME, "AndroidUT");        MyReceiver myReceiver = new MyReceiver();        myReceiver.onReceive(RuntimeEnvironment.application, intent);        //验证广播的处理逻辑是否正确        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application);        assertEquals( "AndroidUT", preferences.getString(MyReceiver.NAME, ""));    }}

11.Service验证

使用方式和Activity类似:

public class MyService extends Service {    private final String TAG = "MyService";    @Nullable    @Override    public IBinder onBind(Intent intent) {        Log.d(TAG, "onBind");        return null;    }    @Override    public void onCreate() {        super.onCreate();        Log.d(TAG, "onCreate");    }    @Override    public boolean onUnbind(Intent intent) {        Log.d(TAG, "onUnbind");        return super.onUnbind(intent);    }    @Override    public void onDestroy() {        super.onDestroy();        Log.d(TAG, "onDestroy");    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        Log.d(TAG, "onStartCommand");        return super.onStartCommand(intent, flags, startId);    }}

测试代码:

@RunWith(RobolectricTestRunner.class)@Config(constants = BuildConfig.class, sdk = 23)public class MyServiceTest {    private ServiceController<MyService> controller;    private MyService mService;    @Before    public void setUp() throws Exception {        ShadowLog.stream = System.out;        controller = Robolectric.buildService(MyService.class);        mService = controller.get();    }    /**     * 控制Service生命周期进行验证     *     * @throws Exception     */    @Test    public void testServiceLifecycle() throws Exception {        controller.create();        controller.startCommand(0, 0);        controller.bind();        controller.unbind();        controller.destroy();    }}

测试结果:

这里写图片描述

3.Shadow的使用

我们一开始说过,Robolectric通过实现一套JVM能运行的Android代码,从而做到脱离Android运行环境进行测试。实际上使用的就是Shadow,比如之前例子中的ShadowActivityShadowLogShadowAlertDialog等。Shadow在实现的同时,帮我拓展了原本的Android代码,实现了许多便于测试的功能,比如例子中用到的 getNextStartedActivityShadowToast.getTextOfLatestToast()ShadowAlertDialog.getLatestAlertDialog()。当然不止这么多,Robolectric提供了大量的Shadow方便我们的使用。

原始Person

public class Person {    private String name;    private int sex;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getSex() {        return sex;    }    public void setSex(int sex) {        this.sex = sex;    }    public int getAge(){        return 11;    }    public String eat(String food){        return food;    }}

创建PersonShadow对象ShadowPerson,实现与原始类方法名一致的方法,Shadow方法需用@Implementation进行注解。

@Implements(Person.class)public class ShadowPerson {    @Implementation    public String getName() {        return "AndroidUT";    }    @Implementation    public int getSex() {        return 0;    }    @Implementation    public int getAge(){        return 18;    }}

Config注解中添加shadows参数,指定对应的Shadow

@RunWith(RobolectricTestRunner.class)@Config(constants = BuildConfig.class,        sdk = 23,        shadows = {ShadowPerson.class})public class ShadowTest {    @Before    public void setUp() {        ShadowLog.stream = System.out;    }    @Test    public void testShadowShadow(){        Person person = new Person();        //实际上调用的是ShadowPerson的方法        Log.d("test", person.getName());        Log.d("test", String.valueOf(person.getAge()));        Log.d("test", String.valueOf(person.getSex()));        //获取Person对象对应的Shadow对象        ShadowPerson shadowPerson = extract(person);        assertEquals("AndroidUT", shadowPerson.getName());        assertEquals(18, shadowPerson.getAge());        assertEquals(0, person.getSex());    }}

测试结果:

这里写图片描述

本篇所有代码已上传至Github。希望大家多多点赞支持!

4.参考

  • Robolectric使用教程
原创粉丝点击