前面的单元测试和集成测试需要开发的功底,一般来说是开发人员用来进行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目录下。
在运行uiautomatorviewer之前,需要先连接上Android设备,然后双击运行uiautomatorviewer.bat。
然后点击第二个或第三个按钮(带有手机图标),然后就会抓取Android设备当前正在运行的界面以及相关视图信息。
建立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信息。
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中可以直接查看。