第 31 课2026.08.14
适用场景:一个测试同时检查异常、调用次数和多个状态,失败时难以判断是哪条业务契约被破坏
第 31 课:Hypium 单一行为断言原则
- 日期:2026-08-14
- 课程序号:第 31 课
- 知识点:让每个测试用例只验证一个主要行为
- 适用场景:一个测试同时检查异常、调用次数和多个状态,失败时难以判断是哪条业务契约被破坏
承接上一课:上一课用行为化命名和 AAA 结构让测试更易读;本课进一步让测试名与失败原因保持一一对应。
核心概念
- 一个
it应描述一个主要行为,例如“保存失败时异常向上传递”或“保存失败时不发送通知”。 - 不要机械地要求每个用例只能写一行
expect;多个断言若共同证明同一个行为,可以保留在同一用例中。 - 当几个断言代表不同契约、任意一个都可能独立失败时,应拆成独立用例,让失败报告直接指出被破坏的行为。
最小 ArkTS / Hypium 示例
ts
import { describe, expect, it } from '@ohos/hypium';
interface DraftService {
save(content: string): Promise<void>;
}
interface NoticeService {
send(message: string): void;
}
class FailingDraftService implements DraftService {
save(content: string): Promise<void> {
return Promise.reject(new Error(`保存失败:${content}`));
}
}
class SpyNoticeService implements NoticeService {
callCount: number = 0;
send(message: string): void {
this.callCount += 1;
}
}
class DraftPublisher {
constructor(
private draftService: DraftService,
private noticeService: NoticeService
) {}
async publish(content: string): Promise<void> {
await this.draftService.save(content);
this.noticeService.send('发布成功');
}
}
export default function draftPublisherFocusedTest(): void {
describe('DraftPublisher', () => {
it('propagatesErrorWhenSaveFails', 0, async () => {
// Arrange
const publisher: DraftPublisher = new DraftPublisher(
new FailingDraftService(),
new SpyNoticeService()
);
let errorMessage: string = '';
// Act
try {
await publisher.publish('鸿蒙笔记');
} catch (error) {
errorMessage = (error as Error).message;
}
// Assert:只验证异常上传契约
expect(errorMessage).assertEqual('保存失败:鸿蒙笔记');
});
it('doesNotNotifyWhenSaveFails', 0, async () => {
// Arrange
const noticeService: SpyNoticeService = new SpyNoticeService();
const publisher: DraftPublisher = new DraftPublisher(
new FailingDraftService(),
noticeService
);
// Act
try {
await publisher.publish('鸿蒙笔记');
} catch (error) {
// 本用例不验证异常内容,只等待失败流程结束
}
// Assert:只验证失败后不通知契约
expect(noticeService.callCount).assertEqual(0);
});
});
}两个用例使用相同失败条件,但分别验证“异常向上传递”和“不会发送通知”。测试失败时,用例名会直接指出被破坏的契约。
3~5 分钟练习
下面的 testPublishFailure 同时断言错误文案和通知次数。请把它拆成两个行为化命名的用例,并写出各自唯一的主要断言。
ts
expect(errorMessage).assertEqual('保存失败:鸿蒙笔记');
expect(noticeService.callCount).assertEqual(0);参考答案
ts
it('propagatesErrorWhenSaveFails', 0, async () => {
// 执行失败发布并捕获错误
expect(errorMessage).assertEqual('保存失败:鸿蒙笔记');
});
it('doesNotNotifyWhenSaveFails', 0, async () => {
// 执行失败发布并检查 Spy
expect(noticeService.callCount).assertEqual(0);
});实际代码中,每个用例仍需独立完成 Arrange 和 Act;这里省略重复部分,只突出拆分后的行为名称与断言。
与上一课的联系
上一课让一个用例通过“条件 + 结果”的名称和 AAA 分段变得易读;本课让一个名称只对应一个主要行为,使失败报告进一步做到原因清晰、定位直接。