三方库升级以后,应用能顺利安装,首页也能打开,却不一定说明这次升级可靠。请求成功时的数据形状可能变化;异常对象中的字段可能发生调整;相邻的两个异步请求可能乱序返回;离线、超时与解析失败时的提示策略也可能走向不同分支。与其等版本上线以后再追日志,不如把这些行为固定为一套可重复的契约检查,让升级是否可接受有具体证据。

依赖稳定性基线封面

在此前四篇中,工程建立了 CatalogPort 作为业务接口,AxiosCatalogGateway 封装 @ohos/axios,把第三方异常统一成 CatalogLoadError,又通过 CatalogCoordinator 的请求代次规避了旧请求覆盖新数据。随后引入了 ohpm 依赖更新与锁文件审查。现在需要补上的不是另一个“万能网络层”,而是针对这些已有模块的回归机制:依赖版本发生变化,哪些行为必须保持一致;如果观察到变化,怎样准确定位在请求适配、结果解析还是界面呈现。

一、先看演进路线,而不是先测速度

工程早期,页面可以直接调用 axios.get()。这种写法在简单示例中很短,却容易让页面直接依赖 Axios 的响应结构与异常字段。加入 CatalogPort 后,页面只知道“请求目录数据并得到 CatalogItem[]”,具体网络库交由网关处理;再加入 CatalogCoordinator,异步结果写入状态之前还要检查请求代次。到了依赖升级阶段,治理对象也从单个版本号扩展为“依赖清单、锁文件、业务契约、故障恢复”四个相互关联的内容。

从直接调用到可回归网关的演进关系

oh-package.json5 的版本范围表达允许哪些版本被安装,oh-package-lock.json5 则用于固定具体依赖解析结果。官方 ohpm update 会根据 semver 约束更新依赖,不同的 ^ 与 ~ 约束范围并不相同。评估升级必须同时记录锁文件的实际变化,不能只看到配置里版本字段没有改变便认定依赖完全一致;也不能把 ohpm clean 当作无风险的重装命令,因为默认清理流程会移除锁文件,需要保留时应采用官方支持的 --keep-lockfile 选项。

稳定性基线应先回答行为问题,再回答性能问题。比如重复请求同一份合法数据是否生成相同的业务条目;接口返回字段缺失时是否转换为格式错误;可恢复的网络故障是否给出重新尝试的路径;连续切换列表筛选条件时能否阻止过时请求改写界面。这些都是可在测试替身中稳定控制的事件,不依赖偶然的公网延迟。

二、定义可注入的响应与故障脚本

下面的代码聚焦业务契约层,使用 ArkTS 类与 Promise 实现一个可注入的目录端口。业务返回结构沿用前文的 CatalogItem,错误类别沿用 CatalogLoadError 的语义,测试替身本身不依赖 Axios 的内部类。这样即使将来更换三方库,回归脚本仍可保留;真正的 AxiosCatalogGateway 只需继续符合端口约定。

class CatalogItem {
  readonly id: string;
  readonly title: string;
  constructor(id: string, title: string) {
    this.id = id;
    this.title = title;
  }
}

type CatalogErrorKind = 'NETWORK' | 'TIMEOUT' | 'HTTP' | 'FORMAT';

class CatalogLoadError extends Error {
  readonly kind: CatalogErrorKind;
  readonly retryable: boolean;
  constructor(kind: CatalogErrorKind, message: string, retryable: boolean) {
    super(message);
    this.kind = kind;
    this.retryable = retryable;
  }
}

interface CatalogPort {
  list(): Promise<CatalogItem[]>;
}

class FixtureCase {
  readonly mode: string;
  readonly items: CatalogItem[];
  constructor(mode: string, items: CatalogItem[]) {
    this.mode = mode;
    this.items = items;
  }
}

class ScriptedCatalogPort implements CatalogPort {
  private cursor: number = 0;
  constructor(private cases: FixtureCase[]) {}

  async list(): Promise<CatalogItem[]> {
    const index: number = this.cursor++;
    const item: FixtureCase | undefined = this.cases[index];
    if (item === undefined) {
      throw new CatalogLoadError('FORMAT', '测试输入已用尽', false);
    }
    if (item.mode === 'timeout') {
      throw new CatalogLoadError('TIMEOUT', '请求超时', true);
    }
    if (item.mode === 'offline') {
      throw new CatalogLoadError('NETWORK', '网络不可用', true);
    }
    if (item.mode === 'invalid') {
      throw new CatalogLoadError('FORMAT', '目录字段不符合契约', false);
    }
    return item.items;
  }
}

class CatalogCoordinator {
  private generation: number = 0;
  items: CatalogItem[] = [];
  error: CatalogLoadError | undefined;
  constructor(private port: CatalogPort) {}

  async refresh(): Promise<void> {
    const current: number = ++this.generation;
    try {
      const next: CatalogItem[] = await this.port.list();
      if (current !== this.generation) { return; }
      this.items = next;
      this.error = undefined;
    } catch (e) {
      if (current !== this.generation) { return; }
      this.error = e instanceof CatalogLoadError ? e :
        new CatalogLoadError('NETWORK', '目录加载失败', true);
    }
  }

  invalidate(): void { this.generation++; }
}

这段实现将行为边界表达为可以检查的类与接口。ScriptedCatalogPort 按顺序给出成功、离线、超时或格式失败,不会生成假网络日志。CatalogCoordinator 只接收当前代次的结果,在异步等待之后仍检查 generation;这和 B03 的并发保护属于同一工程主线,没有重复引入另一套竞争规则。生产环境仍通过 AxiosCatalogGateway 实现 CatalogPort,并使用实际接口响应完成格式验证。若接口契约中的必填字段变化,应由网关转换逻辑处理,而不是让页面四处分散字段兼容分支。

故障脚本与业务期望的检查矩阵

测试时应分别执行以下场景:合法列表返回后 items 正确更新且错误状态清除;离线和超时后保留上一次可用内容,同时展示可重试提示;格式错误时阻止不合法数据进入状态;先发的慢请求晚于后发请求完成时不覆盖新数据。最后一种场景需要一个由测试代码显式控制 Promise 完成次序的端口替身,不应依赖 setTimeout 或真实服务器运气。上述脚本中展示的是独立单次故障分类,跨代次顺序测试需另配受控延迟端口。这样测试失败时才能对应到明确的条件,而不是仅凭“页面偶尔乱了”判断。

三、性能基线必须和稳定性基线分开

当契约检查全部通过,才有意义讨论升级前后的响应处理开销。把单次端到端耗时拆为网络等待、响应解码、字段校验、状态提交和 UI 更新,更容易找到瓶颈。使用真实网络的结果应同时记录网络条件与服务端负载;仅在本地替身内进行的测试,只能评估本地业务逻辑,不可以推断生产网络延迟或实际用户体验。Date.now() 的差值可用于粗略记录同步步骤,但不应该被当作精确帧耗时;高频采样更应考虑系统 Profiler 和不同设备上的重复结果。

建议基线报告保留三类数据:环境元信息、输入序列及执行结果。环境元信息包括 HarmonyOS SDK/设备/构建模式、ohpm 客户端版本、依赖清单和锁文件摘要;输入序列记录成功、失败、超时与乱序的可重复脚本;执行结果记录成功断言数、失败断言的具体原因、异常类别分布和实际测到的时间数据。没有运行过的项目就留下“待采集”,不填漂亮的百分比。这样后续版本升级才能与同一条件下的历史样本进行横向比较。

这一阶段还要区分“依赖变化”与“输入数据变化”。如果升级前用的是两条固定目录数据,升级后用动态十万条真实数据,即使测试结果看起来截然不同,也无法归因于三方库。比较时应固定输入格式、条目数量、处理链路以及异常序列。如果业务确实同步调整了字段模型,就同时存放旧格式和新格式样本,明确这次是兼容性迁移,而不是默默扩大性能测试的变量数量。

四、把结果变成升级前的决策门禁

升级审批不该只靠“构建通过”和“页面能打开”。更稳妥的顺序是:锁定对比版本、核对锁文件差异、执行端口契约检查、运行故障注入矩阵、复核并发与取消策略、采集受控环境的性能样本、最后进行带有真实 UI 的交互检查。步骤之间存在依赖,上一关失败就先定位原因,而不是把若干缺陷合并成一条“回归待观察”。

依赖版本与回归检查的放行决策

需要强调的是,故障种类并不等于用户提示文案。一次网络断开可以提示重新连接,而格式错误应该更接近数据或接口契约问题;如果把这些情况统一映射为“网络繁忙,请重试”,会让用户反复点击却始终不能成功。retryable 是网关提供给上层策略的信号,不应自动等价为立即无限次数重试。协调器仍需限制重复操作,页面也需要保持清楚的加载、成功与失败状态,并考虑用户主动离开页面后的结果失效。

当检测到回归失败,回退流程应同时恢复 oh-package.json5 和 oh-package-lock.json5,通过工程版本管理核对改动,再重新安装、构建并重新执行失败用例。只改一个版本号但留下新的锁文件,不是一条可审计的回退路径。对于依赖升级引发的字段转换变化,建议把最小失败样本纳入永久回归集,而不是在本次修复后删掉它。

五、旧体系到新体系,变化究竟发生在哪里

从直接调用 Axios 到隔离端口,再到请求代次控制、依赖回退和基线审查,真正演进的是系统的可解释性。任何一次失败都应该能被定位到具体输入、具体端口行为和具体状态提交阶段。这样的成本并不是为了抽象而抽象:当某个库的版本变化导致异常格式改变时,核心页面无需跟着修改;当某次弱网测试暴露过时请求覆盖时,修改集中在协调器;当性能出现退化时,也可以先复核原始输入与版本证据,再决定是否下钻到 ArkUI。

下一篇将以这套基线为基础,完善安全与工程质量门禁。第三方库不是升级完成就可以不再关注的黑盒;依赖树、锁文件、网络接口、错误映射和测试证据需要共同组成可追溯的维护链条。

官方参考:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐