跳到主要内容

性能测试与回归治理

问题

如何建立从真实用户发现问题、实验室复现、CI 阻止回归到上线验证的完整性能工程流程?

面试速答版

性能测试要组合三类证据:

  • 现场数据 RUM/CrUX 回答“真实用户是否慢、哪些页面和设备慢”。
  • 实验室测试 回答“为什么慢”,用固定设备、网络、缓存和用户流程录制可复现的 trace。
  • CI 回归测试 回答“这次改动是否让关键页面退化”,使用锁定版本、多次运行、单项指标与资源预算。

完整闭环是:RUM 分群 → 选代表样本 → 实验室复现 → 找到 trace 证据 → 小步优化 → 相同条件复测 → 灰度上线 → 按版本比较现场 P75 和业务指标。不要只追 Lighthouse 100 分,也不要用一次运行就阻塞 PR。

一、现场数据与实验室数据

数据优点局限主要用途
RUM真实设备、网络、内容和用户行为环境复杂,定位成本高判断影响面、分群、验证上线收益
CrUX无需自建即可看公开站点真实 CWV维度和样本受限,约 28 天窗口查看 URL/origin 长期趋势
Lighthouse自动化、可重复、建议丰富以加载审计为主,受环境噪声影响快速体检、CI 基线
DevTools Performancetrace 细节丰富,可看交互和渲染需要可复现路径和人工分析根因定位
WebPageTest多地点、设备、视频和瀑布能力强运行成本和排队时间较高网络/首屏深度分析
两种数据不一致很正常

实验室可能使用固定中端设备和冷缓存,现场包含高低端设备、热缓存、登录态、A/B 实验和长会话。正确做法不是让两者数字完全相同,而是让实验室场景能稳定复现现场某个受影响用户群。

二、先定义要保护的用户旅程

只测首页会漏掉大量真实问题。测试计划至少覆盖:

  • 首次访问:首页/落地页、冷缓存、未登录。
  • 返回访问:热缓存、BFCache 恢复、已登录。
  • 核心导航:首页 → 列表 → 详情,包含 SPA 软导航。
  • 核心交互:搜索输入、筛选、打开弹窗、提交表单、切换 Tab。
  • 长会话:多次路由切换后内存、监听器和缓存是否增长。
  • 异常环境:慢接口、资源失败、离线恢复和低端设备。

每条旅程定义:起点、数据状态、操作步骤、可见完成条件、性能指标和业务成功条件。

interface PerformanceJourney {
name: string;
route: string;
cacheState: 'cold' | 'warm';
deviceProfile: 'mobile-mid' | 'desktop-mid';
steps: string[];
budgets: {
lcp?: number;
inp?: number;
cls?: number;
transferredJs?: number;
};
}

三、测试矩阵怎么设计?

不要穷举所有组合,应从 RUM 设备分布和业务价值选择代表矩阵。

维度至少保留的场景
设备中端移动设备、常用桌面环境
CPU无限制 + 与目标移动设备接近的 CPU slowdown
网络稳定宽带、普通 4G、必要时高延迟/丢包
缓存冷缓存、热缓存、BFCache/预渲染单独验证
页面公开内容页、登录后核心页、重交互页
数据小数据、典型数据、接近上限的数据量
版本当前生产基线、候选版本、灰度版本

例如,营销页可先采用“中端移动设备 + 冷缓存 + 模拟普通 4G”作为 CI 关键场景;内部管理后台可能更应保护热导航 INP 和长列表交互,而不是套用同一个 LCP 预算。

四、建立可复现基线

基线不是“团队觉得合理的数字”,而是当前生产版本在固定条件下的分布。

推荐步骤:

  1. 锁定 Chrome、Lighthouse、Node、依赖和测试镜像版本。
  2. 使用确定的测试数据、登录态、字体和 feature flags。
  3. 关闭浏览器扩展、系统后台任务和不受控 A/B 实验。
  4. 每条 URL 或旅程运行 3–7 次,保存所有原始结果。
  5. 使用中位运行结果做普通比较,同时保留最差值观察稳定性。
  6. 记录构建 SHA、运行器硬件、时间和配置,确保可以追溯。

运行次数不是越多越好。3 次适合快速 PR 反馈,5–7 次更适合高风险页面或波动较大的共享 runner;最终根据测试成本和方差决定。

五、不要只看 Lighthouse 总分

总分是多个指标经过评分曲线和权重计算后的结果,工具升级也可能改变分数。CI 更适合保护以下信号:

  • LCP、TBT、CLS 等可解释的单项指标。
  • 压缩后首屏 JS/CSS、图片、字体和第三方体积。
  • 请求数、第三方域名数和关键请求链长度。
  • 自定义业务标记,例如 editor-ready、search-result-painted。
  • 关键交互的延迟和长任务数量。
lighthouserc.json
{
"ci": {
"collect": {
"url": [
"http://localhost:3000/",
"http://localhost:3000/products/example"
],
"numberOfRuns": 5,
"settings": {
"preset": "desktop"
}
},
"assert": {
"assertions": {
"categories:performance": ["warn", { "minScore": 0.9 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"total-blocking-time": ["error", { "maxNumericValue": 300 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }]
}
}
}
}

这里的数字只是“桌面预设下关键公开页”的示例。移动端、登录页和后台页面要根据自己的基线与用户目标制定,不能把同一配置复制到所有项目。

六、绝对阈值与相对回归一起用

只用绝对阈值会放过“仍达标但持续变差”,只用相对阈值会在基线很差时继续容忍问题。

interface RegressionRule {
metric: string;
absoluteMax: number;
relativeMaxIncrease: number;
}

function evaluateRegression(
baseline: number,
candidate: number,
rule: RegressionRule,
): { passed: boolean; reasons: string[] } {
const reasons: string[] = [];
const increase = baseline === 0 ? 0 : (candidate - baseline) / baseline;

if (candidate > rule.absoluteMax) {
reasons.push(`${rule.metric} 超过绝对预算`);
}

if (increase > rule.relativeMaxIncrease) {
reasons.push(`${rule.metric} 相对基线退化 ${(increase * 100).toFixed(1)}%`);
}

return { passed: reasons.length === 0, reasons };
}

例如可先约定“LCP 不超过 2.5s,且相对生产基线不得退化超过 10%”。10% 是考虑当前 runner 方差后的示例,需要用历史运行数据确定误差带。

七、处理测试噪声

常见噪声来源:

  • CI runner 与其他任务争抢 CPU。
  • 字体、图片 CDN、第三方服务响应变化。
  • A/B 实验、广告内容和个性化数据不同。
  • 浏览器、Lighthouse 或评分模型升级。
  • 测试服务器刚启动,缓存和 JIT 状态不同。
  • 页面存在 WebSocket、轮询等持续网络请求。

降低噪声的方法:

  • 使用专用或规格稳定的 runner。
  • 锁定工具版本和测试数据。
  • 等待服务健康,而不是固定 sleep 后盲测。
  • 重复运行并比较中位值,不用单次最优值。
  • 总分先 warning,稳定的单项预算再 error。
  • 对第三方使用 mock 和真实环境两套测试:前者保护自身回归,后者评估整体体验。
不要通过放宽阈值掩盖所有波动

如果方差突然变大,本身就可能是性能稳定性问题,例如随机长任务、缓存不稳定或第三方抖动。应记录每次原始结果和标准差/极差,而不是只保存一个中位数。

八、自定义业务性能标记

通用指标不一定知道业务何时真正可用。可以为关键节点打标:

business-marks.ts
performance.mark('search-start');

const result = await searchProducts(query);
renderResults(result);

requestAnimationFrame(() => {
performance.mark('search-result-painted');
performance.measure(
'search-to-result',
'search-start',
'search-result-painted',
);
});

注意 rAF 回调发生在下一次绘制前,并不严格等于像素已经呈现在屏幕上;需要精确呈现时可结合后续帧、Event Timing、LoAF 或浏览器测试工具。自定义指标要写清起点和终点语义,避免团队各自定义“首屏完成”。

九、RUM 发布对比

上线验证必须带构建版本和稳定分桶:

维度推荐做法
版本每条样本带 build id/release id
分桶用户稳定进入 control/candidate,避免每次访问随机切换
指标P50/P75/P90、差体验比例、样本量
分群路由、移动/桌面、设备档位、网络、地区
业务转化、任务成功率、错误率同时观察
周期覆盖工作日/周末、缓存冷启动和足够样本

不要直接比较两个时间段的全站平均值:页面流量结构、市场活动和设备占比变化都可能造成假回归。

十、性能 SLO、预算和告警

一个可执行的目标应该包含:

  • 对象:哪个用户旅程或页面模板。
  • 指标:LCP/INP/CLS、自定义完成时间、资源体积。
  • 人群:移动端/桌面端、地区、设备档位。
  • 统计口径:P75、差体验比例、最小样本量和时间窗口。
  • 责任人:哪个团队处理、多久响应。
  • 例外:豁免原因、到期时间和偿还计划。
performance-slo.yml
journey: product-detail
segment: mobile
window: 7d
minimumSamples: 1000
objectives:
lcpP75Ms: 2500
inpP75Ms: 200
clsP75: 0.1
owner: storefront-team

这里的阈值对应 Core Web Vitals 良好标准;资源体积、自定义指标和告警灵敏度仍需根据业务与设备基线确定。

十一、回归发生后的处理流程

紧急线上回归优先止血:关闭 feature flag、回滚版本、延迟第三方或恢复旧资源。完成止血后再补根因修复和防回归测试。

十二、推荐的分层门禁

阶段检查失败策略
本地开发DevTools/Lighthouse 快速检查提示开发者
PRbundle diff、关键 URL Lighthouse、多次运行稳定预算 error,噪声项 warn
预发布脚本化核心旅程、真实后端/第三方阻止高风险发布
灰度RUM 按版本和分群对比自动暂停扩量或回滚
全量SLO、趋势、差体验比例告警与性能复盘

十三、检查清单

  • 性能目标对应真实用户旅程,而不只是首页
  • RUM 和实验室数据各自职责明确
  • 测试设备、网络、缓存、数据和工具版本可追溯
  • 每个关键场景重复运行并保存原始结果
  • 同时使用绝对预算和相对回归
  • Lighthouse 总分不是唯一 error 门禁
  • 自定义指标起点、终点和业务语义一致
  • RUM 样本带版本并按设备/路由分群
  • 第三方和测试噪声有明确处理策略
  • 豁免有负责人、原因和到期时间
  • 回归可通过 feature flag 或回滚快速止血
  • 优化最终由现场数据和业务指标确认

常见面试问题

Q1: Lighthouse 和 RUM 有什么区别?

答案

Lighthouse 是受控实验室审计,适合快速诊断和 CI;RUM 来自真实设备、网络、内容和用户行为,适合判断影响面和上线收益。通常用 RUM 发现问题和分群,用实验室复现根因,再回到 RUM 验证。

Q2: 为什么 Lighthouse 每次分数不同?

答案

CPU 竞争、网络、第三方、测试数据、缓存、浏览器版本都会产生波动。应锁定环境和版本,多次运行取中位结果,保存原始指标,并让稳定的单项预算做 error、总分先做 warning。

Q3: 多次运行应该取平均值还是中位数?

答案

中位数对偶发异常更稳,适合代表普通一次运行;平均值能反映总体成本但容易被极端值影响。实践中保留全部原始结果,CI 使用中位运行,同时观察最差值和方差。

Q4: 为什么不能只设置绝对阈值?

答案

页面即使仍在 2.5s 内,也可能从 1.2s 持续退化到 2.4s。绝对阈值保护用户体验底线,相对回归保护已有成果,两者需要同时使用。

Q5: 性能预算超标是否一定阻止合并?

答案

稳定、可复现且与用户体验直接相关的预算应阻止;噪声高的总分或第三方波动先 warning。确有业务收益的超标可以临时豁免,但要写明负责人、影响、补偿措施和到期时间。

Q6: 如何测试 SPA 的路由切换性能?

答案

不能只重新加载目标 URL。应从前一页开始执行真实导航,记录点击到新页面关键内容呈现、自定义 route-ready、请求 waterfall、INP 和长任务,同时单独测试 BFCache、预取和软导航测量。

Q7: 为什么 CI 通过,线上仍然慢?

答案

CI 设备、数据和用户路径有限,常常没有真实第三方、登录态、低端设备和长会话。CI 只能防已建模场景的回归,必须配合 RUM、灰度和生产 SLO。

Q8: 如何选择性能测试设备?

答案

先看 RUM 设备分布和业务用户,选择覆盖主要流量的中端设备,再补一个高风险低端档位。不要只用开发者高配电脑,也不必穷举所有机型。

Q9: 自定义性能指标有什么风险?

答案

如果起点终点不一致、只测接口不测呈现、路由间命名不同,就无法比较。指标要有明确用户语义、统一实现、版本和采样策略,并与通用 Web Vitals 互相解释。

Q10: 如何比较两个版本的 RUM?

答案

让样本带 build id,稳定分桶,在相同路由、设备、网络和地区内比较 P75、差体验比例与业务成功率。确认样本量和时间窗口足够,避免流量结构变化造成辛普森悖论。

Q11: 性能回归如何快速定位到代码变更?

答案

先确认回归版本和分群,然后二分提交或构建产物,比较 bundle diff、请求瀑布和 Performance trace。将构建 SHA、source map、traceId 与监控样本关联,能显著缩短定位时间。

Q12: 如何让性能治理长期有效?

答案

把目标落实到关键旅程、负责人、CI 门禁、灰度验证和生产 SLO;性能预算变更要评审,豁免要到期;每次事故补充测试或监控。只有一次专项优化,没有自动化与归属,性能很快会反弹。

相关链接