嘲笑一个实体?
我在某处听说模拟实体是一件坏事,您应该只模拟服务(即使我们没有做成熟的 DDD,而是“某种”DDD)。
那么,考虑到以下典型故事,您如何为其编写测试?
- 如果客户具有首选状态,则应在发生情况时通知她。
首选状态取决于其他因素,例如客户最近购买了 10 个小部件,或者他的名字以“A”开头,等等。无论如何,假设我们已经实现了此功能。我如何为上述故事编写测试?具体来说,我对在这种情况下可测试性要求将如何影响我的设计感兴趣。
- 我可以使用一些高级模拟框架(如 Isolator),它可以让我模拟非虚拟属性。可测试性和设计之间没有关联,没有问题。
- 我可以将 IsPreferred 属性设为虚拟并模拟它(实际上是存根)。不知道为什么,就是感觉很脏。请参阅帖子顶部的问题。另外,不确定它如何改进我的设计。 2a.隐藏 ICustomer 接口后面的实体并模拟它。完全不酷。
- 我可以将其设置为读写属性,但这将是一个非常糟糕的设计决策。
您如何处理此类故事?
I've heard somewhere that mocking an entity is a BAD thing, and you should only mock services (even if we're not doing full-blown DDD, but "sorta" DDD).
So, given the following typical story, how do you write a test for it?
- If a customer has a Preferred status, she should be notified when Stuff happens.
Preferred status is something dependent on other things, like, the customer has recently purchased 10 Widgets, or his name starts with "A", etc. Anyway, let's suppose we have already implemented this functionality. How do I write a test for the above story? Specifically, I'm interested in how testability requirement would affect my design in this case.
- I can use some advanced mock framework (like Isolator) which lets me mock non-virtual properties. No correlation between testability and design, no problem.
- I can make the IsPreferred property virtual and mock it (stub actually). Don't know why, but that feels dirty. See the question at the top of the post. Also, not sure how it improves my design.
2a. Hide the entity behind the ICustomer interface and mock it. Totally uncool. - I can make it a read-write property, but that would be a very bad design decision.
How do you handle such stories?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我在几本书/博客文章中读到了类似的内容,建议不要模拟域模型中的实体或对象。据我记得,这条消息是不要模拟数据,而是模拟行为。 (我可能在《XUnit 测试模式或 GOOS》一书中读到过这一点)。
就我个人而言,我认为如果您的实体需要大量设置,那么模拟它是有效的。让我给你举个例子,假设你有品牌、产品、价格和库存。品牌有产品,产品有价格和库存。 被测类 调用 Brand.IsAvailable(),后者依次检查是否至少有一个产品具有有效的价格和库存(这是相当多的逻辑)。因此,如果您尝试对 CUT 进行单元测试,测试所有 Brand.IsAvailable 逻辑就远远超出了范围,因此模拟该方法是有意义的。
如果设置实体很容易,则使用该实体,例如,如果您有一个 User 并且它有一个名为
active
的属性,您可以创建一个用户并在测试中将该属性设置为构建假用户并分配该属性的成本可能与模拟方法调用相同,而且(在我看来)更清晰。我不是 ac# 专家,但我了解到 Isolator 是 .net 最好的框架之一,所以我会按照您在 1 中提到的操作进行操作。
I've read on a few books / blogposts a similar thing that suggested not to mock Entities or objects from the domain model. As far as I remember the message was don't mock data, mock behaviour. (I probably read this in the book XUnit Test Patterns or GOOS).
Personally, I think that if your entity needs a lot of setup, it's valid to mock it. Let me give you an example, imagine you have Brands, Products, Prices and StockAvailability. A Brand has Producs, Products have Prices and Stock. The Class Under Test invokes Brand.IsAvailable() which in turns checks that there's at least one product with a valid price and in stock (that's quite a lot of logic). So if you're trying to unit test the CUT, testing all the Brand.IsAvailable logic is well out of scope, so it would make sense to mock that method.
If it's easy to set up the entity, then use the entity, for example if you have a User and it has a property called
active
, you could create a user and set that property in your test as the cost of building a fake user and assigning that property is probably the same as mocking the method call, and it's (in my view) clearer.I'm not a c# expert, but I've read that Isolator is one of the best frameworks for .net, so I would do what you mentioned on 1.
我不会在实体中放入太多逻辑。他们应该关心自己的状态,例如。关心自己领域的一致性。但他们不应该关心整个系统内的一致性。恕我直言,您设置用户状态的“user.AddOrder”示例太过分了。
如果实体做了所有事情,您也会失去实体的可重用性。如果您有额外的要求(例如,额外的情况,如数据导入、新的订单类型、新的规则类型),您的实体模型就会阻碍,它不够灵活。
这种灵活性的缺乏表现在单元测试中。当您在单元测试中隔离类时,就会清楚什么是实际封装的并且不能再分离。如果您遇到这样的问题,那么您的封装很可能不是很有用。所以单元测试是可重用性的一个很好的证明。
示例(伪代码,不要介意我是否错过了您的应用程序的要点,它只是为了显示逻辑属于哪里):
编辑:如果您仍然认为您的方法是合适的(我的意思是我可以不要根据我的了解来决定),你不需要改变它。但是,如果您的实体需要另一个实体来设置状态,并且您在测试中需要该状态,则需要在测试中执行所有这些操作:创建订单并将其添加到用户。
还有另一种方法可以将事情分开。您可以将规则放入单独的类中,例如。使用策略模式。通常很难对实体设置策略,因为底层数据库层需要注入它们。
I would't put too much logic into entities. They should care about their own state, eg. care about consistency of their own fields. But they shouldn't care about consistency within the whole system. Your example of "user.AddOrder" that is setting a user state is going much too far IMHO.
You also loose reusability of your entities if they do everything. If you get additional requirements (eg. additional situations like import of data, new kind of order, new kind of rule), your entity model gets in the way, it is not flexible enough.
This lack of flexibility shows up in the unit tests. When you isolate classes in the unit tests, it gets clear what's actually encapsulated and can't be separated anymore. If you get a problem with that, your encapsulation is most probably not very useful. So unit tests are a great proof of reusability.
Example (pseudocode, don't mind if I miss the point of your application, it's just to show where logic belongs to):
Edit: If you still think that your approach is appropriate (I mean I can't decide from what I know about it), you don't need to change it. But if your entity takes another entity to set a state, and you need that state in the test, you need to do all this in the test: create an order and add it to the user.
There is another way to split things off. You could put the rule into a separate class, eg. using the strategy pattern. It is usually hard to set strategies on entities, because the underlying database layer needs to inject them.