← 返回题目列表

抽象工厂模式如何做测试和后续演进?

高频 中等 第 6 / 25 题 更新于 2026/08/01
抽象工厂模式单元测试架构演进回归测试

简化版

抽象工厂的测试重点是产品族一致性、每个具体工厂是否完整实现、未知配置是否失败明确、以及新增产品族或产品等级时是否有回归保护。演进时要警惕抽象工厂接口膨胀,产品等级频繁变化时应拆小工厂、引入能力接口或注册表机制。

详细版

抽象工厂把多个产品创建方法集中在一个接口里,因此测试不能只验证某一个产品是否能创建,还要验证整套产品是否来自同一族、配置是否一致、生命周期是否正确。

测试可以分为三层:

  1. 单个产品测试:产品行为正确;
  2. 具体工厂测试:每个创建方法返回正确产品;
  3. 产品族集成测试:同一工厂创建的产品能协作。

演进方面,新增产品族通常容易,新增一个具体工厂即可;新增产品等级则会修改抽象工厂接口,影响所有具体工厂。若产品等级经常新增,说明抽象边界可能太大,需要拆分或改成注册式扩展。

完整版教学

一、抽象工厂为什么需要专门测试

普通工厂只创建一个产品,测试重点是输入到产品的映射。抽象工厂创建一组相关产品,除了映射正确,还要验证产品族一致性。只测 createButton() 能返回对象,不足以证明 createDialog() 和它配套。

示意测试目标:

WindowsFactory -> WindowsButton + WindowsDialog + WindowsMenu
MacFactory     -> MacButton + MacDialog + MacMenu
禁止出现:WindowsFactory -> WindowsButton + MacDialog

抽象工厂的缺陷往往不是“创建失败”,而是“创建成功但族错了”。这种错误在编译期不一定能发现,测试要主动覆盖。

二、具体工厂测试要覆盖完整产品矩阵

假设有 3 个产品族、4 个产品等级,总共需要验证的基本创建映射是:

3 * 4 = 12

这 12 个映射可以用参数化测试完成。每个具体工厂都要验证所有创建方法,不要只测最常用的一个产品。

assertThat(factory.createStorage()).isInstanceOf(AwsStorage.class);
assertThat(factory.createQueue()).isInstanceOf(AwsQueue.class);
assertThat(factory.createMonitor()).isInstanceOf(AwsMonitor.class);

如果产品对象暴露 vendor()family() 元数据,还可以统一断言所有产品的族标识一致。这样新增产品等级时,测试会提醒你补全所有具体工厂。

三、产品族协作测试比单品测试更重要

抽象工厂强调一组产品协作,因此还要测组合行为。例如云厂商产品族里,存储上传完成后会发送队列消息,监控客户端能采集同一 traceId。如果三个产品各自单测通过,但组合协议不一致,业务仍会失败。

Storage.upload(file)
  -> 返回 objectKey
Queue.send(objectKey)
  -> 消费端读取 objectKey
Monitor.record(traceId)
  -> 三者 traceId 能串起来

这类测试可以不用真实云服务,使用同一产品族的 fake 实现。目标是验证产品族内契约一致,不一定做昂贵的端到端测试。

记忆钩子:抽象工厂测试不只问“能不能造”,还要问“造出来是不是一家人,能不能一起干活”。

四、未知配置和失败路径要明确

抽象工厂常由配置选择具体产品族。未知配置、缺少凭证、region 不支持、产品等级未实现,都应该有明确失败方式,而不是返回 null 或默认落到某个厂商。

CloudFactory factory = provider.get("unknown-vendor");

推荐行为是抛出带上下文的异常,例如 UnknownProductFamilyException。如果默认回退到 AWS,测试环境可能悄悄连到真实环境,问题更严重。

失败场景推荐测试
未知厂商抛明确异常
缺少凭证启动校验失败
不支持 region拒绝创建并带 region 信息
未实现产品等级测试覆盖所有工厂方法

失败路径测试能防止抽象工厂变成“静默兜底”的风险点。

五、新增产品族如何演进

新增产品族通常是抽象工厂擅长的方向。比如已有 AWS 和阿里云,现在新增华为云,可以新增 HuaweiCloudFactory 和一组华为云产品,实现已有接口即可。

演进清单:

  1. 新增具体产品类;
  2. 新增具体工厂;
  3. 注册到 FactoryProvider
  4. 补产品矩阵测试;
  5. 补产品族协作测试;
  6. 加灰度和回滚配置。

这条路径基本不要求改动业务使用方,因为业务依赖的是抽象工厂和抽象产品。只要产品等级稳定,抽象工厂对新增族很友好。

六、新增产品等级为什么危险

新增产品等级会修改抽象工厂接口。比如原来只有 storage、queue、monitor,现在要增加 createLogClient(),所有具体工厂都要改。已有 8 个产品族时,就至少要补 8 个具体实现。

新增 1 个产品等级
已有 8 个具体工厂
需要修改 1 个抽象接口 + 8 个具体工厂 + 对应测试

如果产品等级频繁变化,抽象工厂接口会越来越大。此时可以考虑按能力拆分为多个小工厂,例如 StorageFactoryMessagingFactory;也可以使用注册表,让某些可选产品通过能力发现机制扩展。

七、如何避免接口膨胀

接口膨胀说明抽象边界可能选得太粗。一个抽象工厂不应承担整个系统所有产品创建,否则新增任何能力都会影响所有产品族。

可选改造:

问题改造方向
产品等级太多按业务域拆成多个小工厂
某些产品族不支持某能力使用能力接口或可选模块
产品族动态加载引入注册表和插件机制
测试矩阵过大分层测试和契约测试

设计目标是让稳定的一组产品留在同一个抽象工厂里,把变化频繁或可选的能力拆出去。这样既保留产品族一致性,又避免一改全改。

八、常见误区与追问

  • 误区:抽象工厂只需要测每个方法不返回 null。 还要测产品类型、产品族一致性和协作契约。
  • 误区:新增产品等级和新增产品族一样容易。 新增产品等级会修改抽象接口,影响所有具体工厂。
  • 误区:未知配置默认给一个工厂更友好。 静默兜底可能造成环境串用,应明确失败。
  • 误区:抽象工厂越大越统一。 过大的工厂会膨胀成上帝接口,演进成本升高。
  • 追问:如何做产品族契约测试? 为每个具体工厂跑同一套抽象测试,验证共同接口和协作规则。
  • 追问:如何降低测试矩阵成本? 使用参数化测试、契约测试和 fake 产品族,避免重复手写用例。
  • 追问:产品族部分支持某能力怎么办? 不要强塞进总工厂,可拆能力接口或独立扩展点。

九、加强记忆

抽象工厂的测试和演进围绕两个方向:横向新增产品族容易,纵向新增产品等级困难。测试要覆盖产品矩阵、产品族协作和失败路径;演进要防止工厂接口膨胀,必要时拆小工厂或引入注册表。