1.     概述

UI测试(功能测试、黑盒测试)不需要测试者了解应用程序的内部实现细节,只需要知道当执行了某些特定的动作后是否会得到其预期的输出。这种测试方法,在团队合作中可以更好地分离的开发和测试角色。

常见的UI测试的方法是手动去执行,然后去验证程序是否达到的预期的效果,很显然这种方法耗时、繁琐并且很容易出错。因此我们需要一种可靠的方法来进行UI测试,通过测试框架,我们可以完成针对具体使用场景的测试用例,然后可以循环的、自动的来运行我们的测试case。

 

Android的SDk提供了以下的工具来支持我们进行UI自动化测试:

uiautomatorviewer:一个用来扫描和分析android应用程序的UI控件的GUI工具。

uiautomator:一个包含创建测试、执行自动化测试API的Java库。(Uiautomator文档:http://android.toolib.NET/tools/help/uiautomator/index.html )

 

要使用这些工具,你必须安装Android开发工具以下版本:

Android SDKTools:API 21 版本或者21以上版本;

Android SDKPlatform:API 16 版本或者16以上版本.

 

 

2.     UiAutomatorViewer使用

在你开始写测试用例之前,使用uiautomatorviewer可以帮助你熟悉你的UI组件(包括视图和控件)。你可以使用它对当前连接到你电脑上的手机屏幕进行一个快照,然后可以看到手机当前页面的层级关系和每个控件的属性。利用这些信息,你可以写出针对特定UI控件的测试用例。

在 ..\sdk\tools\ 目录下打开uiautomatorviewer.bat (打开前请手机连接电脑)

1)        获取快照

当你要分析一个页面时,首先将手机的页面停留在你要分析的页面,然后用数据线连接电脑。然后点击uiautomatorviewer左上角的第二个图标按钮 Device Screenshot,点击之后会将当前手机界面的快照更新到这里来。

2)        页面层级

右上方的整个区域,就是当前页面布局的层级关系。

3)        控件属性

右下方的整个区域,是当前选中的页面或者是控件的属性信息。这部分比较重要,我们以后写代码的时候就是需要通过查看属性中的控件的id或者是text等来获取控件的实例,然后点击操作它。

 

我们可以通过text、resource-id、class、content-desc等来获取控件。

 

 

3.     UiAutomator

UiAutomator2.0做了一些改进:

1)      基于 Instrumentation,可以获取应用 Context,使用 Android 服务及接口

2)      基于 Junit 4,测试用例无需继承于任何父类,方法名不限,使用注解 Annotation 进行

UI 执行效率比 1.0 快,测试执行可使用Android Junit 方式及 gradle 方式

3)      API 更新,新增UiObject2、Until、By、BySelector 等:API For UI Automator

4)      Log 输出变更,以往使用System.out.print 输出流回显至执行端,2.0 输出至 Logcat。

 

 

4.     UiAutomator2.0使用步骤

1)        在android studio新建一个工程

2)        添加依赖

 

[java] view plain copy
 
  1. androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {  
  2.     exclude group:'com.android.support',module:'support-annotations'  
  3. })  
  4. testCompile 'junit:junit:4.12'  
  5. // Set this dependencyto build and run UI Automator tests  
  6. androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'  



 

3)        在androidTest目录下新建一个Test, 点击button4,跳转到一个新页面

@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class Test1 {
    private UiDevice mDevice;

    @Before
    public void before() {
        // Initialize UiDevice instance
        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        assertThat(mDevice, notNullValue());

        // Start from the home screen
        mDevice.pressHome();
        // open app
        openApp("com.ut.anquanguankong");
    }

    @Test
    public void test() throws InterruptedException {
        //点击desc=button4的按钮
        findObject(By.desc("button4")).click();
    }

    public void openApp(String packageName) {
        Context context = InstrumentationRegistry.getInstrumentation().getContext();
        Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        context.startActivity(intent);
    }

    public UiObject2 findObject(BySelector selector) throws InterruptedException {
        UiObject2 object = null;
        int timeout = 30000;
        int delay = 1000;
        long time = System.currentTimeMillis();
        while (object == null) {
            object = mDevice.findObject(selector);
            sleep(delay);
            if (System.currentTimeMillis() - timeout > time) {
                break;
            }
        }
        return object;
    }
}

5.     UiAutomator2.0 API

UiAutomator2.0是兼用1.0的,2.0的API会包含1.0的API。通过了解这API方法,就可以编写UI自动化测试代码了。官方文档:

https://developer.android.google.cn/reference/android/support/test/uiautomator/package-summary.html,下面介绍常用的2.0 API:

 

 

 

1)   InstrumentationRegistry

a)        类说明

一个暴露的注册实例,持有instrumentation运行的进程和参数,还提供了一种简便的方法调用instrumentation, application context和instrumentation参数。

b)        相关API

返回类型

API

static Bundle

getArguments(): 返回一个instrumentation参数副本

static Context

getContext():  返回instrumentation对应包的Context

InstrumentationRegistry.getContext() == instrumentation.getContext()

static Instrumentation

getInstrumentation(): 返回当前运行的instrumentation

static Context

getTargetContext(): 返回一个目标应用程序的Context

static void

registerInstance(Instrumentation instrumentation, Bundle arguments):记录或暴露当前instrumentation运行和instrumentation参数包的副本,存储在注册中心

c)        示例

@Test
public void InstrumentationRegistryTest() {
    Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
    Context context1 = InstrumentationRegistry.getContext();
    Context context2 = InstrumentationRegistry.getTargetContext();
    Context context3= instrumentation.getContext();

    if(context1 == context2) {
        Log.i("Chris",  "InstrumentationRegistry getContext == getTargetContext");
    }else {
        Log.i("Chris",  "InstrumentationRegistry getContext != getTargetContext");
    }

    if(context1 == context3) {
        Log.i("Chris",  "InstrumentationRegistry getContext == Instrumentation getContext");
    }else {
        Log.i("Chris",  "InstrumentationRegistry getContext != Instrumentation getContext");
    }

    Intent intent = context2.getPackageManager().getLaunchIntentForPackage("xxx.xxx.xxx");
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
    context2.startActivity(intent);
}

 

2)   UiDevice

a)        类说明

UiDevice用与访问关设备状态的信息,也可以使用这个类来模拟用户在设备上的操作。可以通过下面的方法得到实例:

UiDevice mdevice = getUiDevice();

b)        相关API

返回类型

API

Boolean

click(int x, int y): 模拟用户在指定位置点击

String

getCurrentActivityName():  获得的是应用程序在桌面上显示的名字

String

getCurrentPackageName():获得当前显示的应用程序的包名

int

getDisplayHeight():获得当前设备的屏幕分辨率的高

int

getDisplayWighth():获得当前设备的屏幕分辨率的宽

boolean

isScreenOn():判断手机当前是否灭屏

Void

wakeUp():点亮当前屏幕

Boolean

pressBack():点击back键

Boolean

pressHome():点击home键

Boolean

PressMenu():点击menu键

Boolean

PressCode(int code): 利用keycode值模拟一次按下事件, 例如,需要按下数字1 数字1的keycode是 KEYCODE_NUMPAD_1,更多keycode可以在 http://developer.android.com/intl/zh-cn/reference/android/view/KeyEvent.html 进行查询

boolean

swipe(int startX, int startY, int endX, int endY, int steps): 用指定的步长,从A点滑动B点

boolean

takeScreenshot(File storePath): 截取当前屏幕,保存到文件

 

UiAutomator2在UiDevice新增的API

返回类型

API

void

dumpWindowHierarchy(OutPutStream out): 获取当前页面层级到输出流

String

executeShellCommand(String cmd): 执行一个shell命令。备注:此方法只支持api21以上,手机需要5.0系统以上

UiObject2

findObject(BySelector selector): 返回第一个匹配条件的对象

UiObject

findObject(UiSelector selector): 返回一个匹配条件的代表视图的UiObject对象

List<UiObject2>

findObjects(BySelector selector): 返回所有匹配条件的对象

<R> R

wait(SearchCondition<R> condition, long timeout): 等待的条件得到满足


3)   BySelector和By

a)        类说明

BySelector和By是UiAutomator2.0的类。

BySelector类为指定搜索条件进行匹配UI元素,通UiDevice.findObject(BySelector)方式进行使用。

By类是一个实用程序类,可以以简洁的方式创建BySelectors对象。主要功能是使用缩短语法,提供静态工厂方法来构造BySelectors对象。例如:你将使用findObject(By.text("foo")),而不是findObject(newSelector().text("foo"))的方式来查找文本值为“foo”的UI元素。

b)        相关API

在这里介绍By的API,BySelector的API和By的对应的。

返回类型

API

BySelector

clazz(String calssName),

clazz(String packageName, String className),

clazz(Class clazz),

clazz(Pattern className)

通过class来匹配UI

BySelector

desc(String contentDescription)

descContains(String substring)

descStartsWith(String substring)

descEndsWith(String substring)

desc(Pattern contentDescription)

通过contentDescription来匹配UI

BySelector

text(String contentDescription)

textContains(String substring)

textStartsWith(String substring)

textEndsWith(String substring)

text(Pattern contentDescription)

通过text来匹配UI

BySelector

res(String resourceName)

res(String resourcePackage, String resourceId)

res(Pattern resourceName)

通过id来匹配UI

BySelector

checkable(boolean isCheckable)

BySelector

clickable(boolean isClickable)

BySelector

enabled(boolean isEnabled)

BySelector

focusable(boolean isFocusable)

BySelector

focused(boolean isFocused)

BySelector

longClickable(boolean isLongClickable)

BySelector

scrollable(boolean isScrollable)

 

4)   UiObject2

1)        类说明

可以理解为直接操作界面ui元素的实例。UiObject2是UiAutomator2的类。

2)        相关API

基本动作

API

说明

clear()

清楚编辑框内的内容

click()

点击一个对象

clickAndWait(EventCondition<R> condition, long timeout)

点击一个对象然后等待在超时的时间内条件满足则通过,否则抛出异常

drag(Point dest, int speed)

自定义速度拖拽这个对象到指定位置

drag(Point dest)

拖拽这个对象到指定位置

longClick()

长按某个对象

scroll(Direction direction, float percent)

对该对象执行一个滚动操作

scroll(Direction direction, float percent, int speed)

自定义速度,对该对象执行一个滚动操作

setText(String text)

设置文本内容

legacySetText(String text)

通过发送keycode,设置文本内容

 

手势动作

API

说明

pinchClose(float percent, int speed)

自定义速度执行收缩手势

pinchClose(float percent)

执行收缩手势

pinchOpen(float percent, int speed)

自定义速度执行展开手势

pinchOpen(float percent)

执行展开手势

fling(Direction direction)

执行一个扫动手势,Direction代表为起点方向

fling(Direction direction, int speed)

自定义速度,执行一个扫动手势

swipe(Direction direction, float percent, int speed)

执行一个滑动操作,可自定义滑动距离和速度

swipe(Direction direction, float percent)

执行一个滑动操作

setGestureMargin(int margin)

以像素为单位,设置手势边缘

setGestureMargins(int left, int top, int right, int bottom)

以像素为单位,设置手势边缘

 

获取层级与条件判断

API

说明

findObject(BySelector selector)

搜索在这个对象之下的所有元素,并返回第一个与搜索条件匹配的

findObjects(BySelector selector)

搜索在这个对象之下的所有元素,并返回所有与搜索条件匹配的

getChildCount()

返回这个对象直属子元素的数量

getChildren()

返回这个对象下的直接子元素的集合

getParent()

返回该对象的父类

equals(Object object)

比较两个对象是否相等

hashCode()

获取对象的哈希码

hasObject(BySelector selector)

返回该对象是否存在

recycle()

回收该对象

wait(UiObject2Condition<R> condition, long timeout)

等待条件被满足

wait(SearchCondition<R> condition, long timeout)

等待条件被满足

 

5)   Configration

a)        类说明

Configrator用于设置脚本动作的默认延时:

1.        可调节两个模拟动作之间的默认间隔

2.        可调节输入文本的输入时间间隔

3.        可调节每次滚动的时间间隔

4.        可调节等待系统空闲的默认时间

b)        相关API

延时项

默认延时

说明

API

动作

3s

设置延时

setActionAcknowledgmentTimeout(long timeout)

 

 

获取默认延时

getActionAcknowledgmentTimeout()

键盘输入

0s

设置延时

setKeyInjectionDelay(long delay)

 

 

获取默认延时

getKeyInjectionDelay()

滚动

200ms

设置延时

setScrollAcknowledgmentTimeout(long timeout)

 

 

获取默认延时

getScrollAcknowledgmentTimeout()

空闲

10s

设置延时

setWaitForIdleTimeout(long timeout)

 

 

获取默认延时

getWaitForIdleTimeout()

组件查找

10s

设置延时

setWaitForSelectorTimeout(long timeout)

 

 

获取默认延时

getWaitForSelectorTimeout()

 

 

 

6.     断言

1)   断言函数介绍

确定被测试的方法是否按照预期的效果正常工作

比如说:

if (假设成立){

    通过测试

}else{

    报错并终止当前用例测试

}

2)   断言函数用例结构

一个完整的测试用例必需要有断言函数

setUp//初始化

//测试用例,junit4版本才可以使用多条用例

test        初始化场景与数据

test        模拟操作步骤

test        断言

test        恢复场景   

tearDown//回收初始化垃圾

3)   断言函数Java错误类型

a)        Error:

一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。对于这类错误导致的应用程序中断,仅靠程序本身无法恢复和预防(断言)

b)        Exeeption:

表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常(最常见的是UI对象找不到的异常)

 

 

 

4)   断言函数API

 

例如:

//断言两个对象是否相等

asserEquals(Stringmessage,Object expected,Object actual){

    if (expected==null && actual==null){

        return ;   

    }

    if (expected!=null && expected.equals(actual)){

        return

    }

    failNotEquals(message,expected,actual);

}

参数

说明

Message

可选消息,在断言失败后会抛出这个消息

Expected

期望的值

Actual

实际的值

 

相关API

方法

说明

assertEquals(boolean,boolean)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(String,boolean,boolean)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(byte,byte)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(String,byte,byte)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(char,char)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(String,char,char)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(int,int)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(String,int,int)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(long,long)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(String,long,long)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(Object,Object)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(String,Object,Object)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(short,short)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(String,short,short)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(String,String)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(String,String,String)

如果期望(expected)和实际(actual)相等则通过,否则失败

assertEquals(double,double,double)

如果期望(expected)和实际(actual)相差不超过精度值(delta)则通过,否则失败

assertEquals(String,double,

double,double)

如果期望(expected)和实际(actual)相差不超过精度值(delta)则通过,否则失败

assertEquals(float,float,float)

如果期望(expected)和实际(actual)相差不超过精度值(delta)则通过,否则失败

assertEquals(String,float,float,float)

如果期望(expected)和实际(actual)相差不超过精度值(delta)则通过,否则失败

assertFalse(boolean)

如果条件(condition)为False则通过,否则失败

assertFalse(String,boolean)

如果条件(condition)为False则通过,否则失败

assertTrue(boolran)

如果条件(condition)为True则通过,否则失败

assertTrue(String,boolran)

如果条件(condition)为True则通过,否则失败

assertNotNull(Object)

如果条件(condition)为非空则通过,否则失败

assertNotNull(String,Object)

如果条件(condition)为非空则通过,否则失败

assertNull(Object)

如果条件(condition)为空则通过,否则失败

assertNull(String,Object)

如果条件(condition)为空则通过,否则失败

assertNotSame(Object,object)

如果期望(expected)和实际(actual)引用不同的内存对象对象则通过,否则失败

assertNoteSame(String,Object,Object)

如果期望(expected)和实际(actual)引用不同的内存对象对象则通过,否则失败

assertSame(Object,Object)

如果期望(expected)和实际(actual)引用相同的内存对象对象则通过,否则失败

assertSame(String,Object,Object)

如果期望(expected)和实际(actual)引用相同的内存对象对象则通过,否则失败

fail()

用例立即失败

fail(String)

用例立即失败,且抛出指定消息

failNotEquals(String,Object,Object)

用例立即失败,且抛出指定消息与期望、实际值不相等的消息

failNotSame(String,String,String)

用例立即失败,且抛出指定消息与期望、实际值不相等的消息

failSame(String)

用例立即失败,且抛出指定消息

 

 

 

8.     报告分析

1)   错误类型

断言错误:就是断言这个用例的成功或者失败(AssrtionFailedError)

脚本错误:UiObjectNotFoundException(找不到对象异常)、java异常等

2)   报告分析

@Test
 public void testMain() throws InterruptedException, UiObjectNotFoundException {

    BySelector tabSelector = By.desc("TabContainer");
    uiAction.click(By.desc("button4")).isExist("打开主页面出错", tabSelector);
    mDevice.pressBack();
 }

这个方法测试:点击button4,进入主页面。

 

a)        正常运行

testMain打开一个存在的页面。

run started: 1 tests 
TestRunner: started: testMain(com.chris.example.uiautomatordemo.AnquanguankongTest)
MonitoringInstrumentation: Activities that are still in CREATED to STOPPED: 0
InteractionController: runAndwaitForEvents timed out waiting for events
QueryController: Got null root node from accessibility - Retrying...
InteractionController: runAndwaitForEvents timed out waiting for events
TestRunner: finished: testMain(com.chris.example.uiautomatordemo.AnquanguankongTest)
MonitoringInstrumentation: Activities that are still in CREATED to STOPPED: 0
TestRunner: run finished: 1 tests, 0 failed, 0 ignored

从上面报告来看,testMain正常执行。

 

b)        断言错误

testMain打开一个不存在的页面。

run started: 1 tests
TestRunner: started: testMain(com.chris.example.uiautomatordemo.AnquanguankongTest)
MonitoringInstrumentation: Activities that are still in CREATED to STOPPED: 0
InteractionController: runAndwaitForEvents timed out waiting for events
QueryController: Got null root node from accessibility - Retrying...
TestRunner: failed: testMain(com.chris.example.uiautomatordemo.AnquanguankongTest)
TestRunner: ----- begin exception -----
TestRunner: junit.framework.AssertionFailedError: 打开主页面出错
at junit.framework.Assert.fail(Assert.java:50)
at junit.framework.Assert.assertTrue(Assert.java:20)
at junit.framework.Assert.assertNotNull(Assert.java:218)
at com.chris.example.uiautomatordemo.UiAutomatorActionImpl.isExist(UiAutomatorActionImpl.java:102)
at com.chris.example.uiautomatordemo.AnquanguankongTest.testMain(AnquanguankongTest.java:52)
TestRunner: ----- end exception -----
TestRunner: finished: testMain(com.chris.example.uiautomatordemo.AnquanguankongTest)
MonitoringInstrumentation: Activities that are still in CREATED to STOPPED: 0
TestRunner: run finished: 1 tests, 1 failed, 0 ignored

从上面报告来看,testMain执行失败,并给出详细的错误信息。

c)        脚本错误

testMain点击一个不存在的button

run started: 1 tests
TestRunner: started: testMain(com.chris.example.uiautomatordemo.AnquanguankongTest)
MonitoringInstrumentation: Activities that are still in CREATED to STOPPED: 0
InteractionController: runAndwaitForEvents timed out waiting for events
QueryController: Got null root node from accessibility - Retrying...
TestRunner: failed: testMain(com.chris.example.uiautomatordemo.AnquanguankongTest)
TestRunner: ----- begin exception -----
TestRunner: junit.framework.AssertionFailedError: BySelector [DESC='\Qbutton42\E'] no found
at junit.framework.Assert.fail(Assert.java:50)
at junit.framework.Assert.assertTrue(Assert.java:20)
at junit.framework.Assert.assertNotNull(Assert.java:218)
at com.chris.example.uiautomatordemo.UiAutomatorActionImpl.isExist(UiAutomatorActionImpl.java:112)
at com.chris.example.uiautomatordemo.UiAutomatorActionImpl.findObjectWithCheck(UiAutomatorActionImpl.java:75)
at com.chris.example.uiautomatordemo.UiAutomatorActionImpl.click(UiAutomatorActionImpl.java:84)
at com.chris.example.uiautomatordemo.AnquanguankongTest.testMain(AnquanguankongTest.java:52)
TestRunner: ----- end exception -----
TestRunner: finished: testMain(com.chris.example.uiautomatordemo.AnquanguankongTest)
MonitoringInstrumentation: Activities that are still in CREATED to STOPPED: 0
TestRunner: run finished: 1 tests, 1 failed, 0 ignored
posted on 2017-08-21 22:34  薛土豆  阅读(2609)  评论(0编辑  收藏  举报