Android自动化测试入门(二)UI Automator

2,233 阅读5分钟

UI Automator是一个界面测试框架,支持跨进程,几乎可以模拟所有的人工操作。需要运行在4.3或者更高的系统版本上。它的测试代码的编写不依赖于目标应用的内部实现细节,非常适用编写黑盒自动化测试。

UI Automator 测试框架的主要功能包括:

  • uiautomatorviewer: 用来扫描和分析当前设备的当前页面的布局结构,它是sdk中自带的工具位置在 sdk/tools/bin/uiautomatorviewer.bat
  • UiDevice:可以访问目标设备的各种属性,执行设备上的一些操作,比如获取设备的屏幕尺寸,旋转设备,点击设备的返回键,菜单键,home键等。比如点击home键UiDevice.pressHome()
  • UI Automator API:用来编写可靠的测试

UI Automator API中有几个比较重要的类

  • UiObject:代表设备上可见的界面元素,也就是一个一个的控件
  • UiObject2:也是代表界面上的一个元素,它和特定的视图绑定,如果所绑定的视图失效,它也会跟着失效。
  • UiSelector:主要封装了用于界面定位的一系列的方法,主要通过控件的属性来实现控件的定位,用来定位的属性一般有:Text,descirption,class,package,resource-id等。如果搜索到多个满足条件的控件,会返回第一个。属性的搜索可以叠加搜索,比如使用Text筛选出一组控件,还可以使用descirption继续筛选。一般配合UiObject来使用,最终返回一个UiObject对象
  • UiCollection:按照一定的条件列举出界面中所有符合条件的元素,然后在根据内容的文本或者可见内容描述来定位界面上的一个控件或者一个元素。UiCollection对应Android中的ViewGrop
  • UiScrollable:用来处理可滚动控件的滚动操作。
  • Configurator:用来设置用于运行 UI Automator测试的关键参数

官方demo

下面开始使用UI Automator来测试一下系统的计算器,完成一个1+9=10的操作。虽然前面介绍了一堆UI Automator API相关的类,不过用起来的时候很简单,使用官方封装的By类可以很方便的找到对应的界面元素。

打开AndroidStudio,新建一个项目,首先在app下的build.gradle中引入UI Automator的依赖:

androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'

当新建完一个项目的时候,会看到项目中有两个测试相关的文件夹app/src/androidTestapp/src/test

  • app/src/androidTest 主要是运行在真实手机或者模拟器上的测试,比如集成测试,端到端测试,以及仅靠JVM无法完成的功能验证测试
  • app/src/test 在本地计算机上运行的测试,比如单元测试

UI Automator属于运行在手机端,所以去项目中找到app/src/androidTest目录,在这里面就可以编写UI Automator的测试代码了。新建一个测试类CalculateTest

//执行单元测试的执行类
@RunWith(AndroidJUnit4.class)
//4.3以上系统可以使用
@SdkSuppress(minSdkVersion = 18)
public class CalculateTest {......}
  • 指定执行单元测试的执行类为AndroidJUnit4
  • 限制最小运行的sdk是18

一个测试类主要包括三部分,分别用3个注解表示:

  • @Before 测试之前执行,一般用来创建对象或者一些准备工作
  • @Test 用来标注测试方法,可以有多个
  • @After 测试完成之后执行,可以做一些清理工作

想要定位界面上的一个元素,可以通过它的resource-id,包名,文本,类名,内容等信息来定位,如果要测试的应用没有源码,可以通过uiautomatorviewer.bat来获取如下图,点击某个元素,右边就会显示出它的一些信息。

//执行单元测试的执行类
@RunWith(AndroidJUnit4.class)
//4.3以上系统可以使用
@SdkSuppress(minSdkVersion = 18)
public class CalculateTest {

     private UiDevice mUiDevice;

     //测试执行之前的操作
    @Before
    public void startMainActivityFromHomeScreen() {
        mUiDevice = UiDevice.getInstance(getInstrumentation());
        //点击home键
        mUiDevice.pressHome();
        // 获取启动页的包名,并判断是否为空
        final String launcherPackage = getLauncherPackageName();
        assertThat(launcherPackage, notNullValue());
        //等待启动完成
        mUiDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), 3000);

        //启动计算器程序
        Context context = getApplicationContext();
        //通过包名创建启动的intent
        Intent intent = context.getPackageManager()
                .getLaunchIntentForPackage("com.android.calculator2");
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        context.startActivity(intent);
        //等待启动完成
        mUiDevice.wait(Until.hasObject(By.pkg("com.android.calculator2").depth(0)),3000);
    }

    @Test
    public void calculate() throws InterruptedException, UiObjectNotFoundException {
        //点击1
        Thread.sleep(1000);
        mUiDevice.pressKeyCode(KeyEvent.KEYCODE_1);
        Thread.sleep(1000);
        //点击加号
        mUiDevice.findObject(By.res("com.android.calculator2:id/op_add")).click();
        Thread.sleep(1000);
        //点击9
        mUiDevice.findObject(By.text("9")).click();
        Thread.sleep(1000);
        //点击等号
        mUiDevice.findObject(By.desc("等于")).click();
        Thread.sleep(1000);
        //断言验证结果是否正确
        //模拟器
        UiObject2 result = mUiDevice.findObject(By.res("com.android.calculator2:id/result"));
        //华为mate20
//        UiObject2 result = mUiDevice.findObject(By.res("com.android.calculator2:id/formula"));
        //使用UiSelector的方式查找
//        UiObject result = mUiDevice.findObject(new UiSelector().resourceId("com.android.calculator2:id/result"));
        Assert.assertEquals("10",result.getText());
    }

    @After
    public void clearNum() {
        //测试完成之后,点击clear键清除界面上是数字
        //模拟器
        mUiDevice.findObject(By.res("com.android.calculator2:id/clr")).click();
        //华为mate20
//        mUiDevice.findObject(By.res("com.android.calculator2:id/op_clr")).click();
    }

    /**
     * 获取程序的启动包名
     * @return
     */
    private String getLauncherPackageName() {
        final Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        // 通过PackageManager获取启动的包名
        PackageManager pm = getApplicationContext().getPackageManager();
        ResolveInfo resolveInfo = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
        return resolveInfo.activityInfo.packageName;
    }
}
  • 代码中尝试了几种不同方式找到界面上的一个元素控件,比如通过keyCode,通过resourceID,通过text,通过desc。还有别的方式可以进By类中查看。属性筛选可以叠加
  • 代码中一个控件的各种属性通过uiautomatorviewer工具获取,工具位置在sdk/tools/bin/uiautomatorviewer.bat,双击打开就可以了。点击左上角的Device ScreenShot按钮,就能获取当前手机屏幕的快照信息。点击目标控件就能在右边看到该控件的各种属性信息如图。
  • 寻找一个界面控件可以使用mUiDevice.findObject(By...的方式也可以使用mUiDevice.findObject(new UiSelector()....的方式,分别返回UiObject和UiObject2
  • 每个步骤之间最好延时一下,防止测试机还没运行完当前指令后面的指令就执行了。

最后是运行结果:

运行完之后可以在下面的地方导出测试报告

可以在指定目录生成一个HTML文件,打开文件就可以查看测试的一些信息。

注意: 官方建议只有在应用必须要跟系统或者第三方应用有一定的交互的时候,才使用UI Automator来测试。一般情况下如果只是测试自己的应用,建议使用Espresso来代替UI Automator。而且新建一个项目的时候,Espresso相关的依赖都已经在项目中默认引入了,所以官方更推荐我们使用Espresso来测试我们的应用,下一篇来练习Espresso的使用。