我应该直接或通过域服务创建实体DDD和单元测试吗?

时间:2016-09-21 07:47:07

标签: c# unit-testing domain-driven-design builder readability

某些正在测试的实体无法使用构造函数直接创建,但只能通过域服务创建,因为需要使用存储库,可能需要进行一些需要在数据库中命中的验证(想象一下)唯一的代码验证)。

在我的测试中,我有两个选择:

  1. 使用公开实体创建的域服务创建实体,这要求我模拟该服务所需的所有存储库接口,并指示相关的服务器正确运行以便成功创建
  2. 以某种方式直接使用实体构造函数(我使用c#,因此我可以将内部构造函数暴露给测试程序集)并让实体绕过服务逻辑。
  3. 我不确定哪种方法最好,
    第一个是我喜欢的,因为它测试域模型的公共行为,因为从外部角度来看,创建实体的唯一方法是通过Domanin服务。但是由于需要模拟配置,这个解决方案带来了很多“编配”代码 第二个更直接,它创建了绕过服务逻辑的对象,但它是对域模型的一种欺骗,它假设测试代码知道域模型的内部,这不是一个好点。但代码更具可读性。

    我在测试中使用Builders创建实体,因此第一种方法所需的配置代码将在构建器代码中被隔离,但我仍然想知道什么是正确的方法。

2 个答案:

答案 0 :(得分:0)

基本上你在问你应该测试什么级别。选项2是一个单元测试,因为它只测试单个类的代码。选项1更像是集成测试,因为它将一起测试几个组件。

我倾向于选择选项2进行单元测试,原因如下:

  1. 如果仅测试单个类,则单元测试更简单,更有效。如果使用工厂服务来创建测试对象,则测试无法直接控制对象的构造方式。这将导致混乱和乏味的测试代码,例如模拟所有存储库接口。

  2. 在我的测试代码库的不同部分,我通常会有实际的集成测试(或验收测试),它通过它的公共接口从前到后测试整个应用程序(具有外部依赖性,如数据库模拟) /剔除)。我希望这些测试能够覆盖你的问题中的选项1,所以我真的不需要在单元测试套件中重复选项1.

  3. 您可能会问,为了测试几个课程,启动整个应用程序有什么意义?答案非常简单 - 只需坚持两个级别的测试,您的测试代码库将是干净,可读且易于重构的。如果你的测试在他们测试的'级别'方面变化很大(有些测试单个类,有些类在一起,有些是整个应用程序),那么测试代码就变得难以维护。

    一些警告:

    1. 如果您正在开发将部署和运行的“应用程序”,那么此建议适用于您。如果您正在开发一个“共享库”,将其分发给其他团队以供他们认为合适使用,那么您应该从所有公共入口点进行测试,无论“级别”如何。 (但我仍然不会将这些测试称为“单元测试”,并将它们在代码库中分开。)
    2. 如果你没有能力编写完整的集成测试,那么我会使用选项1和2.只是要小心测试代码库变得臃肿。
    3. 还有一点 - 如果因为同样的原因而改变的话,一起测试。选择选项1后,想要结束的情况是每次更改工厂/存储库代码时都必须更改实体测试。如果每个实体的行为没有改变,那么您不必更改测试。

答案 1 :(得分:0)

您可以通过首先不通过域名服务创建您的实体来避免这个难题。

如果您觉得在创建实体之前需要验证某个实体,可能会将其视为域不变量并由Aggregate强制执行。该聚合根将公开一种创建实体的方法。

只要负责产生新实体的聚合体保证了不变量,就可以针对内存中的具体对象测试所有内容,因为聚合内部应该拥有所有需要的数据以检查不变量 - 没有求助于外部存储库。您可以将创建者聚合设置为在内存中处于不变的中断状态或非不变的状态,并直接在聚合的CreateMyEntity方法上执行测试。

Udi Dahan的

Don't Create Aggregate Roots对这种方法是一个很好的解读 - 基本思想是实体和聚合根源不仅仅是天生的。