回归测试是在进行更改后重复检查应用程序以检测先前正常功能中缺陷的过程。每次代码更改 -- 新功能、错误修复或重构 -- 都可能无意中破坏应用程序的现有功能。回归测试自动化验证旧功能是否保持正常。根据 IBM,2023 的研究,回归测试 涵盖了商业产品团队中执行的所有测试的 30% 到 70%,这强调了其作为防止生产事故主要屏障的作用。
要点
回归测试 是一种旨在确认代码更改未影响现有功能的测试类型。术语 “回归” 意味着退回到更差的状态 -- 当先前版本中正常的功能在新版本中无法正常工作时。回归测试在每个开发周期中多次执行,这使它们区别于一次性编写的新功能测试。
回归测试的必要性源于 级联更改 效应:修复一个模块中的错误可以解决问题,但可能破坏依赖于它的相邻功能。例如,更改用户存储库中的 SQL 查询可能会加快登录速度,但可能破坏使用相同查询的数据导出。数据导出的回归测试将在发布前发现此问题。
根据 CISQ 2023 报告,在生产中发现的回归缺陷的修复成本比自动化回归运行阶段高出 15 倍。根据 Capgemini World Quality Report,投资自动化回归测试的公司可在实施后一年内将发布中的回归缺陷比例从 25% 降低到 5%。
有几种回归测试方法,它们在测试量和选择标准上有所不同。方法的选择 取决于项目规模、更改频率和 CI 管道中的可用时间。以下是回归测试的主要类型及其特点。
完整回归运行 无条件地执行项目中所有自动化测试。这种方法提供最大的信心,但需要大量的计算资源和时间。完整运行在大版本发布前执行 -- 每 2-4 周一次。对于具有 5000 个测试的应用程序,完整运行需要 2 到 6 小时,具体取决于基础设施。
选择性方法 只运行与更改模块相关的测试。为了确定关联性,使用代码级别的依赖分析:如果 UserRepository 类被更改,则运行直接或传递依赖于 UserRepository 的测试。Jacoco、Android Test Coverage 和 Xcode Code Coverage 工具提供覆盖图以进行精确选择。选择性运行在每个 pull request 时执行,需要 5-15 分钟。
基于风险的回归 根据功能的严重性和损坏可能性对测试进行排序。关键功能 -- 支付、授权、同步 -- 在每次代码更改时进行测试。辅助功能 -- “关于应用” 屏幕、动画 -- 仅在发布前测试。排序基于生产事故数据每季度重新评估一次。
回归测试和重新测试的概念常常被混淆,尽管它们是不一样的过程。重新测试 是在修复缺陷后重新运行先前失败的特定测试。重新测试的目的是确认修复有效:错误不再重现。重新测试一次性完成,在修复后并且开发人员确认修复后立即进行。
回归测试 是在未更改的现有功能上运行测试。目的是确认一个缺陷的修复没有在其他地方创建新的缺陷。回归测试在每个开发周期中多次运行,无论修复了哪些具体错误。主要区别:重新测试检查修复本身,回归检查修复的后果。
在 CI/CD 管道中,两个过程按顺序执行。在 pull request 合并后,先运行特定错误的重新测试,然后运行完整或选择性回归运行。根据 SmartBear(2022),分离这些过程可将失败的 CI 运行的诊断时间减少 30%,因为团队立即看到哪些缺陷与回归相关,哪些与不起作用的修复相关。
回归测试的自动化是现代移动项目成功的关键因素。手动回归测试 不可扩展:对于 200 个测试的集,一次运行需要 QA 工程师 2-3 个工作日,这使得每日运行变得不可能。自动化回归测试在 10-60 分钟内完成,无需人工干预,允许在每次提交或 pull request 时运行它们。
为保持回归集的最新状态使用 测试分析:Allure、ReportPortal 和 Xray 等工具跟踪每个测试的通过率、持续时间和稳定性。稳定性低于 90% 的测试(由于需求变化频繁出错)被标记为遗留测试并发送给所有者进行审查。
我们来看一下使用 JUnit 5 库和 Espresso 在 Android 上配置自动化回归测试的示例。示例演示了选择性回归 -- 测试检查用户存储库重构后配置文件屏幕是否被破坏。对于 iOS,使用 XCTest 和类似的逻辑 -- 对关键场景的重复测试。
测试使用 MockWebServer 模拟服务器并检查完整路径:加载用户数据、在配置文件屏幕上显示以及在服务器不可用时处理错误。这些测试包含在回归集中,并在每次更改 module-profile 时执行。
@RunWith(AndroidJUnit4::class)
class ProfileRegressionTest {
@get:Rule
val composeRule = createComposeRule()
@Test
fun profileScreen_rendersCorrectly() {
val user = User(id = 1, name = "Alice", email = "alice@test.com")
composeRule.setContent {
ProfileScreen(user)
}
composeRule.onNodeWithText("Alice").assertIsDisplayed()
composeRule.onNodeWithText("alice@test.com").assertIsDisplayed()
}
@Test
fun profileScreen_handlesNetworkError() {
setNetworkError()
composeRule.onNodeWithText("加载错误").assertIsDisplayed()
}
}
对于 iOS,回归测试使用 XCTestExpectation 在从 API 接收数据后异步检查 UI 更新。测试模拟网络响应并检查 UI 元素是否正确更新。
class ProfileRegressionTests: XCTestCase {
func testProfileScreen_rendersCorrectly() {
let viewModel = ProfileViewModel(userId: 1)
let view = ProfileView(viewModel: viewModel)
viewModel.loadProfile()
let expectation = expectation(description: "profile loaded")
viewModel.onProfileLoaded = {
XCTAssertEqual(viewModel.userName, "Alice")
XCTAssertEqual(viewModel.userEmail, "alice@test.com")
expectation.fulfill()
}
waitForExpectations(timeout: 3.0)
}
}
构建有效的回归集是一个基于缺陷和代码更改数据的迭代过程。主要策略 是将所有现有测试包含在回归集中,并在每次发布前运行完整运行。随着测试基础的增长(超过 2000 个测试),完整运行变得太长时间,需要选择性方法。
第二阶段 -- 引入依赖分析工具:Android 的 Jacoco、iOS 的 Xcode Test Plan。这些工具构建 “测试 -- 类 -- 方法” 映射,并允许确定哪些测试受到特定更改的影响。基于覆盖率分析的选择性运行可将执行时间减少 60-80%,同时保持 95% 的回归检测有效性,根据 Spotify Engineering(2022)。
第三阶段 -- 持续监控和优化。6 个月未失败的测试被移入低优先级集。每月失败超过一次的测试是审查的候选者:要么它们捕获了真正的问题(需要修复),要么过于脆弱(需要稳定化)。回归集的季度审查是保持其有效性和执行速度的标准做法。
常见问题
选择性回归运行 -- 在每次 pull request 时。完整回归运行 -- 在每次发布前和每周(nightly build)。关键规则:运行越频繁,回归越快被发现,修复成本越低。对于关键项目,每次合并时可以进行完整回归。
所有单元测试(基础回归)、关键组件的集成测试以及关键用户场景的 UI 测试。不要包含 实验性功能的测试、flakiness 高于 10% 的测试和需要手动环境的测试。
删除已移除功能的测试,在需求变化时更新测试,每季度进行集审计。CI 分析 -- Allure、ReportPortal -- 帮助识别失去相关性的测试:如果测试 3 个月未更改且未失败,则可从每日运行中删除。
使用多设备并行测试执行,实施基于更改代码覆盖率分析的选择性回归,关闭不相关屏幕的视觉截图。目标时间:选择性运行 -- 5-10 分钟,完整运行 -- 不超过 2 小时。
不,回归测试也包括手动检查:发布后的探索性测试、UX 回归和界面更改后的可访问性检查。自动化覆盖 70-80% 的回归检查;其余 20-30% 是手动测试,专注于无法或过于昂贵自动化的场景。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。