前面的单元测试和集成测试需要开发的功底,一般来说是开发人员用来进行TDD迭代开发的,对一般的测试人员非常不友好。

一般的测试人员只做UI级别的测试工作,Android提供了UI自动化测试的框架,避免了重复的人力工作。

Android提供了多种UI测试框架,就UI框架的适用性来说,一般推荐UI Automator,因为该框架可以进行跨App(一个App存在与其它App进行交互的可能情形)测试。但是UI Automator只支持Android 4.3及以上版本。

UI Automator使用Java语言。

抓取App的UI及其视图组件的信息

测试人员一般不知道App开发的工程代码,对UI大多只能进行功能测试。这就要求有对App的UI进行分析能力的工具。Android的SDK自带的uiautomatorviewer工具可以对Android App进行UI分析,该工具一般位于sdk目录下的tools/bin目录下。

android自动化测试之UI 安卓ui自动化测试_UI Automator

在运行uiautomatorviewer之前,需要先连接上Android设备,然后双击运行uiautomatorviewer.bat。

android自动化测试之UI 安卓ui自动化测试_android自动化测试之UI_02

然后点击第二个或第三个按钮(带有手机图标),然后就会抓取Android设备当前正在运行的界面以及相关视图信息。

android自动化测试之UI 安卓ui自动化测试_UI Automator_03

建立UI Automator测试环境

由于UI测试是建立在仪器级单元测试之上,所以需要在仪器级单元测试的配置之上添加UI Automator的配置。

dependencies {
    ...

    // Required -- JUnit 4 framework
    testCompile 'junit:junit:4.12'
    // Optional -- Mockito framework
    testCompile 'org.mockito:mockito-core:1.10.19'

    androidTestCompile 'com.android.support:support-annotations:26.1.0'
    androidTestCompile 'com.android.support.test:runner:1.0.1'
    androidTestCompile 'com.android.support.test:rules:1.0.1'

    // Optional -- UI testing with UI Automator
    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
}

UI Automator测试示例

假设我们已有一个包名为“com.trb.uiautomatordemo”的App已经安装在Android设备上,该App只有一个Activity和UI布局文件。

package com.trb.uiautomatordemo;
...

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = findViewById(R.id.button1_MainActivity);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "button1OnClickDemo", 
        Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onBackPressed() {
        Process.killProcess(Process.myPid());
    }
}

Activity示例

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.trb.uiautomatordemo.MainActivity">

    <Button
        android:id="@+id/button1_MainActivity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textAllCaps="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

</android.support.constraint.ConstraintLayout>

layout布局文件示例

如果不知道该App的工程源码,那么现在Android设备上运行该App,然后使用uiautomatorviewer抓取App的UI信息。

android自动化测试之UI 安卓ui自动化测试_UI自动化测试_04


UI信息抓取的示例

然后我们可以新建一个测试工程(或者在App的源码工程里进行测试),配置好UI Automator的测试环境,创建一个测试类。

package com.trb.uiautomatordemo;

import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SdkSuppress;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.Until;
import android.util.Log;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 21)
public class MainActivityButtonTest {
    private static final String TAG = MainActivityButtonTest.class.getSimpleName();

    private String launcherPackage;
    private UiDevice uiDevice;

    /**
     * 该方法用于启动要测试的App。
     * 带有“@Before”注解的方法会在带有“@Test”注解的方法之前运行。
     */
    @Before
    public void startApp() throws Exception {
        Log.v(TAG, "startApp: start");
        //获取Android设备的实例
        uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        //在Android设备上按下Home键
        uiDevice.pressHome();
        //获取Android设备的Launcher App的包名
        launcherPackage = uiDevice.getLauncherPackageName();
        Log.v(TAG, "startApp: " + launcherPackage);
        Assert.assertNotNull(launcherPackage);
        //让设备等待5000毫秒
        uiDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), 5000);
        //获取当前LauncherApp的Context
        Context appContext = InstrumentationRegistry.getContext();
        //启动包名为“com.trb.uiautomatordemo”的App
        //此时我们假设已有一个包名为“com.trb.uiautomatordemo”的App安装在Android设备
        Intent intent = appContext.getPackageManager()
                .getLaunchIntentForPackage("com.trb.uiautomatordemo");
        Assert.assertNotNull(intent);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        appContext.startActivity(intent);
        //让设备等待5000毫秒
        uiDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), 5000);
        Log.v(TAG, "startApp: over");
    }

    @Test
    public void testButtonClick() throws Exception {
        Log.v(TAG, "testButtonClick: 1");
        //获取界面中text属性为“Hello World!”的按钮的UI对象
        UiObject btn = uiDevice.findObject(new UiSelector()
                .text("Hello World!")
                .className("android.widget.Button"));
        Assert.assertNotNull(btn);

        Log.v(TAG, "testButtonClick: button not null");
        //对该按钮执行点击操作
        btn.click();
        uiDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), 5000);

        Log.v(TAG, "testButtonClick: 0");
    }

}

测试类的示例

最后运行测试方法,然后观察Android设备上的运行现象和运行日志。

注意:
由于Android Studio的logcat信息显示有问题,建议使用adb的logcat命令将运行日志写入到文件里。

UI Automator简介

UI Automator中的核心类是
android.support.test.uiautomator.UiDevice
android.support.test.uiautomator.UiObject
android.support.test.uiautomator.UiSelector

通过上面这三个类,就可以分别操作Android设备以及UI上的视图组件。

UiDevice代表了一个Android设备,并提供了Android设备上相关按键的模拟方法。例如按下Home键位的模拟方法就是UiDevice.pressHome()。

UiObject代表了一个UI上的视图组件,大到布局容器,小到Button或者TextView,并提供了视图组件的相关事件的对应方法。例如UIObject.click()方法表示对视图组件执行点击操作。

UiSelector代表一个UiObject的属性,配合UiDevice进行使用,将与配置的属性的视图组件从UI中筛选出来。

更详细的信息,请查阅UI Automator的源码。
UI Automator的源码比较简单,在Android Studio中可以直接查看。