Robotium框架的使用和源码解析

来源:互联网 发布:医药类软件 编辑:程序博客网 时间:2024/06/15 19:47

使用:

1、添加依赖

androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.6.3'

2、添加测试代码,例如:

@RunWith(AndroidJUnit4.class)public class MainActivityTest {    @Rule    public ActivityTestRule<MainActivity> activityTestRule =            new ActivityTestRule<>(MainActivity.class);    private Solo solo;    @Before    public void setUp() throws Exception{        // 获取activity        solo = new Solo(InstrumentationRegistry.getInstrumentation(),activityTestRule.getActivity());    }    @After    public void tearDown() throws Exception {        solo.finishOpenedActivities();  // 关闭打开的活动    }    @Test    public void testAddNote() throws Exception {        solo.clickOnView(solo.getView(R.id.test)); // 点击view    }}

robotium框架是通过模拟用户操作手机屏幕来进行测试,几个基本操作:

  1. 要知道操作控件的坐标
  2. 对控件进行模拟操作
  3. 判断操作的结果是否符合预期

控件的获取

1、通过id获取

solo.getView(R.id.test);

2、通过索引、文本获取

solo.getText(0); // 第一个Textview控件solo.getText("hello");   // 包含"hello"文字的textView控件(文字部分匹配即可)

3、通过对控件类型进行过滤获取

// 比如:一个LinearLayout中子控件都是动态添加的,就可以用这种方式获取LinearLayout parentView = (LinearLayout) solo.getView(R.id.test);ArrayList<TextView> textViews = solo.getCurrentViews(TextView.class, parentView);textViews.get(0);// getCurrentViews()方法有4个重载方法,可以指定过滤条件

控件的操作

1、点击、长按操作

clickOnView(View view) / clickLongOnView(View view)clickOnScreen(float x,float y) / clickLongOnScreen(float x,float y) 

2、输入框操作

enterText(EditText editText,String text)   // 直接对文本框赋值typeText(EditText editText,String text)  // 一个一个文本的输入clearText(EditText editText)   

3、滑动、滚动

// 从起始坐标滑动到终点坐标,通过setCount参数指定滑动的步长drag(float fromX, float toX, float fromY, float toY, int setpCount) // 滑动至屏幕的顶部/滑动至屏幕的顶部底部scrollToTop()/scrollToDown()// 向上滑一屏/向下滑一屏(不能触发ListView的加载更多的监听)scrollUp()/scrollDown()   

4、搜索和等待

sleep(int time)  // 休眠boolean searchText(String text)  // 从当前页面搜索指定文本boolean waitForView(int id)/waitForText(String text)  // 等待指定控件/文本出现boolean waitForActivity(String name)  // 等待指定的Activity出现boolean waitForLogMessage(String logMessage) // 等待指定的日志信息出现boolean waitForDialogToOpen()/waitForDialogToClose()  // 等待指定弹窗打开/关闭

Robotium中查找控件、点击控件等api都默认使用了搜索等待机制,因此非特殊情况是不用添加额外的等待操作。

5、截图及其他

takeScreenshot(String name)   // 截图,图片名为name,默认路径为/sdcard/Robotium-ScreenshotsfinishOpenedActivities()   // 关闭已经打开的所有activitygoBack() / goBackToActivity(String name)  // 点击返回键/不断的点击返回键直至返回到指定的activityhideSoftKeyboard()  // 收起键盘setActivityOrientation(int orientation)   // 设置Activity转屏方向

其他:Robotium 4.0开始支持对webView的进行测试,提供了相关的api,步骤和native测试一样,获取元素、操作、断言。

断言

1、Junit中的断言

// condition应该为true。当condition为false时,抛出Message异常void assertTrue(String message, boolean condition)// condition应该为false。当condition为true时,抛出Message异常void assertFalse(String message, boolean condition)// 直接使用例失败,并抛出message异常void fail(String message)

2、Robotium中的断言

// 当前页面是否为name参数指定的activityvoid assertCurrentActivity(String message, String name)  // 当前是否处于低内存状态void assertMemoryNowLow()  

3、android中断言的api

// 断言view是否在屏幕中assertOnSreen(View origin, View view)// 断言两个View是否底端对齐,即它们的底端y坐标是否相等assertBottomAligned(View first, View second)...

源码解析

getView获取流程:

    public View getView(int id) {        return this.getView(id, 0);    }
    public View getView(int id, int index) {        View viewToReturn = this.getter.getView(id, index);  // 这个方法下面会分析        if(viewToReturn == null) {  // 没有获取到view,断言失败            String resourceName = "";            try {                resourceName = this.instrumentation.getTargetContext().getResources().getResourceEntryName(id);            } catch (Exception var6) {                Log.d(this.config.commandLoggingTag, "unable to get resource entry name for (" + id + ")");            }            int match = index + 1;            if(match > 1) {                Assert.fail(match + " Views with id: \'" + id + "\', resource name: \'" + resourceName + "\' are not found!");            } else {                Assert.fail("View with id: \'" + id + "\', resource name: \'" + resourceName + "\' is not found!");            }        }        return viewToReturn;    }

下面看this.getter.getView(id, index)方法

    public View getView(int id, int index) {        return this.getView(id, index, 0);    }
    public View getView(int id, int index, int timeout) {        return this.waiter.waitForView(id, index, timeout);    }
    public View waitForView(int id, int index, int timeout) {        if(timeout == 0) {              timeout = Timeout.getSmallTimeout();  // 初始化的时候会设置超时时间为10s        }        return this.waitForView(id, index, timeout, false);    }
    public View waitForView(int id, int index, int timeout, boolean scroll) {        HashSet uniqueViewsMatchingId = new HashSet();        long endTime = SystemClock.uptimeMillis() + (long)timeout;          while(SystemClock.uptimeMillis() <= endTime) {                 this.sleeper.sleep();   //  Thread.sleep(500);            Iterator i$ = this.viewFetcher.getAllViews(false).iterator();  // 通过反射获取到当前页面所有的view            while(i$.hasNext()) {                View view = (View)i$.next();                Integer idOfView = Integer.valueOf(view.getId());                if(idOfView.equals(Integer.valueOf(id))) {   // 获取到控件                    uniqueViewsMatchingId.add(view);                    if(uniqueViewsMatchingId.size() > index) {                        return view;   // 返回控件                    }                }            }            if(scroll) {                this.scroller.scrollDown();            }        }        // 在endTime时间内还没获取到控件,则返回null        return null;    }

总结一下就是,通过反射获取当前页面的view,在10s内不断循环,和当前的getView传入的id配置是否相同,如果相同就返回view。

下面看一下clickOnView

    public void clickOnView(View view) {        view = this.waiter.waitForView(view, Timeout.getSmallTimeout());        this.clicker.clickOnScreen(view);    }

先看waitForView()方法

    public View waitForView(View view, int timeout) {        return this.waitForView(view, timeout, true, true);    }
    public View waitForView(View view, int timeout, boolean scroll, boolean checkIsShown) {        long endTime = SystemClock.uptimeMillis() + (long)timeout;        int retry = 0;        if(view == null) {            return null;        } else {            label39:            do {                for(; SystemClock.uptimeMillis() < endTime; this.sleeper.sleep()) {                      boolean foundAnyMatchingView = this.searcher.searchFor(view);                      if(checkIsShown && foundAnyMatchingView && !view.isShown()) {                         // 如果找到这个view,这个view没有显示,则sleep 300ms                        this.sleeper.sleepMini();                        ++retry;                        View identicalView = this.viewFetcher.getIdenticalView(view);                        if(identicalView != null && !view.equals(identicalView)) {                            view = identicalView;                        }                        continue label39;                    }                    if(foundAnyMatchingView) {  // 如果找到匹配的view则返回                        return view;                    }                    if(scroll) {                        this.scroller.scrollDown();                    }                }                return view;            } while(retry <= 5);              return view;        }    }

下面看clickOnScreen()方法

   public void clickOnScreen(View view) {        this.clickOnScreen(view, false, 0);    }
    public void clickOnScreen(View view, boolean longClick, int time) {        if(view == null) {            Assert.fail("View is null and can therefore not be clicked!");        }        float[] xyToClick = this.getClickCoordinates(view);  // 获取点击的坐标        float x = xyToClick[0];        float y = xyToClick[1];        if(x == 0.0F || y == 0.0F) {            this.sleeper.sleepMini();            try {                view = this.viewFetcher.getIdenticalView(view);  // 获取找到的view            } catch (Exception var8) {                ;            }            if(view != null) {                  xyToClick = this.getClickCoordinates(view);                x = xyToClick[0];                y = xyToClick[1];            }        }        if(longClick) {            this.clickLongOnScreen(x, y, time, view);        } else {            this.clickOnScreen(x, y, view);   // 点击view        }    }

总结一下就是获取view,然后获取view的坐标,触发点击事件。