UI Automator: 丰富 Espresso Android UI 测试
添加几行代码来测试应用对设备旋转的反应
现在我们有了一个在不同屏幕上呈现不同布局的应用, 截图测试是验证布局的一个良好而有效的选择, 但我们如何在Espresso UI测试中测试应用在旋转设备时应用正确布局的能力呢?
我们需要UI Automator。👈
什么是 UI Automator?
UI Automator 是为 Android 应用创建自动化UI测试的工具. 它是 Android 测试支持库的一部分. 它使我们能够在被测应用和不同应用之间执行任务, 因此对于全面集成和端到端测试而言, 它非常有价值。
UI Automator 及其与 Espresso 的关系
虽然 UI Automator 为 Android 界面测试提供了出色的工具, 但人们经常讨论它与 Espresso 的关系. 正如官方文档所建议的, UI Automator 和 Espresso 有一些功能重叠, 但 Espresso 有更多的同步机制, 因此对于常见的 UI 测试, 它是首选。
范围
-
UI Automator允许我们对设备上的任何可见元素执行操作, 包括系统应用和硬件按钮. 因此, 它非常适合测试扩展到被测应用之外的用户流。
-
Espresso仅限于在被测应用内进行交互, 可对应用组件和状态进行更精细的控制。
使用案例
-
Espresso 擅长测试应用内的复杂交互, 如输入表单, 对话框和转换。
-
UI Automator 更适合我们必须与多个应用进行交互, 或在通知或不同网络状态等系统条件下检查应用行为的情况。
性能
-
一般来说, Espresso 比 UI Automator 更快, 因为它直接与应用交互, 而UI Automator 可能会遇到一些问题。
-
UI Automator 可能会出现延迟, 因为它会扫描整个屏幕来执行操作。
结合使用 UI Automator 和 Espresso
将 UI Automator 和 Espresso 结合使用可发挥这两个框架的优势. 例如, Espresso可用于在我们的应用中进行详细测试, 而UI Automator则可以处理像权限对话框这样的情况, 这是Android操作系统的一部分, 还可以帮助改变屏幕方向。
这种组合为UI测试提供了一种全面的方法, 可确保应用特定功能的稳健性以及与系统级功能的集成。
设置UI自动化程序
假设我们的项目已为AndroidX测试正确设置. 要在项目中引入 UI Automator, 只需要一个新的依赖项。
dependencies {
...
androidTestImplementation('androidx.test.uiautomator:uiautomator:2.3.0')
}
那么, 我们如何在测试中改变屏幕方向呢?
鉴于我们已经有了一些 Espresso UI 测试, 我们只需获取一个UIDevice的实例, 然后就可以开始了。
import androidx.test.uiautomator.UiDevice;
import androidx.test.platform.app.InstrumentationRegistry;
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
@After
fun tearDown() {
uiDevice.setOrientationNatural()
}
@Test
fun appNavigationLayoutTest() {
with(mainActivityTestRobot) {
// Rotate to landscape
uiDevice.setOrientationLeft()
checkNavigationLayoutIsCorrect()
// Rotate to portrait
uiDevice.setOrientationNatural()
checkNavigationLayoutIsCorrect()
}
}
}
💡 InstrumentationRegistry.getInstrumentation()是来自androidx.test.espresso包。
我们如何运行与 WindowSizeClass 匹配的测试?
由于我们可以在不同的设备/模拟器上运行UI测试, 因此我们的生产代码应正确计算WindowSizeClass, 并相应地渲染预期布局. 如何确保使用正确的测试集来测试相应的屏幕尺寸/布局?
我们可以在测试中使用上文提到的InstrumentationRegistry来确定设备屏幕分辨率. 使用WindowSizeClass库中的WindowSizeClass.calculateFromSize(...)方法, 我们可以计算出当前的WindowSizeClass, 并有条件地针对该屏幕尺寸运行一组测试。
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
fun getWindowSizeClass(): WindowSizeClass {
val metrics = InstrumentationRegistry
.getInstrumentation().targetContext.resources.displayMetrics
val widthPx = metrics.widthPixels
val heightPx = metrics.heightPixels
val density = metrics.density
val widthDp = widthPx / density
val heightDp = heightPx / density
return WindowSizeClass.calculateFromSize(
size = DpSize(width = widthDp.dp, height = heightDp.dp)
)
}
@Test
fun checkNavigationLayoutIsCorrect() {
// ...
val windowWidthSizeClass = getWindowSizeClass().widthSizeClass
if (windowWidthSizeClass == WindowWidthSizeClass.Compact) {
assertNavigationBarIsDisplayed()
} else {
assertNavigationRailIsDisplayed()
}
// ...
}
💡 我们在生产代码中使用的calculateWindowSizeClass()只能在可组合函数中运行, 因此我采用了这种变通方法。
触发下拉刷新的另一种方法
使用 Espresso, 我们可以使用.performTouchInput来触发一个swipeDown操作, 从而在我们的 Jetpack Compose UI 上模拟拉动刷新操作。
例如:
...
onNodeWithContentDescription(label = "pull_to_refresh")
.performTouchInput {
swipeDown(
startY = 0f,
endY = 500f,
durationMillis = 1_000,
)
}
....
这要求我们在向下滑动之前首先找到一个现有的节点。
要使用 UI Automator 创建更逼真的下拉刷新测试场景, 我们可以直接在屏幕上模拟轻扫动作, 而无需先定位特定的UI元素. 这种方法可以模拟用户在屏幕上的任何位置自然地执行轻扫手势来启动拉动刷新, 而无需考虑具体的UI布局或元素位置。
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
// Get the screen size
val screenWidth = device.displayWidth
val screenHeight = device.displayHeight
// Calculate start and end positions for the swipe gesture
val startX = screenWidth / 2 // Swipe from middle of the screen width
val startY = screenHeight / 4 // Start swipe from 1/4th down the screen
val endX = startX // End swipe at the same X-coordinate
val endY = startY + (screenHeight / 2) // End swipe halfway down the screen
// Perform the swipe gesture
device.swipe(startX, startY, endX, endY, 50) // 50 steps to make the swipe smooth
💡 作为折衷, 轻扫动作需要更长的时间. 由于每一步的执行时间被控制在 5 毫秒, 因此在 100 步的情况下, 轻扫动作需要大约 1/2 秒才能完成。
使用 UI Automator 测试通知
如果你还在阅读, 那么让我们将 UI Automator 集成到 Espresso UI 测试中, 看看还有哪些可能的用例。
处理系统通知并与之交互是 UI Automator 的另一个亮点, 因为它可以在测试应用的上下文之外进行交互。
@Test
fun checkNotifcationMessage() {
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.openNotification()
// Assuming the notification has text content "New Message"
val notification = device.findObject(UiSelector().textContains("New Message"))
if (notification.exists() && notification.isEnabled) {
notification.click()
// Espresso tests to check app behaviour after clicking the notification
}
}
切换飞行模式--UI Automator 执行 Shell 命令的强大功能
在 UI 测试中模拟实际的 UI 操作来更改某些设备设置具有挑战性, 比如切换飞行模式。为此, UI Automator 提供了`executeShellCommand()方法。
@Test
fun checkThingsShouldWorkInAirplaneMode() {
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
// Command to enable airplane mode
device.executeShellCommand("settings put global airplane_mode_on 1")
// Command to refresh the system's airplane mode state
device.executeShellCommand("am broadcast -a android.intent.action.AIRPLANE_MODE --ez state true")
// Add a delay or wait for the state to change
// Test code to verify app behavior with no network
// Command to disable airplane mode
device.executeShellCommand("settings put global airplane_mode_on 0")
device.executeShellCommand("am broadcast -a android.intent.action.AIRPLANE_MODE --ez state false")
}
注意事项和权限:
-
权限: 确保我们的测试设置和环境允许执行这些操作. 修改全局设置需要WRITE_SECURE_SETTINGS权限, 非 root 设备上的普通应用无法获得该权限。
-
已 root 设备和模拟器: 在已 root 设备或模拟器上运行此类测试更为直接, 因为我们可以更好地控制设备设置。
-
影响: 请注意使用此类命令的广泛影响. 更改设备设置会影响测试中的应用, 其他应用和设备的正常运行。
💡 对于网络测试, 在可能的情况下, 在控制的测试环境中使用网络模拟可能仍是首选, 以保持一致性并便于设置。
总结
将 UI Automator 与 Espresso 集成是增强 Android UI 测试的绝佳方法. 它使我们能够在测试中模拟真实世界的场景, 如设备旋转和系统通知。
这种工具组合使我们能够超越应用的边界进行测试, 并确保我们的应用在各种条件下的表现符合预期。
只需添加几行代码, 我们就能进行更彻底, 更全面的测试, 确保我们的应用符合现代可用性标准, 并提供卓越的用户体验。
最后作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些软件测试的学习资源,希望能给你前进的路上带来帮助。
视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。