第 40 课2026.08.26
适用场景:保存、校验、上传等多阶段流程需要系统验证“任一步失败后,只停止其后的步骤”
第 40 课:Hypium 多阶段失败矩阵
- 日期:2026-08-26
- 课程序号:第 40 课
- 知识点:用参数化用例覆盖串行流程每个阶段的失败与中止边界
- 适用场景:保存、校验、上传等多阶段流程需要系统验证“任一步失败后,只停止其后的步骤”
承接上一课:上一课只验证第二阶段失败;本课把失败位置整理成数据表,用独立用例覆盖第一、第二、第三阶段。
核心概念
- 每个用例只指定
failAt、期望错误和期望事件,避免复制三套测试流程。 - 事件序列既证明失败发生在哪一阶段,也证明失败点之后的步骤没有启动。
- 为每组数据注册独立的
it,失败报告会直接显示具体阶段,而不是只告诉你整个循环失败。 - 参数化减少重复代码,但不能弱化断言:错误传播和副作用中止仍要同时验证。
最小 ArkTS / Hypium 示例
ts
import { describe, expect, it } from '@ohos/hypium';
interface FailureCase {
name: string;
failAt: string;
expectedMessage: string;
expectedEvents: string;
}
interface PromiseResult {
rejected: boolean;
message: string;
}
class Stage {
constructor(private readonly name: string,
private readonly failAt: string,
private readonly events: string[]) {}
async run(): Promise<void> {
this.events.push(`${this.name}-started`);
if (this.failAt === this.name) {
throw new Error(`${this.name}失败`);
}
this.events.push(`${this.name}-finished`);
}
}
class PublishController {
readonly events: string[] = [];
async publish(failAt: string): Promise<void> {
await new Stage('保存', failAt, this.events).run();
await new Stage('校验', failAt, this.events).run();
await new Stage('上传', failAt, this.events).run();
this.events.push('发布成功');
}
}
const cases: FailureCase[] = [
{
name: '保存失败后不启动校验',
failAt: '保存',
expectedMessage: '保存失败',
expectedEvents: '保存-started'
},
{
name: '校验失败后不启动上传',
failAt: '校验',
expectedMessage: '校验失败',
expectedEvents: '保存-started,保存-finished,校验-started'
},
{
name: '上传失败后不报告成功',
failAt: '上传',
expectedMessage: '上传失败',
expectedEvents: '保存-started,保存-finished,校验-started,校验-finished,上传-started'
}
];
export default function multiStageFailureMatrixTest(): void {
describe('PublishController.publish failure matrix', () => {
cases.forEach((testCase: FailureCase) => {
it(testCase.name, 0, async () => {
// Arrange
const controller: PublishController = new PublishController();
// Act
const result: PromiseResult = await controller.publish(testCase.failAt).then(
(): PromiseResult => ({ rejected: false, message: '' }),
(error: Error): PromiseResult => ({ rejected: true, message: error.message })
);
// Assert
expect(result.rejected).assertTrue();
expect(result.message).assertEqual(testCase.expectedMessage);
expect(controller.events.join(',')).assertEqual(testCase.expectedEvents);
});
});
});
}事件中同时记录 started 和 finished:例如上传失败时应出现 上传-started,但不能出现 上传-finished 或 发布成功。
3~5 分钟练习
在失败矩阵中增加“无失败”的成功用例,要求三个阶段全部完成,并最终记录 发布成功。
参考答案
ts
interface PublishCase {
name: string;
failAt: string;
rejected: boolean;
expectedEvents: string;
}
const successCase: PublishCase = {
name: '全部阶段成功后报告发布成功',
failAt: '',
rejected: false,
expectedEvents: '保存-started,保存-finished,校验-started,校验-finished,' +
'上传-started,上传-finished,发布成功'
};
const result: PromiseResult = await controller.publish(successCase.failAt).then(
(): PromiseResult => ({ rejected: false, message: '' }),
(error: Error): PromiseResult => ({ rejected: true, message: error.message })
);
expect(result.rejected).assertFalse();
expect(controller.events.join(',')).assertEqual(successCase.expectedEvents);成功用例与失败矩阵共用同一事件协议,能同时守住“失败立即中止”和“全部完成才成功”两侧边界。
与上一课的联系
上一课用可控拒绝精确验证第二阶段失败;本课把相同的错误传播与后续中止契约扩展成失败位置矩阵,系统覆盖串行流程的每一个阶段。