Android 提供了各种工具和 API,可以帮助您创建针对不同屏幕和窗口尺寸的测试。
DeviceConfigurationOverride
DeviceConfigurationOverride
可组合函数允许您覆盖配置属性,以测试 Compose 布局中的多个屏幕和窗口尺寸。 ForcedSize
覆盖函数将任何布局放入可用空间中,这使您可以在任何屏幕尺寸上运行任何 UI 测试。例如,您可以使用小型手机外形尺寸运行所有 UI 测试,包括大型手机、可折叠设备和平板电脑的 UI 测试。
DeviceConfigurationOverride(
DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
) {
MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
}
此外,您还可以使用此可组合函数设置字体大小、主题和其他可能要在不同窗口尺寸上测试的属性。
Robolectric
使用 Robolectric 在 JVM 上本地运行 Compose 或基于视图的 UI 测试,无需使用设备或模拟器。您可以 配置 Robolectric 以使用特定屏幕尺寸,以及其他有用属性。
在以下 示例 中(来自“Android 新闻”),Robolectric 配置为模拟 1000x1000 dp 的屏幕尺寸,分辨率为 480 dpi。
@RunWith(RobolectricTestRunner::class)
// Configure Robolectric to use a very large screen size that can fit all of the test sizes.
// This allows enough room to render the content under test without clipping or scaling.
@Config(qualifiers = "w1000dp-h1000dp-480dpi")
class NiaAppScreenSizesScreenshotTests { ... }
您也可以像在此代码段中所做的那样,从测试主体设置限定符,此代码段来自 “Android 新闻” 示例。
val (width, height, dpi) = ...
// Set qualifiers from specs.
RuntimeEnvironment.setQualifiers("w${width}dp-h${height}dp-${dpi}dpi")
请注意,RuntimeEnvironment.setQualifiers()
会使用新配置更新系统和应用程序资源,但不会对活动活动或其他组件触发任何操作。
您可以在 Robolectric 的 设备配置 文档中了解更多信息。
Gradle 管理的设备
Android Gradle 插件的 Gradle 管理的设备 (GMD) 允许您定义模拟器和真实设备的规格,这些模拟器和真实设备将运行您的 工具测试。创建具有不同屏幕尺寸的设备规格,以实施测试策略,其中某些测试必须在某些屏幕尺寸上运行。通过将 GMD 与 持续集成 (CI) 相结合,您可以确保在需要时运行适当的测试,配置和启动模拟器,并简化您的 CI 设置。
android {
testOptions {
managedDevices {
devices {
// Run with ./gradlew nexusOneApi30DebugAndroidTest.
nexusOneApi30(com.android.build.api.dsl.ManagedVirtualDevice) {
device = "Nexus One"
apiLevel = 30
// Use the AOSP ATD image for better emulator performance
systemImageSource = "aosp-atd"
}
// Run with ./gradlew foldApi34DebugAndroidTest.
foldApi34(com.android.build.api.dsl.ManagedVirtualDevice) {
device = "Pixel Fold"
apiLevel = 34
systemImageSource = "aosp-atd"
}
}
}
}
}
您可以在 testing-samples 项目中找到 GMD 的多个示例。
Firebase 测试实验室
使用 Firebase 测试实验室 (FTL) 或类似的设备农场服务,在您可能无法访问的特定真实设备上运行测试,例如不同尺寸的可折叠设备或平板电脑。Firebase 测试实验室是 一项付费服务,提供免费层级。FTL 还支持在模拟器上运行测试。这些服务通过提前配置设备和模拟器,提高了仪器测试的可靠性和速度。
有关将 FTL 与 GMD 配合使用的信息,请参阅 使用 Gradle 管理的设备扩展测试范围。
使用测试运行器过滤测试
最佳测试策略不应重复验证同一事项,因此大多数 UI 测试无需在多个设备上运行。通常,您可以通过在手机外形规格上运行所有或大多数 UI 测试,并在不同屏幕尺寸的设备上仅运行一小部分测试来过滤 UI 测试。
您可以使用批注对某些测试进行标注,使其仅在特定设备上运行,然后使用运行测试的命令将参数传递给 AndroidJUnitRunner。
例如,您可以创建不同的批注
annotation class TestExpandedWidth
annotation class TestCompactWidth
并将其用于不同的测试
class MyTestClass {
@Test
@TestExpandedWidth
fun myExample_worksOnTablet() {
...
}
@Test
@TestCompactWidth
fun myExample_worksOnPortraitPhone() {
...
}
}
然后,您可以在运行测试时使用 android.testInstrumentationRunnerArguments.annotation
属性过滤特定的测试。例如,如果您使用的是 Gradle 管理的设备
$ ./gradlew pixelTabletApi30DebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation='com.sample.TestExpandedWidth'
如果您没有使用 GMD,并且在 CI 上管理模拟器,请首先确保正确的模拟器或设备已准备就绪并已连接,然后将参数传递给用于运行仪器测试的 Gradle 命令之一
$ ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation='com.sample.TestExpandedWidth'
请注意,Espresso Device(请参阅下一部分)也可以使用设备属性过滤测试。
Espresso Device
使用 Espresso Device 在任何类型的仪器测试(包括 Espresso、Compose 或 UI Automator 测试)中对模拟器执行操作。这些操作可能包括设置屏幕尺寸或切换可折叠状态或姿势。例如,您可以控制可折叠模拟器并将其设置为桌面模式。Espresso Device 还包含 JUnit 规则和批注,以要求某些功能
@RunWith(AndroidJUnit4::class)
class OnDeviceTest {
@get:Rule(order=1) val activityScenarioRule = activityScenarioRule<MainActivity>()
@get:Rule(order=2) val screenOrientationRule: ScreenOrientationRule =
ScreenOrientationRule(ScreenOrientation.PORTRAIT)
@Test
fun tabletopMode_playerIsDisplayed() {
// Set the device to tabletop mode.
onDevice().setTabletopMode()
onView(withId(R.id.player)).check(matches(isDisplayed()))
}
}
请注意,Espresso Device 仍处于 Alpha 阶段,并且具有以下要求
- Android Gradle 插件 8.3 或更高版本
- Android 模拟器 33.1.10 或更高版本
- 运行 API 级别 24 或更高版本的 Android 虚拟设备
过滤测试
Espresso Device 可以读取已连接设备的属性,使您能够使用 批注 过滤测试。如果满足批注的要求,则跳过测试。
RequiresDeviceMode 批注
可以使用 RequiresDeviceMode
批注多次,以指示仅当设备支持所有 DeviceMode
值时才会运行的测试。
class OnDeviceTest {
...
@Test
@RequiresDeviceMode(TABLETOP)
@RequiresDeviceMode(BOOK)
fun tabletopMode_playerIdDisplayed() {
// Set the device to tabletop mode.
onDevice().setTabletopMode()
onView(withId(R.id.player)).check(matches(isDisplayed()))
}
}
RequiresDisplay 批注
使用 RequiresDisplay
批注,您可以使用 尺寸类别 指定设备屏幕的宽度和高度,这些类别根据官方的 窗口尺寸类别 定义尺寸范围。
class OnDeviceTest {
...
@Test
@RequiresDisplay(EXPANDED, COMPACT)
fun myScreen_expandedWidthCompactHeight() {
...
}
}
调整显示尺寸
使用 setDisplaySize()
方法在运行时调整屏幕尺寸。将此方法与 DisplaySizeRule
类结合使用,该类确保在测试期间进行的任何更改都将在下一个测试之前撤消。
@RunWith(AndroidJUnit4::class)
class ResizeDisplayTest {
@get:Rule(order = 1) val activityScenarioRule = activityScenarioRule<MainActivity>()
// Test rule for restoring device to its starting display size when a test case finishes.
@get:Rule(order = 2) val displaySizeRule: DisplaySizeRule = DisplaySizeRule()
@Test
fun resizeWindow_compact() {
onDevice().setDisplaySize(
widthSizeClass = WidthSizeClass.COMPACT,
heightSizeClass = HeightSizeClass.COMPACT
)
// Verify visual attributes or state restoration.
}
}
使用 setDisplaySize()
调整显示尺寸时,不会影响设备的密度,因此如果尺寸不适合目标设备,则测试将失败并出现 UnsupportedDeviceOperationException
。为了防止在这种情况下运行测试,请使用 RequiresDisplay
批注将其过滤掉
@RunWith(AndroidJUnit4::class)
class ResizeDisplayTest {
@get:Rule(order = 1) var activityScenarioRule = activityScenarioRule<MainActivity>()
// Test rule for restoring device to its starting display size when a test case finishes.
@get:Rule(order = 2) var displaySizeRule: DisplaySizeRule = DisplaySizeRule()
/**
* Setting the display size to EXPANDED would fail in small devices, so the [RequiresDisplay]
* annotation prevents this test from being run on devices outside the EXPANDED buckets.
*/
@RequiresDisplay(
widthSizeClass = WidthSizeClassEnum.EXPANDED,
heightSizeClass = HeightSizeClassEnum.EXPANDED
)
@Test
fun resizeWindow_expanded() {
onDevice().setDisplaySize(
widthSizeClass = WidthSizeClass.EXPANDED,
heightSizeClass = HeightSizeClass.EXPANDED
)
// Verify visual attributes or state restoration.
}
}
StateRestorationTester
使用 StateRestorationTester
类测试可组合组件的状态恢复,而无需重新创建活动。这使测试更快、更可靠,因为活动重新创建是一个复杂的流程,涉及多种同步机制
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
val stateRestorationTester = StateRestorationTester(composeTestRule)
// Set content through the StateRestorationTester object.
stateRestorationTester.setContent {
MyApp()
}
// Simulate a config change.
stateRestorationTester.emulateSavedInstanceStateRestore()
}
Window Testing 库
Window Testing 库包含实用程序,可帮助您编写依赖于或验证与窗口管理相关的功能(例如 活动嵌入 或可折叠功能)的测试。此工件可通过 Google 的 Maven 存储库 获得。
例如,您可以使用 FoldingFeature()
函数生成自定义的 FoldingFeature
,您可以在 Compose 预览中使用它。在 Java 中,请使用 createFoldingFeature()
函数。
在 Compose 预览中,您可能以以下方式实现 FoldingFeature
@Preview(showBackground = true, widthDp = 480, heightDp = 480)
@Composable private fun FoldablePreview() =
MyApplicationTheme {
ExampleScreen(
displayFeatures = listOf(FoldingFeature(Rect(0, 240, 480, 240)))
)
}
此外,您可以使用 TestWindowLayoutInfo()
函数在 UI 测试中模拟显示功能。以下示例模拟了一个 FoldingFeature
,其在屏幕中心具有 HALF_OPENED
的垂直铰链,然后检查布局是否符合预期
Compose
import androidx.window.layout.FoldingFeature.Orientation.Companion.VERTICAL
import androidx.window.layout.FoldingFeature.State.Companion.HALF_OPENED
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
@RunWith(AndroidJUnit4::class)
class MediaControlsFoldingFeatureTest {
@get:Rule(order=1)
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
@get:Rule(order=2)
val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()
@Test
fun foldedWithHinge_foldableUiDisplayed() {
composeTestRule.setContent {
MediaPlayerScreen()
}
val hinge = FoldingFeature(
activity = composeTestRule.activity,
state = HALF_OPENED,
orientation = VERTICAL,
size = 2
)
val expected = TestWindowLayoutInfo(listOf(hinge))
windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(expected)
composeTestRule.waitForIdle()
// Verify that the folding feature is detected and media controls shown.
composeTestRule.onNodeWithTag("MEDIA_CONTROLS").assertExists()
}
}
视图
import androidx.window.layout.FoldingFeature.Orientation
import androidx.window.layout.FoldingFeature.State
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
@RunWith(AndroidJUnit4::class)
class MediaControlsFoldingFeatureTest {
@get:Rule(order=1)
val activityRule = ActivityScenarioRule(MediaPlayerActivity::class.java)
@get:Rule(order=2)
val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()
@Test
fun foldedWithHinge_foldableUiDisplayed() {
activityRule.scenario.onActivity { activity ->
val feature = FoldingFeature(
activity = activity,
state = State.HALF_OPENED,
orientation = Orientation.VERTICAL)
val expected = TestWindowLayoutInfo(listOf(feature))
windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(expected)
}
// Verify that the folding feature is detected and media controls shown.
onView(withId(R.id.media_controls)).check(matches(isDisplayed()))
}
}
您可以在 WindowManager 项目 中找到更多示例。
其他资源
文档
示例
- WindowManager 示例
- Espresso Device 示例
- Now In Android
- 使用屏幕截图测试验证不同的屏幕尺寸
Codelabs