• 作者:老汪软件技巧
  • 发表时间:2024-09-09 04:01
  • 浏览量:

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

将 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()
    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")
}

注意事项和权限

对于网络测试, 在可能的情况下, 在控制的测试环境中使用网络模拟可能仍是首选, 以保持一致性并便于设置.

总结

将 UI Automator 与 Espresso 集成是增强 Android UI 测试的绝佳方法. 它使我们能够在测试中模拟真实世界的场景, 如设备旋转和系统通知. 这种工具组合使我们能够超越应用的边界进行测试, 并确保我们的应用在各种条件下的表现符合预期.

只需添加几行代码, 我们就能进行更彻底, 更全面的测试, 确保我们的应用符合现代可用性标准, 并提供卓越的用户体验.

一家之言, 欢迎斧正!

Happy Coding! Stay GOLDEN!