Android平台Robotium UI测试详解

来源:互联网 发布:sql注入绕过安全狗 编辑:程序博客网 时间:2024/05/01 14:33

Android平台Robotium UI测试详解

Robotium 框架工作原理及实践

Robotium 是什么

一款面向Android端的开源自动化测试框架,,Robotium是基于Instrumentation的测试框架,器测试用例的编写框架是基于Junit的
- 优势
- 同时支持Native应用和Hybrid(混合)
- 支持黑盒测试和白盒
- 基于Instrumentation,测试用例执行速度快
- 运行时识别的是控件,测试用例更健壮
- 可以通过Maven,Gradle或Ant运行
- 可以没有项目代码,只有APK文件
- 可以截图
- 缺点
- 无法跨应用
- 代码运行在被测进程,会影响性能,无法同时监控性能

Robotium 的Native使用

关于Eclipse的使用在Robotium的GitHub主页中可以找到文档,以下均是基于AndroidStudio的

要完成对手机的模拟操作,应包含以下几个基本操作:
1. 需要知道所要控件的坐标
2. 对要操作的控件进行模拟操作
3. 判断操作完成后的结果是否符合预期

1.使用 Robotium 准备工作

  1. 在需要测试modle中添加依赖

    androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.6.3'
  2. AndroidStudio 在创建项目的时候,会默认的创建出androidTest路径,如果没有的话,需要在想要测试的module中的src文件夹下创建一个androidTest/java的包,然后配置module的build.gradle来指向它

    android {    sourceSets {    androidTest {      java.srcDirs = ['androidTest/java']    }  }}
  3. 然后就可以写测试代码了,在java中建一个和项目根包名相同的包,我们带代码都放到这里面

2.简单的例子

写一个简单的求和功能

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:orientation="vertical"    tools:context="com.lanou.chenfengyao.robotiumdemo.MainActivity">    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="horizontal">        <EditText            android:id="@+id/num1_et"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:hint="a"/>        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="+"/>        <EditText            android:id="@+id/num2_et"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:hint="b"/>        <TextView            android:id="@+id/result_tv"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="=结果"/>    </LinearLayout>    <Button        android:id="@+id/get_result_btn"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="求和"/></LinearLayout>

MainActivity代码

package com.lanou.chenfengyao.robotiumdemo;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;public class MainActivity extends AppCompatActivity {    private EditText num1Et, num2Et;    private Button getResultBtn;    private TextView resultTv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        num1Et = (EditText) findViewById(R.id.num1_et);        num2Et = (EditText) findViewById(R.id.num2_et);        getResultBtn = (Button) findViewById(R.id.get_result_btn);        resultTv = (TextView) findViewById(R.id.result_tv);        getResultBtn.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                int a = Integer.valueOf(num1Et.getText().toString());                int b = Integer.valueOf(num2Et.getText().toString());                //故意写错了                resultTv.setText("= " + a + b);            }        });    }}

可以看到点击按钮的时候就会求和,并写回TextView上,可以看到我们的求和结果是故意写错了,直接这么写会是字符串的拼接而不是求和的,我们会通过测试代码来找到这个错误

测试代码-1

在androidTest/java/包名下新建一个Java class,开始写我们的测试代码,测试代码类需要继承ActivityInstrumentationTestCase2,这是测试单独Activity时需要继承的泛型填写我们需要测试的Activity类名,接下来是代码

import android.test.ActivityInstrumentationTestCase2;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;import com.robotium.solo.Solo;/** * If there is no bug, then it is created by ChenFengYao on 2017/3/3, * otherwise, I do not know who create it either. */public class TestFirst extends ActivityInstrumentationTestCase2<MainActivity> {    private Solo solo;    public TestFirst() {        super(MainActivity.class);    }    @Override    protected void setUp() throws Exception {        super.setUp();        solo = new Solo(getInstrumentation());        getActivity();    }    public void testRun() {        EditText num1Et = (EditText) solo.getView(R.id.num1_et);        EditText num2Et = (EditText) solo.getView(R.id.num2_et);        Button resultBtn = (Button) solo.getView(R.id.get_result_btn);        TextView resultTv = (TextView) solo.getView(R.id.result_tv);        solo.enterText(num1Et,"1");        solo.enterText(num2Et,"2");        solo.clickOnView(resultBtn);        solo.sleep(200);        assertEquals("= 3",resultTv.getText().toString());    }}

这段代码我们复写了setUp方法,在setUp方法里我们初始化了Robotium中最重要的对象,Solo对象,几乎所有对于UI的操作都是针对这个Solo对象的,然后通过调用getActivity来启动MainActivity

下面 是testRun方法,这个方法并不是重写出来的,而是就写成这个名字,写成testXX的都能被识别,在testRun里,通过solo.getView方法来获得界面中我们所有需要的View元素,然后通过solo.enterText对EditText进行赋值,接下来调用button的点击事件,值得注意的是,测试代码并 不是运行在主线程中 的,所以我们没有办法直接操作view元素来进行更改ui,也正因为这样,我们在点击过后让solo等待一下,方便让点击事件生效,最后开始断言,textview中的文字是否是我们期望的文字,如果是的话,就证明我们的程序是没有问题的.

点击类名左边的绿色箭头就可以直接运行,也可以在运行项目那选择测试用例点击运行按钮

运行效果
可以看到程序运行后自动的填写了数组,并点击了确定,AndroidStudio的控制台也自动变成到了测试的界面,而测试页面也出现了错误日志
错误日志
第一句就告诉了我们 我们期待的结果是 = 3,而 出现的结果是 = 12,我们把程序代码改正再来运行一下

这回出现了我们期待的结果,测试就通过了

测试代码-2

Android也提供了通过注解的形式来运行测试代码,要比之前的形式更优雅一点

import android.support.test.InstrumentationRegistry;import android.support.test.rule.ActivityTestRule;import android.support.test.runner.AndroidJUnit4;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;import com.robotium.solo.Solo;import org.junit.After;import org.junit.Before;import org.junit.Rule;import org.junit.Test;import org.junit.runner.RunWith;import static org.junit.Assert.assertEquals;/** * If there is no bug, then it is created by ChenFengYao on 2017/3/3, * othrwise, I do not know who create it either. */@RunWith(AndroidJUnit4.class)public class TestFirst{    private Solo solo;    @Rule    public ActivityTestRule<MainActivity> activityTestRule =            new ActivityTestRule<>(MainActivity.class);    @Before    public void setUp() throws Exception {        solo = new Solo(InstrumentationRegistry.getInstrumentation(),                activityTestRule.getActivity());    }    @Test    public void testRun() {        EditText num1Et = (EditText) solo.getView(R.id.num1_et);        EditText num2Et = (EditText) solo.getView(R.id.num2_et);        Button resultBtn = (Button) solo.getView(R.id.get_result_btn);        TextView resultTv = (TextView) solo.getView(R.id.result_tv);        solo.enterText(num1Et,"1");        solo.enterText(num2Et,"2");        solo.clickOnView(resultBtn);        solo.sleep(200);        assertEquals("= 3",resultTv.getText().toString());    }    @After    public void tearDown() throws Exception {        solo.finishOpenedActivities();    }}

效果与之前的测试代码是一样的,就不额外演示了,与之前的代码不同,我们想要获得Activity对象则需要通过@Rule注解来获得,@Before和@After都是junit框架提供的,代表测试代码运行前和运行后,而测试方法则需要是用@Test注解进行修饰

Robotium的Native使用

控件的获取

Robotium中获取控件主要有两大方式:
- 根据被测应用的控件ID来获取
- 先获取当前页面所有的控件,对这些控件进行过滤封装后再提供相应获取控件的API

1. 根据控件的ID来获取

EditText num1Et = (EditText) solo.getView("num1_et");EditText num2Et = (EditText) solo.getView(R.id.num2_et);

在Android中,所有的控件都会继承View,所以该方法能获取项目中所有的View,如果被测试的应用中控件有唯一ID的话,是一定能通过这种形式来获取所要操作的控件的,获取View之后再向下转型成我们需要的类型即可, 当View拥有唯一ID的时候,优先使用这种方式

2. 根据控件类型的索引,文本来获取

  1. 根据索引获取
    Robotium会先将当前界面中所有控件全部获取,然后按控件类型,索引进行过滤后获取指定的控件

    //获取页面中第一个类型是Button的控件Button button = (Button) solo.getButton(0);

    而基本上常用的控件Robotium都会提供这样的API,例如EditText,TextView等都是这样,但是如果是自定义组件就不行了

  2. 根据文本获取控件

    //返回界面中text属性是"登陆"的ButtonButton loginBtn = (Button) solo.getButton("登陆");

    这种方法则局限性更大,只能找到具有text属性的一些控件

  3. 根据控件类型获取控件
    Robotium可以获取 页面中/指定View下 所有的控件,或同一类型的控件

    //获取当前页面或dialog中所有的控件ArrayList<View> getCurrentViews();//获取当前页面或Dialog中所有类型为classToFilterBy的控件ArrayList<T> getCurrentViews(Class<T> classToFilterBy);//获取父控件parent下所有控件类型为classToFilterBy的控件ArrayList<T> getCurrentViews(class<T> classToFilterBy, View parent);
  4. 获取相同id的控件
    在遇到ListView这种组件的时候,要想对item内部的View进行操作会发现这些View无论是Id还是文本还是类型都是一致的,而ListView本身是可以很方便的获得这些item的,最简单的可以通过listView.getChildAt(index)的形式,而在获取到Item之后,我们又可以通过调用findViewById来获取该Item下的组件了

    //先获取ListViewListView listView = (ListView) solo.getView("main_lv");//获取指定的itemView convertView =  listView.getChildAt(0);//从指定item中获取指定viewTextView itemTv = (TextView) listView.findViewById(R.id.item_tv);

    这种通过findViewById的方式来获取控件,也适用于从Activity等页面中来获取指定控件.

注意:
- 当没找到想要的控件时,Robotium就会抛出运行时异常,我们可以手动捕获该异常来防止测试代码崩溃
- 尽量不适用通过索引的方式获得控件,这种方式过于依赖页面的布局结构

控件操作

对Android端控件的操作大概有点击,长按,文本输入,滑动,滚动,截图等,而为了页面真实性,有时还需要加入等待等操作

注意:
- Robotium 对控件的操作是不能够跨进程的,所以它是不能够点击到例如通知栏等区域,如果编写了这样的代码,程序是会崩溃的
- 在调试时建议打开 “指针位置” 功能,这样在编写测试代码的时候,就可以知道点击的位置坐标了

1. 点击,长按

点击和长按操作可以是找到组件然后点击该组件,而如果是自定义View时根据位置去处理手势的话,还可以根据指定的坐标去点击或者长按

//点击/长按指定的View控件void clickOnView(View view);void clickLongOnView(View view);//点击/长按指定的屏幕坐标void clickOnScreen(float x, float y);void clickLongOnScreen(float x, float y);

Robotium 还提供了点击文本,图片的api,例如 clickOnButton(String text),它会先根据text找到Button,然后再去点击这个Button,目前Robotium也提供了点击RecyclerView的item的方法clickInRecyclerView(int itemIndex)这样的方法,来方便对ListView,RecyclerView进行操作

2. 输入操作

//在指定EditText中输入textvoid enterText(EditText editText, String text);//在指定EditText中键入文本void typeText(EditText editText, String text);//清空指定的输入框void clearEditText(EditText editText);

clearEditText比较好理解,而enterText和typeText的区别是enter是直接对EditText进行赋值,而type则是像用户一样的一个一个文本的输入

EditText editText = (EditText) solo.getView(R.id.main_et);solo.enterText(editText, "editText");

3. 滑动,滚动操作

//从起始x,y坐标滑动至终点x,y坐标,通过stepCount参数指定滑动时的步长void drag(float fromX, float toX, float fromY, float toY, int stepCount);//滚动到顶部/底部void scrollToTop();void scrollToBottom();//向上滚动屏幕/向下滚动屏幕void scrollUp();void scrollDown();//滚动至ListView第line行void scrollListToLine(AbsListView absListView, int line);
  • 根据坐标法进行滑动的时候坐标不需要多说,而步长的意思是滑动的速度,例如从A点滑动到B点,如果步长为1,那么将直接产生从A到B的滑动手势,如果步长为100,则会将A到B之间均分成100份,然后依次滑动

  • 滚动到顶部/底部的方法是将当前屏幕滚动到顶部或底部,什么能滚,滚什么,例如:如果是ListView就会直接滚动到ListView的顶部,如果是WebView就会滚动到WebView的顶部

  • 向上滑/向下滑则也是根据当前屏幕控件来走的,基本上是滑动一屏的距离,而与drag不同的是,这些都是调用控件自身的api来进行滚动的,例如drag能触发下拉刷新等操作,但是scroll则不能,所以drag更贴近用户的操作

  • 对新的RecyclerView,同样提供了Api,但是不能指定滑动到第几行,只有向上/向下滑动,和滚动到顶端/底端,并且也只能通过RecyclerView的索引来找到RecyclerView

    boolean scrollUpRecyclerView(index);boolean scrollDownRecyclerView(index);boolean scrollRecyclerViewToTop(index);boolean scrollRecyclerViewToBotton(index);

4. 搜索与等待

UI自动化测试常常遇到的问题就是项目快速迭代导致界面经常变更,脚本经常出错,控件的搜索与等待则可以缓解这一问题

//休眠指定时间void sleep(int time);//从当前页面搜索指定文本boolean searchText(String text);//等待控件/文本的出现boolean waitForView(int id);boolean waitForText(String text);//等待指定Activity的出现boolean waitForActivity(String name);//等待指定Log的出现boolean waitForLogMessage(String logMessage);//等待对话框的打开/关闭boolean waitForDialogToOpen();boolean waitForDialogToClose();//等待某种加载条件的达成boolean waitForCondition (Condition condition, int timeout);//等待Fragment的加载boolean waitForFragmentById (String id);boolean waitForFragmentByTag (String tag);

大多数API都是比较好理解的,这个Condition是一个接口,里面只提供了一个isSatisfied()方法,该方法返回boolean类型的值,需要我们自己写的,我们可以设置一些复杂的条件来实现这个接口

solo.waitForCondition(new Condition() {            @Override            public boolean isSatisfied() {                return solo.isCheckBoxChecked(0);            }        },200);

而几乎所有的等待操作都可以设置超时时间,需要注意的是,Robotium中查找控件,点击控件等API都默认使用了搜索与等待机制,所以非必要的情况下一般是不需要我们主动设置等待的

5. 其他操作

剩下的一些操作包括截图,对软键盘的操作,点击物理按键等等

//截图,name为图片的参数,默认路径是/sdcard/Robotium-Screenshots/void takeScreenshot();void takeScreenshot(String name);//截取某段时间内一个序列void takeScreenshotSequence(String name);//关闭当前已打开的所有Activityvoid finishOpenedActivities();//点击返回键void goBack();//不断点击返回键直至返回到指定Activityvoid goBackToActivity(String name);// 收起键盘void hideSoftKeyboard();// 设置Activity转屏方向void setActivityOrientation(int orientation);

在自动化测试中,当执行失败是,除了Log,最方便解决定位问题的就是运行时的截图,Robotium中提供了棘突功能,

断言

在自动化测试中,我们获取控件,执行操作后,接下来就是要对操作后的场景进行断言了,断言就是我们在程序时的一些假设,就是我们断定在程序运行到某个时候,某个属性一定是多少,我们写程序的时候,大多数都是我们事先知道输入与输出的,断言就是来测试程序的输出与我们的输入是否相符

1.Junit中的断言

//断言传入的condition参数应该为True,否则抛出一个带有message提示的Throwable异常void assertTrue(String message, boolean condition);// 断言传入的condition 参数应该为False, 否则抛出message的异常void assertFalse(String message, boolean condition);//直接认为断言失败,抛出一个message异常void fail(String message);// 断言相等void assertEquals(String message,Obj o1, Obj o2);

Junit中的断言都在AndroidSdk中的 junit.framework.Assert包下的Assert类中, 常用的只有第一个,在使用的时候应该明确说明message参数的作用,方便我们后期查看

Button loginBtn = (Button) solo.getView("loginBtn");assertTrue("登录按钮应该显示", loginBtn.isShown());

2. Robotium中的断言

// 断言当前界面是否为name参数指定的Activity,若不是则抛出一个带有message提示的Throwable异常void assertCurrentActivity(String message, String name);//断言不处于低内存状态void assertMemoryNotLow();

Robotium基于Junit中的断言,也封装了几个方便在Android端自动化的时候使用,这些方法都是sole对象的方法

3.Android中的断言

//断言view在屏幕中void assertOnScreen(View origin, View view);//断言两个View是否低端对齐void assertBottomAligned(View first, View second);

这些断言都是Android提供的,可以方便的判断一些Android中的UI信息他们在android.test.ViewAsserts包下,但是目前(API level 24)这个类已经被标注成Deprecated了,目前Android提供了一个ViewMatchers的类来处理View的断言,而处理的思路和以前有了变化,核心方法是

static <T> void assertThat(String message T actual, Matcher<T> matcher);

几乎所有的判断用的都是这个方法,message 是抛出的异常信息,Matcher 是断言规则,之前的在屏幕上显示,底部对齐等都变成了一种断言规则,我们也可以写自己的规则,而T则是需要的View,这里使用的是泛型,也就是该断言框架可以不仅局限于用来断言View了 也可以用来断言任何东西,例如想要判读一个TextView是否显示某种文字

TextView textView = (TextView) solo.getView(R.id.main_tv);ViewMatchers.assertThat("显示: Welcome",textView,ViewMatchers.withText("Welcome"));

Robotium 原理

1.获取控件

我们来看一看solo对象通过view的id来获取控件的方法

public View getView(int id) {    if(this.config.commandLogging) {        Log.d(this.config.commandLoggingTag, "getView(" + id + ")");    }    return this.getView(id, 0);}

Log先不用看,这个方法最后又调用了getView(int id, int index)的方法,那么我们再来看一看这个方法

public View getView(int id, int index) {    if(this.config.commandLogging) {        Log.d(this.config.commandLoggingTag, "getView(" + id + ", " + index + ")");    }    View viewToReturn = this.getter.getView(id, index);    if(viewToReturn == null) {        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;}

这段代码中重要的是View viewToReturn = this.getter.getView(id,index);这一句,其他的都是生成log,或者报错等,这个getter对象就是Robotium中专门用来获取View的类,那么再来看看这里面的方法

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();    }    return this.waitForView(id, index, timeout, false);}

在getView(int id, int index)方法里,又调用了 有timeout的方法重载,而在有timeout的方法重在里,又调用了waitForView,这样找到控件的方法,最后,就和等待的方法回合了,他们最后都调用了
waitForView(int id, int index, int timeout, boolean scroll)方法
一起来看一下这个方法

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();        Iterator i$ = this.viewFetcher.getAllViews(false).iterator();        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();        }    }    return null;}

这段代码就是最后来找到View的代码了,首先会创建出一个HashSet来存放View,接着会通过迭代器,从ViewFetcher中获取所有的View,这里传入的boolean值得意思是是否只获取可见的View,去除所有View之后,拿到他们的ID,再与我们传入的id进行对比,如果匹配成功就返回我们的View.那么拿到View的关键就是Robotium怎么获取的我们所有的View,接着一路看源码下去

static {    try {        String e;        if(VERSION.SDK_INT >= 17) {            e = "android.view.WindowManagerGlobal";        } else {            e = "android.view.WindowManagerImpl";        }        windowManager = Class.forName(e);    } catch (ClassNotFoundException var1) {        throw new RuntimeException(var1);    } catch (SecurityException var2) {        var2.printStackTrace();    }}public ArrayList<View> getAllViews(boolean onlySufficientlyVisible) {    View[] views = this.getWindowDecorViews();    ArrayList allViews = new ArrayList();    View[] nonDecorViews = this.getNonDecorViews(views);    View view = null;    if(nonDecorViews != null) {        for(int ignored = 0; ignored < nonDecorViews.length; ++ignored) {            view = nonDecorViews[ignored];            try {                this.addChildren(allViews, (ViewGroup)view, onlySufficientlyVisible);            } catch (Exception var9) {                ;            }            if(view != null) {                allViews.add(view);            }        }    }    if(views != null && views.length > 0) {        view = this.getRecentDecorView(views);        try {            this.addChildren(allViews, (ViewGroup)view, onlySufficientlyVisible);        } catch (Exception var8) {            ;        }        if(view != null) {            allViews.add(view);        }    }    return allViews;}public View[] getWindowDecorViews() {    try {        Field viewsField = windowManager.getDeclaredField("mViews");        Field instanceField = windowManager.getDeclaredField(this.windowManagerString);        viewsField.setAccessible(true);        instanceField.setAccessible(true);        Object e = instanceField.get((Object)null);        View[] result;        if(VERSION.SDK_INT >= 19) {            result = (View[])((ArrayList)viewsField.get(e)).toArray(new View[0]);        } else {            result = (View[])((View[])viewsField.get(e));        }        return result;    } catch (Exception var5) {        var5.printStackTrace();        return null;    }}

跟踪到最后发现,它通过反射拿到了一个叫WindowManagerGlobal的对象,并从这个对象中获得了顶级ViewDecorView,但是仔细观察它的反射方式,是直接通过类名构建了一个 新的WindowManagerGlobal对象 ,那么是如何从这个全新的WindowManagerGlobal中拿到DecorView的呢,只能从Android的源码入手分析了

在所有的Activity创建的过程中,我们知道都会有一个Window对象,当然真正的使用的是其子类PhoneWindow,PhoneWindow中会为Activity创建出一个DecorView对象,而DecorView是Activity的顶级View,在创建完成之后,还要通过WindowManager对象去将DecorView显示到屏幕上,这一步会调用WindowManager的makeVisible()方法

void makeVisible() {    if (!mWindowAdded) {        ViewManager wm = getWindowManager();            wm.addView(mDecor, getWindow().getAttributes());        mWindowAdded = true;    }    mDecor.setVisibility(View.VISIBLE);}

在这一步中,会调用WindowManagerImpl的addView方法,WindowManagerImpl就是WindowManager真正的实现类,

@Overridepublic void addView(View view, ViewGroup.LayoutParams params{    mGlobal.addView(view, params, mDisplay, mParentWindow);}

会将DecorView添加到一个叫WindowManagerGlobal的对象中,这个对象中有一个叫sDefaultWindowManager的静态的WindowManagerGlobal,所有的DecorView都会添加到这个sDefaultWindowManager的一个成员变量mViews中,它是一个专门用来存放所有的DecorView的ArrayList,同时又因为sDefaultWindowManager是静态的,所以在全局无论通过什么途径拿到它,拿到的都是同一个对象

2. 控件的操作

Robotium获取控件后,调用clickOnView(View view)方法就可以完成点击操作,这个方法可以分为两步实现
1. 根据View获取控件在屏幕中的位置
2. 根据坐标模拟点击事件

对WebView的支持

Robotium 在 4.0 开始 全面支持WebView,它支持通过ID,className等方式来获取WebElement元素

//获取当前WebView所有的WebElement元素ArrayList<WebElement> getCurrentWebElements();//通过By根据指定的元素获取当前WebView的所有WebElement元素ArrayList<WebElement> getCurrentWebElements(By by);//通过by根据指定的元素属性点击WebElementvoid clickOnWebElement(By by);//点击指定的WebElementvoid clickOnWebElement(WebElement webElement);//根据by找到WebElement,并输入指定的文本textvoid enterTextInWebElement(By by, String text);//等待根据by获得的WebElement出现boolean waitForWebElement(By by);

By的其实就是当我们知道了Web元素的某种属性之后来通过该属性来获取控件的,例如

ArrayList<WebElement> webElements = solo.getCurrentWebElements();ArrayList<WebElement> webElements = solo.getCurrentWebElements(By.id("example_id"));

在使用Robotium操作WebView的时候需要注意的是,它只支持原生的WebView而不支持第三方浏览器内核

3 0
原创粉丝点击