1、测试哲学:为什么需要模拟边界?领域驱动设计中的测试金字塔与六边形架构

在领域驱动设计(DDD)与六边形架构(Hexagonal Architecture)的实践中,测试的核心挑战并非“如何写代码”,而是“如何隔离领域逻辑与外部依赖”。模拟边界(Mock & Stub)正是为了解决这一隔离问题而诞生的测试哲学。

1.1 测试金字塔:从底层到顶层的投入策略

测试金字塔由 Mike Cohn 提出,指导团队将测试资源按层级分配。在 DDD 项目中,金字塔的每一层对应不同的验证目标:

层级 验证对象 典型工具 运行速度 维护成本
单元测试 领域实体、值对象、领域服务 Go 原生 testing + testify 毫秒级 低
集成测试 仓储实现、消息适配器、外部 API 客户端 testcontainers-go / httptest 秒级 中
端到端测试 完整用户旅程、系统边界交互 cucumber / godog 分钟级 高
关键原则:单元测试应占总测试量的 70% 以上。领域逻辑的每一次分支、每一个业务规则,都应在不启动数据库、不发送真实 HTTP 请求的前提下完成验证。

1.2 六边形架构:领域是核心,边界是端口

六边形架构(又称端口与适配器模式)将应用程序分为三层:

  • 领域层(内六边形):纯业务逻辑,零外部依赖。
  • 应用层:编排用例,定义输入/输出端口(接口)。
  • 基础设施层(外六边形):实现端口,连接数据库、消息队列、外部 API。

这种架构天然要求领域层不感知任何具体实现——它只依赖接口。而测试中的 Mock 与 Stub,正是这些接口的“替身演员”。

六边形架构中的测试边界与模拟对象 领域层 实体 · 值对象 · 领域服务 零外部依赖 输入端口 输出端口 HTTP 适配器 (输入) DB 适配器 (输出) 消息队列适配器 (输出) Mock / Stub Mock / Stub 测试时,用 Mock/Stub 替换外六边形适配器,领域层无需感知真实数据库或 HTTP 服务 —— 这就是“模拟边界”的核心含义

1.3 为什么必须模拟边界?三大核心原因

  1. 速度:一次真实的数据库查询可能耗时 10~50ms,而一个 Stub 返回结果仅需 0.001ms。当测试数量达到数千个时,速度差异决定开发体验。
  2. 确定性:外部服务可能超时、返回错误、或数据不一致。Mock 让你在测试中精确控制每种场景(成功、失败、超时、空结果),无需依赖外部环境。
  3. 领域隔离:DDD 要求领域逻辑独立于技术实现。如果不模拟边界,测试会同时验证领域规则和基础设施行为,导致失败时无法快速定位问题归属。
常见误区:不要对领域实体本身使用 Mock。实体是纯数据+行为,应直接构造真实实例进行测试。Mock 只应用于领域层与外部世界的接口——即六边形架构中的端口。

1.4 Mock 与 Stub 的本质区别

特性 Stub(桩) Mock(模拟)
核心目的 提供预设返回值 验证交互行为
是否验证调用次数 否 是
典型场景 仓储查询返回固定实体 验证领域服务是否调用了发送通知接口
Go 实现方式 结构体实现接口 + 硬编码返回 testify/mock 或 gomock 生成

1.5 在 Go 项目中的实践原则

  • 面向接口编程:领域层定义端口(interface),基础设施层实现它。测试时注入 Stub 或 Mock。
  • 依赖注入:通过构造函数或选项模式将依赖传入领域服务,避免在领域层内部直接 new 具体实现。
  • 测试替身只用于边界:不要 Mock 标准库、不要 Mock 领域实体、不要 Mock 你未拥有的代码。
Mock vs Stub 决策流程 需要模拟外部依赖? 只关心返回值? 还是关心调用行为? 使用 Stub 硬编码返回 + 无行为验证 使用 Mock 验证调用次数 + 参数 原则:Stub 用于查询(读),Mock 用于命令(写)

1.6 本章小结