渐进式编程课
鸿蒙 目录
第 32 课2026.08.17

适用场景:多个用例反复创建相同的 Fake、Spy 和被测对象,准备代码过多,真正的测试差异不突出

第 32 课:Hypium 测试数据工厂

  • 日期:2026-08-17
  • 课程序号:第 32 课
  • 知识点:用小型工厂函数集中创建测试依赖和被测对象
  • 适用场景:多个用例反复创建相同的 Fake、Spy 和被测对象,准备代码过多,真正的测试差异不突出

承接上一课:上一课把不同业务契约拆成独立用例;本课用测试数据工厂减少拆分后重复的 Arrange 代码,同时保持每个用例的差异清晰。

核心概念

  • 工厂函数提供一套可用的默认测试环境,统一创建 Fake、Spy 和被测对象。
  • 用例只传入与默认条件不同的依赖,例如失败用例只替换保存服务。
  • 工厂只负责装配测试对象,不隐藏 Act 和 Assert;测试正文仍应明确执行了什么、验证了什么。

最小 ArkTS / Hypium 示例

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

interface DraftService {
  save(content: string): Promise<void>;
}

interface NoticeService {
  send(message: string): void;
}

class SuccessfulDraftService implements DraftService {
  save(content: string): Promise<void> {
    return Promise.resolve();
  }
}

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('发布成功');
  }
}

interface PublisherFixture {
  publisher: DraftPublisher;
  noticeService: SpyNoticeService;
}

function createPublisherFixture(
  draftService: DraftService = new SuccessfulDraftService()
): PublisherFixture {
  const noticeService: SpyNoticeService = new SpyNoticeService();
  return {
    publisher: new DraftPublisher(draftService, noticeService),
    noticeService: noticeService
  };
}

export default function draftPublisherFactoryTest(): void {
  describe('DraftPublisher', () => {
    it('notifiesWhenSaveSucceeds', 0, async () => {
      // Arrange:直接使用默认成功环境
      const fixture: PublisherFixture = createPublisherFixture();

      // Act
      await fixture.publisher.publish('鸿蒙笔记');

      // Assert
      expect(fixture.noticeService.callCount).assertEqual(1);
    });

    it('doesNotNotifyWhenSaveFails', 0, async () => {
      // Arrange:只覆盖本用例不同的失败依赖
      const fixture: PublisherFixture = createPublisherFixture(
        new FailingDraftService()
      );

      // Act
      try {
        await fixture.publisher.publish('鸿蒙笔记');
      } catch (error) {
        // 本用例只验证失败后不通知
      }

      // Assert
      expect(fixture.noticeService.callCount).assertEqual(0);
    });
  });
}

createPublisherFixture() 默认构造成功场景;失败用例只替换 DraftService。两个用例仍明确保留 Act 和 Assert,不把业务行为藏进工厂。

3~5 分钟练习

给 PublisherFixture 增加 draftService 字段,让用例既能取得被测对象,也能直接取得本次使用的保存服务。修改工厂返回值,但不要把 publish() 调用放进工厂。

参考答案

ts
interface PublisherFixture {
  publisher: DraftPublisher;
  draftService: DraftService;
  noticeService: SpyNoticeService;
}

function createPublisherFixture(
  draftService: DraftService = new SuccessfulDraftService()
): PublisherFixture {
  const noticeService: SpyNoticeService = new SpyNoticeService();
  return {
    publisher: new DraftPublisher(draftService, noticeService),
    draftService: draftService,
    noticeService: noticeService
  };
}

工厂集中返回测试所需对象,但何时执行 publish()、断言哪个行为,仍由各用例决定。

与上一课的联系

上一课通过单一行为原则把契约拆开,可能带来重复的 Arrange 代码;本课用测试数据工厂集中默认装配,让用例保持独立的同时只突出各自的条件差异。