渐进式编程课
鸿蒙 目录
第 35 课2026.08.20

适用场景:同一个异步接口会因多种非法输入而失败,需要确认每种输入都以约定的错误信息拒绝

第 35 课:Hypium 异步失败参数化测试

  • 日期:2026-08-20
  • 课程序号:第 35 课
  • 知识点:为每组非法参数注册独立用例,并显式验证 Promise 拒绝结果
  • 适用场景:同一个异步接口会因多种非法输入而失败,需要确认每种输入都以约定的错误信息拒绝

承接上一课:上一课为多组成功参数生成独立的异步用例;本课沿用同一结构覆盖失败参数,并防止“Promise 没有拒绝但测试仍通过”的假阳性。

核心概念

  • 每组非法参数仍注册为独立的 it,让失败报告直接显示具体输入场景。
  • 用 then 的成功和失败回调把 Promise 结果转换为明确的数据,避免异常分支被遗漏。
  • 断言既要验证 rejected 为 true,也要验证错误信息符合契约。
  • 参数表只描述输入和预期错误,不在测试中复制生产代码的判断逻辑。

最小 ArkTS / Hypium 示例

ts
import { describe, expect, it } from '@ohos/hypium';

interface RejectCase {
  name: string;
  userId: number;
  expectedMessage: string;
}

interface PromiseResult {
  rejected: boolean;
  message: string;
}

class UserService {
  async loadName(userId: number): Promise<string> {
    if (userId <= 0) {
      return Promise.reject(new Error('userId 必须大于 0'));
    }
    return Promise.resolve(`用户${userId}`);
  }
}

function registerRejectCase(testCase: RejectCase): void {
  it(testCase.name, 0, async () => {
    // Arrange
    const service: UserService = new UserService();

    // Act
    const result: PromiseResult = await service.loadName(testCase.userId).then(
      (): PromiseResult => ({ rejected: false, message: '' }),
      (error: Error): PromiseResult => ({ rejected: true, message: error.message })
    );

    // Assert
    expect(result.rejected).assertTrue();
    expect(result.message).assertEqual(testCase.expectedMessage);
  });
}

export default function userServiceRejectParameterizedTest(): void {
  describe('UserService.loadName rejects invalid ids', () => {
    const cases: RejectCase[] = [
      { name: 'rejectsZeroUserId', userId: 0, expectedMessage: 'userId 必须大于 0' },
      { name: 'rejectsNegativeUserId', userId: -1, expectedMessage: 'userId 必须大于 0' }
    ];

    cases.forEach((testCase: RejectCase) => {
      registerRejectCase(testCase);
    });
  });
}

如果被测 Promise 意外成功,result.rejected 会是 false,第一条断言立即失败;因此测试不会因为只检查错误文案而误通过。

3~5 分钟练习

给参数表增加 userId: -99 的独立失败用例,名称为 rejectsLargeNegativeUserId,预期错误信息仍为 userId 必须大于 0。不要修改 UserService 或合并已有用例。

参考答案

ts
const cases: RejectCase[] = [
  { name: 'rejectsZeroUserId', userId: 0, expectedMessage: 'userId 必须大于 0' },
  { name: 'rejectsNegativeUserId', userId: -1, expectedMessage: 'userId 必须大于 0' },
  { name: 'rejectsLargeNegativeUserId', userId: -99, expectedMessage: 'userId 必须大于 0' }
];

重新运行测试后应看到三个独立失败场景用例;每个用例都会明确证明 Promise 被拒绝,并核对拒绝原因。

与上一课的联系

上一课用 async/await 验证多组 Promise 成功结果;本课继续使用“一组参数对应一个用例”的结构,把覆盖范围扩展到 Promise 拒绝路径,并增加防止假阳性的拒绝标记断言。