您是否曾经发布过存在界面问题的应用程序?
在当今快节奏的发展背景下,这种情况变得愈发频繁——即便对那些有专门的人工QA和自动化UI测试人员的团队也是如此。
我们常低估UI(用户界面)问题的重要性。但这些问题远不只是按钮错位那么简单。许多UI缺陷会导致应用无法正常使用,例如:
- 文本色差导致可读性不足
- 不完善的UI损害品牌可信度
- 负面评价导致下载量下滑
但是等一下......我已经做过了UI测试啊。
即使您有UI测试,这并不意味着它们验证了像素的完美性。许多UI测试检查组件、屏幕或应用程序的行为,但不会检查像素级还原度。
不同类型的UI测试验证了应用程序的不同方面。使用人造数据的端到端和UI测试侧重于用户行为,但它们不检查元素之间的对齐、明暗模式的颜色正确性以及其他视觉细节。
现在有一个解决方案——那就是视觉测试。
什么是视觉测试?
视觉测试专注于识别组件和屏幕中的像素缺陷。在Android开发中,视觉测试是通过屏幕截图比较来实现的。这涉及到将UI的当前状态与基线截图(通常称为“黄金基准截图”)所包含的组件或屏幕的预期状态进行比较。
屏幕截图对比技术,允许您精准识别以下问题:
- 元素对齐与间距异常
- 深浅模式及自定义主题中的色彩偏差
- 各种设备(手机、平板电脑、可折叠设备)和字体大小的布局问题
- 从右到左(RTL)和从左到右(LTR)布局中的版式渲染错误,这通常与区域设置有关
- 本地化问题,例如当某些语言的内容超过可用空间时,文本就会溢出
- 与显示内容相关的辅助功能问题,例如颜色对比度问题
- 特定场景中的意外渲染问题
比如下图:
Android(安卓)中有多个可用于屏幕截图测试的框架,下面的表格对比了当前一些主流的测试框架:
框架名字 | 测试类型 | 渲染引擎 |
---|---|---|
Shot | 设备测试 | 原生设备渲染 |
Roborazzi | 本地测试 | Robolectric原生图形 |
Paparazzi | 本地测试 | Layoutlib引擎 |
Compose预览测试 | 本地测试 | Layoutlib引擎 |
注:"Layoutlib引擎"是Android Studio用于预览Compose组件的专用渲染引擎。
这些框架基本都遵循类似的工作流程,主要包含两个命令:
- “record”命令为您的测试生成黄金基准截图
- “verify”命令将当前UI状态与黄金基准截图进行比较。
其中命令语法因框架而异。例如,在使用Shot框架时,您将使用-Precord参数来生成基线图像:
./gradlew :app:debugExecuteScreenshotTests -Precord
好的,这看起来很简单,但是来看个具体的例子吧。
视觉测试实战:Shot框架应用示例
为了确保截图测试的一致性,使用一致的模拟数据并使用相同的模拟器或设备(如果您使用仪器测试),这一点至关重要。使用不同的设备或模拟器会导致分辨率变化,并将导致测试失败。
让我们使用Shot框架来探究心情跟踪应用程序中统计屏幕的两个截图测试。测试将侧重于表示空和成功的UI状态(可用于图表渲染的数据)。
companion object {
val TEST_DATE = LocalDate(2024, Month.SEPTEMBER, 15)
}
@get:Rule
val composeTestRule = createComposeRule()
private val dateProvider = mockk<DateProvider>()
private val moodHistoryRepository = mockk<MoodHistoryRepository>()
@Before
fun setUp() {
initDI()
}
@Test
fun statisticsScreen_noData() {
val startDate = TEST_DATE.atStartOfMonth().atStartOfDayIn(defaultTimeZone)
val endDate = TEST_DATE.atEndOfMonth().atEndOfDay()
every { dateProvider.getCurrentDate() } returns TEST_DATE
every { moodHistoryRepository.getAverageDayToHappiness(startDate, endDate) } returns flowOf(emptyList())
every { moodHistoryRepository.getActivityToHappinessByDate(startDate, endDate) } returns flowOf(emptyList())
composeTestRule.setContent {
FeelTrackerAppTheme {
StatisticsScreen(
viewModel = koinViewModel(),
onHome = { },
onBreathingPatternSelection = { },
onSettings = { }
)
}
}
compareScreenshot(
rule = composeTestRule,
name = "statisticsScreen_noData"
)
}
@Test
fun statisticsScreen_hasData() {
val startDate = TEST_DATE.atStartOfMonth().atStartOfDayIn(defaultTimeZone)
val endDate = TEST_DATE.atEndOfMonth().atEndOfDay()
val firstDateOfTheMonth = TEST_DATE.atStartOfMonth()
every { dateProvider.getCurrentDate() } returns StatisticsScreenScreenshotTest.TEST_DATE
every { moodHistoryRepository.getAverageDayToHappiness(startDate, endDate) } returns averageDayToHappinessChartData(firstDateOfTheMonth)
every { moodHistoryRepository.getActivityToHappinessByDate(startDate, endDate) } returns activityToHappinessData()
composeTestRule.setContent {
FeelTrackerAppTheme {
StatisticsScreen(
viewModel = koinViewModel(),
onHome = { },
onBreathingPatternSelection = { },
onSettings = { }
)
}
}
compareScreenshot(
rule = composeTestRule,
name = "statisticsScreen_hasData"
)
}
private fun initDI() {
stopKoin()
startKoin {
allowOverride(true)
androidContext(InstrumentationRegistry.getInstrumentation().targetContext)
modules(
...
module {
single { dateProvider }
single { moodHistoryRepository }
}
)
}
}
private fun averageDayToHappinessChartData(startDate: LocalDate): Flow<List<MoodDayToHappiness>> {
return flowOf(listOf(...))
}
private fun activityToHappinessData(): Flow<List<ActivityToHappiness>> {
return flowOf(listOf(...))
}
}
重要提示:
- 测试类需要实现“ScreenshotTest”接口的截图比较功能
- 需要固定日期来确保截图测试的可重复性
- 该测试使用数据提供商和存储库的打桩(mock)实例来模拟不同的屏幕状态。它使用“Koin”框架。
操作流程:
- 生成黄金基准截图的命令
./gradlew :app:debugExecuteScreenshotTests -Precor
此命令将UI的当前状态保存为未来比较的参考点。
现在,让我们通过将平均每日情绪图表的标题从“平均每日情绪”更改为“每日情绪”来模拟真实场景,并进行验证:
- 变更后验证命令
./gradlew :app:debugExecuteScreenshotTests
执行后,Shot框架会生成一份报告,在其中突出显示比较后的差异点
- 执行效果演示
注:此图像包含缩放效果,仅用于演示目的。
这种视觉反馈比手动检查每个组件和屏幕要高效得多。要更好地了解不同类型的UI测试之间的区别以及它们如何相互补充,请查看“并非所有UI测试都是一样的”一文。
结论
视觉测试对于交付没有视觉缺陷的应用程序至关重要。虽然UI测试侧重于行为验证,但它们无法检查应用程序的像素完整性。
通过实施视觉测试,开发团队可以在发布应用程序之前发现视觉缺陷。这些测试将发现组件错位、各种主题的颜色不正确、不同设备上的视觉问题等等。
未经打磨的UI看起来总是不专业,这可能会损害对品牌的信任度。通过将UI测试纳入开发过程,不仅检查了像素的完美性,还保护了品牌形象和声誉。