在 Clean Architecture(或 DDD)中测试用例时如何管理实体依赖性

How to manage entity dependencies when testing a use case in Clean Architecture ( or DDD )

备注:

我们走吧:

我目前正在尝试在新项目中实施 DDD 和清洁架构原则。然而,我已经坚持了 1 周 ---> 如何为我的用例编写单元测试

这是我的问题:

在干净的架构中,当我们创建一个用例(或 DDD 中的域服务)时,在大多数情况下它将依赖于一定数量的实体 + 其余的(存储库,api ... )

为了编写我的用例的单元测试,我开始于:

- 模拟与外部交互的“其余”依赖项(存储库,API ...)

- 但是接下来,我应该如何处理单元测试中的实体依赖项?

以下是我想到的解决方案:

- 但是,通过我对单元测试最佳实践的阅读,我明白我们应该尽可能避免创建模拟,因为它们是“代码味道”,一个好的设计应该可以在没有它们的情况下进行。

- 的确,嘲笑我的实体意味着我削弱了我的测试。测试将与我模拟的实体紧密耦合。

- 此外,重新创建实体的结构对我来说似乎毫无意义...

* 如果我的用例使用多个实体方法:那么我应该重新创建每个方法的 return 值。

* 如果我的实体结构很复杂,我最终会编写复杂的假货,因此我的测试会失去很多可靠性,而且我的假货更有可能是错误的,而不是我原来的实体)

* 更糟糕的是,如果我使用一个工厂,那么我将不得不制作一个工厂的假货 -> 而那个假货将不得不建立一个假的实体......

- 另一方面,如果我不模拟我的实体,那么我会采用我认为的方式进行集成测试:测试不同实体之间的交互 ​​...

- 也正如一些 mocking 支持者所指出的那样:如果我不 mock 我的依赖项,那么即使我的测试单元是有效的,如果我的依赖项导致错误,测试也会失败。这会导致我的测试出现误报信号...

- 通过阅读几篇文章,一些文章提供了限制耦合的解决方案(将副作用与其余逻辑隔离,将逻辑与 I/O 隔离,使用纯函数,依赖注入,...)但即使应用所有这些,用例也将无法补救地需要这些实体来工作......因此它不是解决方案。

可是那怎么办呢?我错过了什么吗?
如何对用例(或 DDD 中的服务域)进行良好的单元测试? : 如何管理被测单元具有实体依赖性的单元测试中的实体?

例子:

为了说明我的问题并更好地理解您的答案,这里是该问题的一个虚构示例:

假设我有以下实体:

+class HorseEntity() 
    +constructor(name,age,health, ...) 
    +equipSaddle() 
    +makeShoeing() 
    +checkHealth() 

我想创建一个用例来将一匹马添加到我的马厩:

+class addHorseUseCase() 
    +execute(requestDto,HorseRepo,HorseEntity, ...) 

这个用例是:

  1. 创建马实体
  2. 检查马的健康状况
  3. 给马穿鞋
  4. 给马配上鞍
  5. 并将其添加到马厩。

创建测试时,我应该如何处理“HorseEntity”依赖项?

我的问题总结:

- 如何为用例编写良好的单元测试以及如何在我的测试中处理实体依赖关系?
- 一般来说,我应该如何处理单元测试中的实体依赖项?
- 在上面的这个例子中,如何为“addHorseUseCase”编写一个好的单元测试?

感谢您以后的回答。

PS: 我把这个问题从法语翻译成英语,如果你不明白我其中一个句子的措辞,请不要犹豫告诉我,这样我可以编辑它。

您正在寻找“什么有意义?”回答,所以我只能告诉你我的观点。

首先你不需要模拟所有依赖项或者你在其他测试中模拟字符串和数组列表对象吗?

我的目标是使我的单元测试尽可能快并且易于设置。

  1. 如果在纯 POJO 上运行,测试将非常快。您的用例使用实体,而实体是 POJO,因此您的用例测试将 运行 快速。
  2. 我模拟了提供实体的存储库,因为存储库通常将用例连接到外部系统,例如数据库或休息服务。这些系统会使测试变慢并且难以设置。

所以回答你的问题...

How to write a GOOD unit test for a use case and how to handle entity dependencies in my test ?

使用解决方案 2:我不模拟实体。我经常这样做。

In general, what should I do with my entity dependencies in my unit tests?

您可以模拟它们,但这会使您的代码更加复杂。所以只要使用它们并确保它们是普通对象即可。

In this example above how to write a good unit test for "addHorseUseCase"?

+class addHorseUseCase() 
    +execute(requestDto,HorseRepo,HorseEntity, ...) 
  1. 创建一个 HorseRepo 模拟一个 requestDto、一个 HorstEntity 以及调用用例所需的任何内容。
  2. 调用用例。
  3. 对用例响应进行断言。

编辑

I also made the decision to not mock my entities in order to have the simplest unit test. So, don't you feel like you're doing an integration test?

这是一种集成测试,因为您测试组合在一起的各个软件模块。但是您不与外部系统集成。我想这是关于定义集成对您意味着什么。

这是我的 3 个定义测试:

  • unit: 单个 class 或函数的 api 孤立。

  • 组件:一组 class 服务于一个问题而不涉及外部系统的 es 或函数。

  • 集成:与外部系统的交互行为 - 他们的 apis.

我对这些不同测试的权重最好用金字塔形象化。

              /\       <-- integration
             /--\
            /    \     <-- component
           /      \
          /--------\   
         /          \  <-- unit
        +------------+

如您所见,我通常会尝试尽可能多地进行组件和单元测试,并且根据需要(必需)进行很少的集成测试。我这样做是为了实现我的目标:“我想要尽可能多的测试 运行 快速且易于设置。”

现在问题可能出现了:“定位失败怎么办?”

例如当您对用例和实体层进行隔离测试时,很容易看出错误发生的位置。当测试组合测试时,您不能说错误是发生在实体层还是用例层。没错,但您还应该对实体进行单元测试。如果是这样,它们也可能会失败,并且作为体系结构的后果(用例取决于实体),您要么在用例和实体层中出现故障,要么用例失败是实体层中故障的结果。因此,您应该遵循架构并首先修复实体测试,因为用例依赖于它们。之后您将查看是否有任何用例失败幸存。

我想说的是,当涉及到故障定位时,通过模拟实体来隔离用例测试与组件测试相比有一个小优势,但创建了更多的代码来管理。所以这是一个权衡。