PAYATHON 2026

如何验证 UI 显示的存储用量与后端数据一致?

技术老齐

推荐方法:保留真实链路测试,补充分层测试

建议保留现有的端到端测试:以已验证的内部 API 为数据基准,让 UI 请求经过真实中间层,再核对页面显示。这能检查中间层是否传错或算错数据。

浏览器响应 mock 适合检查前端如何显示指定数据,也方便覆盖边界值和异常场景。不过,它绕过了真实服务链路,无法单独证明 UI 与内部 API 的数据一致。

当前最需要调整的是预期值的计算方式。测试应根据明确的单位和格式规则确定预期值,避免复制生产代码的转换逻辑。否则,测试和生产代码可能犯同样的错误,测试依然通过。

不同测试分别覆盖什么

假设数据经过以下链路:

内部 API → 中间服务 → UI 使用的 API → 前端格式化 → 页面显示
测试方式验证范围主要用途
浏览器响应 mock前端格式化与渲染单位切换、小数位、空值、请求失败
中间层集成测试内部 API 数据到 UI API 响应的处理字段映射、单位转换、聚合、权限过滤
真实链路端到端测试内部 API 到页面显示验证各层连接后,页面与基准数据一致

如果真实存储环境难以控制,可以在内部 API 的响应边界提供固定数据,让后面的中间服务和前端照常运行。这能覆盖中间层的处理逻辑,但测试范围仅从数据注入的位置开始,仍需保留少量真实链路测试,验证实际数据接入。

实施步骤

1. 先明确比较的口径

至少需要确认:

  • UI 显示的是租户用量、卷用量,还是整个文件系统的用量。
  • 配额字段表示业务配额,还是底层存储总容量。
  • API 返回的是字节数,还是已经转换过的数值。
  • 页面使用 MiB/GiB,还是 MB/GB。
  • 单位如何切换,保留几位小数,以及如何舍入或截断。
  • 数据是否经过缓存,允许多长的刷新延迟。

二进制单位的换算为:

1 MiB = 2^20 bytes = 1,048,576 bytes
1 GiB = 2^30 bytes = 1,073,741,824 bytes

这些规则应以产品约定或接口契约为准。仅凭“当前实现就是这么算的”,无法判断实现是否正确。

2. 使用可控且稳定的数据

优先使用独立的测试租户或测试卷,通过真实业务流程或测试环境的数据准备机制建立已知状态,再等待内部 API 的统计结果稳定。

共享存储上的后台写入、异步统计和缓存刷新,可能导致 API 与 UI 读到不同时间的数据。测试时应尽量停止并发写入,并在约定的刷新期限内等待页面更新。

如果系统提供统计时间戳或版本号,可以用它们确认 API 与 UI 对应同一份统计结果。如果只是先后读取两次,既没有快照,也没有稳定的环境,就无法保证两次数据严格对应同一时刻。

3. 独立确定预期值

固定场景可以直接写出经核对的输入和预期显示值,便于发现转换错误。例如,约定达到 1 GiB 时切换单位,并保留一位小数:

原始字节数预期显示
00.0 MiB
10485761.0 MiB
10737418241.0 GiB
16106127361.5 GiB

单位阈值附近的数值、舍入边界和大数值,可以在格式化单元测试或 mock 测试中充分覆盖。端到端测试保留少量有代表性的场景即可。

如果必须比较环境中的动态值,可以根据上述约定编写独立的测试换算函数,再用固定用例校验。不要直接导入生产格式化函数来生成预期值。

Playwright 示例:通过真实请求核对页面

示例的运行前提是:

  • 测试环境已准备好稳定数据,用量为 1610612736 字节,配额为 10737418240 字节。
  • 页面按二进制单位显示,保留一位小数。
  • Playwright 已配置 baseURL,API 请求与页面访问同一个测试租户。
  • 页面会在正常加载或自身刷新后显示最新统计结果。

接口路径、字段和 data-testid 都是示例,需要替换为实际定义。

import { test, expect } from '@playwright/test';

test('存储页面与内部 API 的数据一致', async ({ page, request }) => {
  // 数据准备应在测试前完成,并等待统计稳定。
  const response = await request.get('/internal/storage');
  expect(response.ok()).toBeTruthy();

  const storage = await response.json();

  // 确认基准数据符合本用例的前提。
  expect(storage).toMatchObject({
    usedBytes: 1610612736,
    quotaBytes: 10737418240,
  });

  // 不拦截页面请求,让请求经过真实的中间服务。
  await page.goto('/storage');

  // 预期文本依据显示规则预先核对。
  await expect(page.getByTestId('storage-used'))
    .toHaveText('1.5 GiB');

  await expect(page.getByTestId('storage-quota'))
    .toHaveText('10.0 GiB');
});

使用预先核对的显示值,可以验证已知数据经过真实链路后是否显示正确,也省去了在测试中重写复杂转换逻辑的工作。

toHaveText() 会重试断言,但不会主动让页面重新请求数据。如果页面只在加载时读取一次,应在数据稳定后再打开页面;如果要测试刷新行为,就执行产品实际提供的刷新操作。等待期限应根据统计刷新约定配置,避免随意添加固定时长的 sleep。

df -h 能否作为基准

只有当 UI 与 df 统计的是同一个文件系统,且用量含义相同时,二者才适合比较。

df 报告文件系统空间信息,业务 API 可能统计的是租户配额、逻辑文件大小或计费容量。压缩、去重、快照和文件系统元数据等因素,也可能让这些数值不同。租户配额尤其不能直接等同于文件系统总容量。

df -h 的输出已经过单位转换和舍入,不适合作为精确断言的原始值。在 GNU df 环境中,可以按字节单位输出指定挂载路径的信息:

df -B1 --output=size,used,avail /path/to/mount

这些选项适用于 GNU df,其他系统需使用对应实现支持的方式。命令的执行位置也必须与被测存储对应,以免读到宿主机或其他容器的文件系统。

如果还需要验证内部 API 的数据是否符合底层存储统计,应单独建立“存储统计到内部 API”的测试。页面数据不一致时,就可以结合各段测试定位问题。

注意事项

  • 格式测试应同时检查数值、小数位和单位。只比较数字,可能漏掉 MiB/GiB 标错的问题。
  • 舍入造成的精度损失与异步刷新造成的数据变化,应分别处理。前者按显示规则断言,后者通过稳定数据和有期限的等待解决。
  • 不要随意加入“允许误差 5%”之类的容差。如果业务确实允许偏差,应写清依据、范围和适用场景。
  • 用于计算的字节数如果可能超过 JavaScript 的安全整数范围,需要确认接口如何表示该数值,避免用 Number 处理后丢失精度。
  • 测试失败时,保留内部 API 原始响应、UI API 响应、页面显示和采样时间,便于判断问题出在数据源、转换、缓存还是渲染。

备注:内容仅供参考。