嘲笑一个实体?

时间:2011-09-13 11:55:50

标签: tdd mocking

我听说某个地方嘲弄一个实体是个坏事,你应该只模拟服务(即使我们没有做全面的DDD,但是“sorta”DDD)。

因此,鉴于以下典型故事,您如何为其编写测试?

  • 如果客户具有“首选”状态,则应在“事件发生”时通知她。

首选状态取决于其他因素,例如,客户最近购买了10个小部件,或者他的名字以“A”开头等等。无论如何,我们假设我们已经实现了此功能。我如何为上述故事编写测试?具体来说,我对可测试性要求在这种情况下如何影响我的设计感兴趣。

  1. 我可以使用一些高级模拟框架(如Isolator),它允许我模拟非虚拟属性。可测试性和设计之间没有相关性,没问题。
  2. 我可以将IsPreferred属性设置为虚拟并模拟它(实际上是stub)。不知道为什么,但感觉很脏。请参阅帖子顶部的问题。此外,不知道它如何改善我的设计。 2A。隐藏ICustomer接口后面的实体并模拟它。完全不冷静。
  3. 我可以使它成为读写属性,但这将是一个非常糟糕的设计决定。
  4. 你如何处理这些故事?

2 个答案:

答案 0 :(得分:6)

我在一些书/博客上看过类似的事情,建议不要模仿域模型中的实体或对象。据我记得的消息是不要模拟数据,模拟行为。 (我可能在XUnit Test Patterns或GOOS一书中读过这篇文章。)

就个人而言,我认为如果你的实体需要很多设置,那么模拟它是有效的。让我举个例子,假设你有品牌,产品,价格和StockAvailability。一个品牌有产品,产品有价格和库存。 Class Under Test调用Brand.IsAvailable(),它反过来检查至少有一个产品具有有效价格和库存(这是非常多的逻辑)。因此,如果您正在尝试对CUT进行单元测试,那么测试所有Brand.IsAvailable逻辑远远超出范围,因此模拟该方法是有意义的。

如果设置实体很容易,那么使用实体,例如,如果您有一个用户并且它有一个名为active的属性,您可以创建一个用户并在测试中将该属性设置为构建虚假用户并分配该属性的成本可能与模拟方法调用相同,并且(在我看来)更清晰。

我不是c#专家,但我读过Isolator是.net的最佳框架之一,所以我会在 1 上做你提到的。

答案 1 :(得分:2)

我不会将过多的逻辑放入实体中。他们应该关心自己的状态,例如。关心自己领域的一致性。但他们不应该关心整个系统的一致性。您设置用户状态的“user.AddOrder”示例going太过IM。

如果他们做了所有事情,你也会失去实体的可重用性。如果你得到额外的要求(例如,数据导入,新订单,新类型的规则等附加情况),你的实体模型会受到影响,它就不够灵活。

单元测试中显示出缺乏灵活性。当您在单元测试中隔离类时,它会清楚实际封装的内容,并且不再可以分离。如果你遇到问题,你的封装很可能不是很有用。所以单元测试是可重用性的一个很好的证明。

示例(伪代码,不介意我是否错过了应用程序的要点,只是为了显示逻辑所属的位置):

class User
{
    AccountState State { get; }
    void MakeTopSeller()
}

class Order
{
    decimal GetTotal();
    User Salesman { get; }
}


class OrderService
{
  void AddOrder()
  {
      // ....
      if (order.GetTotal() > ToSellerAmount)
      {
          order.Salesman.MakeTopSeller();
      }
  }
}

编辑:如果您仍然认为您的方法合适(我的意思是我无法根据我所了解的方法做出决定),您无需更改它。但是,如果您的实体需要另一个实体来设置状态,并且您在测试中需要该状态,则需要在测试中执行所有这些操作:创建订单并将其添加到用户。

还有另一种方法可以解决问题。您可以将规则放入单独的类中,例如。使用策略模式。通常很难在实体上设置策略,因为底层数据库层需要注入它们。