You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

整洁架构(DDD)中用例单元测试的实体依赖如何管理

核心原则先行

首先纠正一个普遍的认知误区:单元测试的「单元」从来不是指单个类,而是单个可独立验证的、无外部IO副作用的业务逻辑单元。在DDD/整洁架构的语境下,同属领域层的实体、值对象是领域规则的载体,本身就是业务逻辑的组成部分,不属于需要隔离的外部依赖。
针对实体依赖的通用处理原则非常明确:

  • 仅对跨边界的IO类依赖(仓储、RPC接口、第三方服务、配置中心等)做Mock/Stub
  • 同领域层内无副作用的实体、值对象、领域服务,直接使用真实实例,不需要Mock
  • 实体自身的逻辑必须单独编写单元测试全量覆盖,确保实体逻辑的正确性,从根源避免「实体bug导致上层用例测试误报」的问题——哪怕真的出现这种情况,也不是误报,而是测试帮你提前发现了实体逻辑的缺陷,本来就需要修复
  • 只有当实体设计不合理、内部夹带了IO调用等副作用时,才需要考虑Mock实体,这种情况优先重构实体把副作用移到应用层/基础设施层,而非妥协Mock实体

对三类待选方案的误区澄清

  • 方案1(注入Fake实体):完全本末倒置。实体本身就是你要验证的业务逻辑的一部分,Mock实体等于把你需要校验的核心逻辑替换成了假实现,测试完全失去有效性,还会额外增加大量维护成本,属于完全不可取的方案。
  • 方案2(不Mock实体):这是正确的方向,你对「集成测试」的判断有误。集成测试的核心是验证跨边界、跨层的交互逻辑(比如用例调用真实仓储读写数据库),同属领域层的多个逻辑组件一起测试,本身就是单元测试的合理范围,不存在所谓的「测试范围过大」问题。
  • 方案3(重构生产代码解耦实体):完全没有必要。只要实体是纯领域逻辑、无外部IO依赖,本身就和用例是内聚的领域逻辑单元,不需要为了测试强行解耦。

示例场景的单元测试实现

你给出的addHorseUseCase场景中,唯一需要Mock的只有跨边界的HorseRepo依赖,HorseEntity直接使用真实实例即可,必要时可以用Spy监听实体方法的调用,不需要替换实体的真实实现。
以下是测试伪代码示例:

# 场景1:健康检查不通过,新增马匹失败
def test_add_horse_failed_when_health_unqualified():
    # 仅Mock仓储依赖
    mock_repo = Mock()
    use_case = AddHorseUseCase()
    req = AddHorseReq(name="枣红马", age=4, health_status="sick")

    result = use_case.execute(req, mock_repo, HorseEntity)

    # 验证返回错误
    assert result.err_code == "HEALTH_CHECK_FAILED"
    # 验证仓储未被调用
    mock_repo.save.assert_not_called()

# 场景2:健康检查通过,新增马匹成功
def test_add_horse_success():
    mock_repo = Mock()
    use_case = AddHorseUseCase()
    req = AddHorseReq(name="枣红马", age=4, health_status="healthy")
    # 对实体方法打Spy,仅监听调用,不替换实现
    spy_check_health = spy(HorseEntity, "checkHealth")
    spy_make_shoeing = spy(HorseEntity, "makeShoeing")
    spy_equip_saddle = spy(HorseEntity, "equipSaddle")

    result = use_case.execute(req, mock_repo, HorseEntity)

    # 验证执行成功
    assert result.is_success()
    # 验证实体方法按预期顺序调用
    assert spy_check_health.called_once()
    assert spy_make_shoeing.called_once()
    assert spy_equip_saddle.called_once()
    # 验证仓储被正确调用,参数符合预期
    mock_repo.save.assert_called_once()
    saved_horse = mock_repo.save.call_args[0][0]
    assert saved_horse.name == "枣红马"
    assert saved_horse.has_shoe == True
    assert saved_horse.has_saddle == True

内容的提问来源于stack exchange,提问作者Ecora

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 21:36:05