Robotium框架的使用和源码解析

本文详细介绍了Robotium测试框架的使用方法,包括添加依赖、通过不同方式获取和操作控件、断言以及截图功能。同时,对Robotium的源码进行了部分解析,如getView和clickOnView的方法流程,揭示了其内部实现机制。

使用:

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-Screenshots
finishOpenedActivities()   // 关闭已经打开的所有activity
goBack() / goBackToActivity(String name)  // 点击返回键/不断的点击返回键直至返回到指定的activity
hideSoftKeyboard()  // 收起键盘
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参数指定的activity
void 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的坐标,触发点击事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值