本文同步发布在个人博客 使用UI为应用程序编写自动化测试脚本 – 码微
本文将介绍如何基于 编写 UI脚本来自动测试任何应用程序 。将编写一个在设置界面里添加 Wi-Fi 网络的 测试脚本 , 并检查设备是否连接成功
UI 介绍
UI 是一个测试框架 , 它允许我们编写可以与设备中安装的任何应用程序交互的脚本 。UI不需要访问应用程序源代码即可工作 。因此,脚本可以导航并与应用程序托盘、设置应用程序、第三方应用程序或你想要的任何其他应用程序交互 。
创建项目
在里创建一个新项目 。将在项目中创建三个不同的文件夹:main:和test 。这是默认的项目结构 , 其中main包含应用程序代码,test包含在开发机器上运行的单元测试,即默认情况下进行的检测测试,如 UI测试 。
如果是给当前的应用程序编写测试脚本口袋妖怪个体值计算器 for android,那么可以在目录中创建脚本,
但在本文中,我们仅使用 UI脚本创建项目,主要测试另一个应用程序 , 所以在开始需要做一些修改:
打开app模块的build.口袋妖怪个体值计算器 for android,在节点下添加以下代码:
sourceSets {androidTest {java.srcDir 'src/main/java'}}
可以同时删除test和目录 。同时删除一些不再使用的资源:
app/src/main/res/values/themes.xml app/src/main/res/values-night/themes.xml app/src/main/res/values/colors.xml
配置好目录后,让我们添加所需的依赖项 。我们需要使用而不是添加依赖项ation 。你还可以删除不会使用的依赖项:
dependencies {implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"implementation 'androidx.test.ext:junit:1.1.3'implementation 'androidx.test:runner:1.4.0'implementation 'androidx.test.uiautomator:uiautomator:2.2.0'}
更改后 , 你build.将如下所示:
plugins {id 'com.android.application'id 'kotlin-android'}android {compileSdkVersion 30buildToolsVersion "30.0.3"defaultConfig {applicationId "com.paceli.wifitest"minSdkVersion 18targetSdkVersion 30versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}kotlinOptions {jvmTarget = '1.8'}sourceSets {androidTest {java.srcDir 'src/main/java'}}}dependencies {implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"implementation 'androidx.test.ext:junit:1.1.3'implementation 'androidx.test:runner:1.4.0'implementation 'androidx.test.uiautomator:uiautomator:2.2.0'}
需要手动将标签添加到.xml文件中:
在节点下添加子节点,
的name属性必须定义为.test..
必须和当前应用包名保持一致
修改后的.xml以下更改:
编写脚本
创建并配置项目后 , 创建一个新类并添加注释@(::class),定义为测试运行器 。
与 JUnit 一样 , 测试方法必须使用@Test. 启动(初始化)和停止(反初始化)方法必须分别用注释@和@After 。如下代码所示:
package com.paceli.wifitestimport android.util.Logimport androidx.test.ext.junit.runners.AndroidJUnit4import org.junit.*import org.junit.runner.RunWith@RunWith(AndroidJUnit4::class)class UiAutomatorOrder {/*** Run before the method with @Test annotation*/@Beforefun before() {Log.d(TAG, "Before")}/*** Run after the method with @Before annotation* and before methods with @After annotation*/@Testfun test() {Log.d(TAG, "Test")}/*** Run after each method with @Test annotation*/@Afterfun after() {Log.d(TAG, "After")}companion object {private const val TAG = "UiAutomatorExample"/*** Run once before other methods from [UiAutomatorOrder] class*/@JvmStatic@BeforeClassfun beforeClass() {Log.d(TAG, "Before Class")}/*** Run once after other methods from [UiAutomatorOrder] class*/@JvmStatic@AfterClassfun afterClass() {Log.d(TAG, "After Class")}}}
运行此示例将产生以下输出:
Before ClassBeforeTestAfterAfter Class
现在在测试类中创建一个名为的方法并标注@Test.
为了点击按钮、从屏幕读取文本、执行滑动手势以及与 UI 的任何其他元素交互,我们需要获取类的实例
此操作在init函数中完成
package com.paceli.wifitestimport androidx.test.ext.junit.runners.AndroidJUnit4import androidx.test.platform.app.InstrumentationRegistryimport androidx.test.uiautomator.UiDeviceimport org.junit.Testimport org.junit.runner.RunWith@RunWith(AndroidJUnit4::class)class WifiTest {private val device: UiDeviceinit {val instrumentation = InstrumentationRegistry.getInstrumentation()device = UiDevice.getInstance(instrumentation)}@Testfun validateWifi() {}}
要与屏幕上的元素进行交互,我们需要使用实例获取对它们的引用 。为此,我们将调用方法传递我们想要与之交互的元素的属性 。一些属性,比如文本,对用户是可见的,但还有一些我们在设备屏幕上看不到,所以需要使用SDK 中的UI工具($/tools/bin/) 获取屏幕信息
通过单击左上角的 按钮,该工具将显示当前ADB连接的设备的屏幕截图和屏幕dump信息 。
可以通过在屏幕截图或右侧的层次结构视图中单击元素来选择元素 。index, text, -desc,等属性显示在Node 视图中 。
类表示来自屏幕的元素,它的一个实例由方法返回 。为了启动设置应用,用户需要在主屏幕上执行滚动手势以启动应用程序列表 , 然后单击设置图标 。于是我们得到了一个的实例 , 代表了应用列表界面中的设置图标,然后分别调用了和click方法 。
@Testfun validateWifi() {// Open apps list by scrolling on home screenval workspace = device.findObject(By.res("com.google.android.apps.nexuslauncher:id/workspace"))workspace.scroll(Direction.DOWN, 1.0f)// Click on Settings icon to launch the appval settings = device.findObject(By.res("com.google.android.apps.nexuslauncher:id/icon").text("Settings"))settings.click()}
如果某些元素需要一些时间才能显示在屏幕上,我们可以使用方法wait,此方法的参数为和 。下面调用这个方法打开该 & 部分,然后继续到Add 屏幕
// ...// Wait up to 2 seconds for the element be displayed on screenval networkAndInternet = device.wait(Until.findObject(By.text("Network & internet")), 2000)networkAndInternet.click()// Click on element with text "Wi?Fi"val wifi = device.wait(Until.findObject(By.text("Wi?Fi")), 2000)wifi.click()// Click on element with text "Add network"val addNetwork = device.wait(Until.findObject(By.text("Add network")), 2000)addNetwork.click()
在Add 屏幕上,有一个文本字段,用户必须在其中输入网络 SSID 。要在 UI脚本中输入文本,我们只需要获取此字段的实例并调用传递我们要输入的字符串
下面的代码展示输入 SSID ,然后单击保存按钮添加它 。
// ...// Obtain an instance of UiObject2 of the text fieldval ssidField = device.wait(Until.findObject(By.res("com.android.settings:id/ssid")), 2000)// Call the setText method usingKotlin's property access syntaxval ssid = "AndroidWifi"ssidField.text = ssid//Click on Save buttondevice.findObject(By.res("android:id/button1").text("Save")).click()
为了检查Wi-Fi是否正确添加以及设备是否连接到它,可以简单地检查一下屏幕上是否显示,为此,让我们使用方法 , 该方法返回一个布尔值,指示某个元素当前是否正在屏幕上显示 。
// ...// BySelector matching the just added Wi-Fival ssidSelector = By.text(ssid).res("android:id/title")// BySelector matching the connected statusval status = By.text("Connected").res("android:id/summary")// BySelector matching on entry of Wi-Fi list with the desired SSID and statusval networkEntrySelector = By.clazz(RelativeLayout::class.qualifiedName).hasChild(ssidSelector).hasChild(status)// Perform the validation using hasObject// Wait up to 5 seconds to find the element we're looking forval isConnected = device.wait(Until.hasObject(networkEntrySelector), 5000)Assert.assertTrue("Verify if device is connected to added Wi-Fi", isConnected)
我们还可以选择使用API 来获取设备连接的当前 Wi-Fi 的 SSID 。这是可能的,因为 UI脚本作为应用程序运行,因此它可以访问应用程序开发过程中常用的 API,如、系统服务、等 。
为了能够获取 Wi-Fi SSID , 请将这些权限添加到.xml:
以下方法获取应用程序的(UI脚本)并使用它来获取的实例并获取 Wi-Fi SSID 。我们将使用返回值与我们之前添加的网络名称进行比较 。
private fun getCurrentWifiSsid(): String? {val context = InstrumentationRegistry.getInstrumentation().contextval wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManagerval wifiInfo = wifiManager.connectionInfo// The SSID is quoted, then we need to remove quotesreturn wifiInfo.ssid?.removeSurrounding(""")}
现在测试方法已准备就绪:
@Testfun validateWifi() {// Open apps list by scrolling on home screenval workspace = device.findObject(By.res("com.google.android.apps.nexuslauncher:id/workspace"))workspace.scroll(Direction.DOWN, 1.0f)// Click on Settings icon to launch the appval settings = device.findObject(By.res("com.google.android.apps.nexuslauncher:id/icon").text("Settings"))settings.click()// Wait up to 2 seconds for the element be displayed on screenval networkAndInternet = device.wait(Until.findObject(By.text("Network & internet")), 2000)networkAndInternet.click()// Click on element with text "Wi?Fi"val wifi = device.wait(Until.findObject(By.text("Wi?Fi")), 2000)wifi.click()// Click on element with text "Add network"val addNetwork = device.wait(Until.findObject(By.text("Add network")), 2000)addNetwork.click()// Obtain an instance of UiObject2 of the text fieldval ssidField = device.wait(Until.findObject(By.res("com.android.settings:id/ssid")), 2000)// Call the setText method usingKotlin's property access syntaxval ssid = "AndroidWifi"ssidField.text = ssid//Click on Save buttondevice.findObject(By.res("android:id/button1").text("Save")).click()// BySelector matching the just added Wi-Fival ssidSelector = By.text(ssid).res("android:id/title")// BySelector matching the connected statusval status = By.text("Connected").res("android:id/summary")// BySelector matching on entry of Wi-Fi list with the desired SSID and statusval networkEntrySelector = By.clazz(RelativeLayout::class.qualifiedName).hasChild(ssidSelector).hasChild(status)// Perform the validation using hasObject// Wait up to 5 seconds to find the element we're looking forval isConnected = device.wait(Until.hasObject(networkEntrySelector), 5000)Assert.assertTrue("Verify if device is connected to added Wi-Fi", isConnected)// Perform the validation using Android APIsval connectedWifi = getCurrentWifiSsid()Assert.assertEquals("Verify if is connected to the Wifi", ssid, connectedWifi)}
要拥有完整的测试脚本,我们需要在执行实际测试之前进行一些设置 。如果你不是在主屏幕时运行脚本,你会注意到抛出了 发生这种情况是因为我们假设测试开始时设备位于主屏幕中 。为了保证这一点,请在@注释中添加以下设置方法 。
@Beforefun setUp() {// Press Home key before running the testdevice.pressHome()}
现在,Home在运行测试之前将始终按下该键 。你可能还想在测试执行后返回主屏幕,为此只需添加另一个带有@After注释的方法 , 类似于我们对设置方法所做的 。
@Afterfun tearDown() {// Press Home key after running the testdevice.pressHome()}
总之 , setUp方法用于确保设备处于所需的初始状态 。方法负责在运行测试后进行清理,以免影响后续的方法 。假设你正在编写一个向设备添加密码的脚本 。必须在拆卸方法中删除此密码,否则下一个脚本可能会卡在密码屏幕中 。
使用 ADB 运行脚本
运行前需要现在 中构建APK , 点击Build -> Make 即可
构建完成后通过ADB安装:
adb install -r -g app-debug.apk
执行:
adb shell am instrument -w -e class 'com.paceli.wifitest.WifiTest' com.paceli.wifitest/androidx.test.runner.AndroidJUnitRunnercom.paceli.wifitest.WifiTest — 是要执行的测试脚本的类名 。com.paceli.wifitest/androidx.test.runner.AndroidJUnitRunner — 是测试包名和运行器类 , 格式为/.
结论
希望本文可以帮助你创建自己的测试脚本,提高应用程序的测试覆盖率并减少手动工作
【使用UI Automator 为 Android 应用程序编写自动化测试脚本】本文到此结束,希望对大家有所帮助 。
- 冰墩墩以什么为原型进行创作
- ?包贝尔晒照为妻子庆生 包文婧与蛋糕合影露甜笑
- 李易峰李多海恋情为什么分手 ?李易峰李多海在一起了吗
- 男人到中年需要什么样的女人 ?女人到了中年为什么不需要男人
- 红酒为什么要醒酒?醒酒是什么意思? ?红酒为什么需要醒酒? 醒酒是什么意思?
- 猫为什么那么贱
- 手机信号4G满格,但玩王者荣耀为什么还是卡到460?
- 很多人说《大鱼海棠》中的椿不配得到湫的爱,这是为什么?
- 《复仇者联盟3》中超级英雄众多,为什么灭霸唯独就认识钢铁侠一个?
- 画唐卡为什么短命