单元测试Robolectric的使用详解

来源:互联网 发布:万方数据库下载论文 编辑:程序博客网 时间:2024/06/06 03:13

Robolectric通过实现一套JVM能运行的Android代码,然后在Junit test运行的时候去截取android相关的代码调用,然后转到他们的他们实现的代码去执行这个调用的过程。不用依赖真实的 Android 环境中运行(模拟器或者真机)

Robolectric主要适用于UI的测试,比如Activity,Fragment,一些页面操作的测试场景,采用Shadow的方式对Android中的组件进行模拟测试,从而实现Android单元测试Robolectric正好弥补了Mockito的不足,两者结合使用是最完美的。

现在我们就开启Robolectric的测试之旅吧!

需要测试的类:

public class QXUnitTestActvity extends MSBaseActivity {
@BindView(R.id.btn_changge) Button mbtn;
@BindView(R.id.textView) TextView mTv;
public String name;
private QxBroacastReceiver receiver;
private Handler mHandler;
public int mCount=0;

@Overridepublic void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_unit_test);    ButterKnife.bind(this);    name="create";    registerBroacast();    mHandler=new Handler(getMainLooper());}private void registerBroacast() {    IntentFilter filter=new IntentFilter();    filter.addAction("com.wr.qx.ok");   receiver= new QxBroacastReceiver();    registerReceiver(receiver,filter);}@TargetApi(Build.VERSION_CODES.LOLLIPOP)@OnClick(R.id.btn_changge) void changeText(){    mTv.setText("我被修改了");    if(name.equals("jump")){        startActivity(new Intent(this,MSCurrentInvestActivity.class));    }    mHandler.postDelayed(new Runnable() {        @Override        public void run() {            mCount=6;        }    },500);    Toast.makeText(this,"hello",Toast.LENGTH_LONG).show();    Dialog dialog=new Dialog(this);    TextView tv=new TextView(this);    tv.setText("我是一个dialog");    dialog.setContentView(tv);    dialog.show();    dialog.setCancelable(true);    final AlertDialog alertDialog=new AlertDialog.Builder(this)            .setIcon(getDrawable(R.mipmap.ic_launcher))            .setMessage("hello")            .setNegativeButton("OK", new DialogInterface.OnClickListener() {                @Override                public void onClick(DialogInterface dialogInterface, int i) {                    dialogInterface.dismiss();                }            }).create();    alertDialog.show();}@Overrideprotected void onStart() {    super.onStart();    name="start";}@Overrideprotected void onResume() {    super.onResume();    name="jump";}@Overrideprotected void onPause() {    super.onPause();    name="pause";}@Overrideprotected void onStop() {    super.onStop();    name="stop";}@Overrideprotected void onDestroy() {    super.onDestroy();    name="destroy";    unregisterReceiver(receiver);}@Overridepublic void onBackPressed() {    super.onBackPressed();    name="onBackPressed";}

}

测试类:

@RunWith(RobolectricTestRunner.class)//表示用RobolectricTestRunner来跑测试
@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP)//基本的配置信息

public class QXUnitTestActvityTest {
private QXUnitTestActvity mActvity;
private Button mbtn;
private TextView mTv;
private CountDownLatch runFlag;

@Beforepublic void setUp() {    //加上这么一句话无论是测试代码中的log或者被测试中的log都会在控制台输出的    ShadowLog.stream = System.out;    Log.d("Test_log", "开启了log日志");    //相当于启动了actvity 经过了 oncreate,onStart和onResume这三个生命周期    //一般只是在启动activiy的时候调用    mActvity = Robolectric.setupActivity(QXUnitTestActvity.class);    mbtn = (Button) mActvity.findViewById(R.id.btn_changge);    mTv = (TextView) mActvity.findViewById(R.id.textView);}@Afterpublic void tearDown() {    mActvity = null;    mbtn = null;    mTv = null;}//测试启动初始化是否成功@Testpublic void testInit() {    Assert.assertEquals("actvity packageName is error", "com.ran.qxlinechart", mActvity.getPackageName());    Assert.assertNotNull("button is null", mbtn);    Assert.assertNotNull("testView is null", mTv);    Assert.assertEquals("textView text is error", "text", mTv.getText().toString());}//测试点击事件是否相应@Testpublic void testClickButton() {    mbtn.performClick();    Assert.assertEquals("textView text is error", "我被修改了", mTv.getText().toString());}//测试Actvity生命周期的调用@Testpublic void testLifecycleMethods() throws InterruptedException {    ActivityController<QXUnitTestActvity> controller = Robolectric.buildActivity(QXUnitTestActvity.class);    QXUnitTestActvity actvity = (QXUnitTestActvity) controller.get();    //所有的生命周期都可以模仿但是这里的actviy一定是controller里get得到的,    // 不能用mActvity因为全局的那个是tartUp的更生命周期的这个不是一个    controller.create();    Assert.assertEquals("create", actvity.name);    controller.start();    Assert.assertEquals("start", actvity.name);    controller.resume();    Assert.assertEquals("jump", actvity.name);    controller.pause();    Assert.assertEquals("pause", actvity.name);    controller.stop();    Assert.assertEquals("stop", actvity.name);    controller.destroy();    Assert.assertEquals("destroy", actvity.name);    mbtn.performClick();    Assert.assertEquals("textView text is error", "我被修改了", mTv.getText().toString());}//Fragment是继承app.Fragment 生命周期@Testpublic void testFragmentLifecyle() {    FragmentController<QxFramentApp> controller = Robolectric.buildFragment(QxFramentApp.class);    QxFramentApp framentApp = controller.get();    controller.create();////这样一步就走到了onCreatView不仅仅走了onCraete    Assert.assertEquals("createView", framentApp.name);    controller.start();    Assert.assertEquals("start", framentApp.name);    controller.resume();    Assert.assertEquals("resume", framentApp.name);    controller.destroy();    Assert.assertEquals("destroy", framentApp.name);}//测试Actvity的跳转@Testpublic void testStartActiviy() {    mbtn.performClick();    Assert.assertEquals("textView text is error", "我被修改了", mTv.getText().toString());

// mActvity.name=”jump”; 使用这个是错误的

// ActivityController controller=Robolectric.buildActivity(QXUnitTestActvity.class).create().start();//已经走到resume了
//
// QXUnitTestActvity actvity= (QXUnitTestActvity) controller.get();
// actvity.name=”jump”;也是行不通的
//只能在自己的生命周期里更改name
ShadowApplication shadowApplication = ShadowApplication.getInstance();
//表示shadowApplication.getNextStartedActivity()不能是null(notNullValue())如果是null就会报错,然后提示”next actvity is null shoud not jump”
// Assert.assertThat(“next actvity is null shoud not jump”,shadowApplication.getNextStartedActivity(),is(notNullValue()));

    //或许用下面这个来验证 用上面注释的一条验证最简单    Intent expIntent = new Intent(mActvity, MSCurrentInvestActivity.class);    Intent act = shadowApplication.getNextStartedActivity();

//如果两者不同就会报错提示
Assert.assertEquals(“不是从mActvity跳转到MSCurrentInvestActivity或者是携带的数据不一样”, expIntent.getComponent(), act.getComponent());
}

//Fragment是继承v4的 测试Fragment添加初始化是否成功@Testpublic void testFragment() {    QxFragment fragment = new QxFragment();    //把Fragment添加到Activity中,并且启动Fragment,触发他的生命周期函数OnCreateView    SupportFragmentTestUtil.startFragment(fragment);    Bundle bundle = fragment.getArguments();    Assert.assertThat("fragment not start success", fragment.getView(), is(notNullValue()));}//测试模拟延迟任务的执行@Testpublic void testDelayTask() {    Assert.assertFalse(mActvity.mCount == 6);//此时没有走线程呢所以不会是6    mbtn.performClick();    //利用Looper的影子来安排任务的执行,这也符合Android的架构情况    ShadowLooper.runUiThreadTasksIncludingDelayedTasks();//等待UI delay的Tasks运行完毕然后再往下执行    Assert.assertTrue(mActvity.mCount == 6);}/** * ShadowXX其实就是这个XX的影子,代理或者是复制品  有N个ShadowXX,比如ShadowApplication,ShadowActvity,ShadowToast * ShadowListView,ShadowLinearLayout,ShadowBitmap等,也就是说只要Android有的类几乎都有相应的影子Shadow。也就是说只要我们 * 有了一个类的Shadow我们就相当于有了这个类,而且功能更强大,毕竟是经过了,包装的了,创建一个类的Shadow也很容易: * ShadowActivity shadowActivity= Shadows.shadowOf(mActvity); * View vie= shadowActivity.getContentView(); */@Testpublic void testBroadCast() {    ShadowApplication shadowApplication = ShadowApplication.getInstance();    Intent intent = new Intent("com.wr.qx.ok");    Assert.assertTrue(shadowApplication.hasReceiverForIntent(intent));//是否已经注册action是"com.wr.qx.ok"的广播    //模拟注册广播后 激发广播后的操作    QxBroacastReceiver receiver = new QxBroacastReceiver();    receiver.onReceive(RuntimeEnvironment.application, intent);    //检查接到广播后是否启动了context.startActivity(new Intent(context, MSCurrentInvestActivity.class));    Assert.assertThat("没有收到广播 然后启动actvity", shadowApplication.getNextStartedActivity(), is(notNullValue()));}@Testpublic void testDialog() {    mbtn.performClick();    Dialog dialog = ShadowDialog.getLatestDialog();    Assert.assertNotNull("dialog not creat", dialog);    dialog.dismiss();    Assert.assertTrue(!dialog.isShowing());}@Testpublic void testAlertDialog() {    mbtn.performClick();    AlertDialog alertDialog = ShadowAlertDialog.getLatestAlertDialog();    Assert.assertNotNull("dialog not creat", alertDialog);    alertDialog.dismiss();    Assert.assertTrue(!alertDialog.isShowing());}@Testpublic void testToast() {    mbtn.performClick();    Assert.assertNotNull("not invote Toast", ShadowToast.getLatestToast());    String toastText = ShadowToast.getTextOfLatestToast();    Assert.assertEquals("toast error", "hello", toastText);}//测试模拟资源文件的获取@TargetApi(Build.VERSION_CODES.LOLLIPOP)@Testpublic void testReadResouce() {    Application application = RuntimeEnvironment.application;    Drawable drawable = application.getDrawable(R.mipmap.ic_launcher);}

}

综上:Robolectric的之所以能灵活的操作UI,Actvity,Fragment,Resouce,Application等几乎所有Android API中的基础类,原因就是Robolectric内部把这些分别包装成了,各自的影子类ShadowXX,然后利用这些影子类模拟原类的行为和属性,从而达到测试的目的,比如上面我们用到的ShadowApplication ,ShadowActivity,ShadowDialog等,还有就是Android的几个核心类对应的XXController比如ActivityController和FragmentController等来控制自己的生命周期方法和主要行为。

0 0