单元测试域对象
我们需要在用户在活动页面上输入电子邮件地址时添加活动提醒。事件是另一个领域对象。我们最初的想法是创建一个 Customer 域对象和相关的 CustomerService:
public class CustomerService {
public void AddEventReminder(string emailAddress, int eventId) {
var customer = new Customer(emailAddress);
customer.AddEmailReminder(eventId);
}
}
我们如何在单元测试中验证新客户确实调用了 AddEmailReminder 方法?
我的想法:
- 用工厂来创造客户。这闻起来很臭,因为我认为你只应该在对象创建有一定复杂性的地方使用工厂。
- 糟糕的代码。也许有更好的方法来做到这一点?
- 起订量魔法。
另请注意(也许是相关的),我们如何确定这里的聚合根?我们任意决定客户是,但它同样可以是事件。我已阅读并理解有关聚合根的文章,但在这种情况下尚不清楚。
We have a requirement to add an event reminder when a user enters their email address on an event page. Event is another domain object. Our initial thought was to create a Customer domain object and related CustomerService:
public class CustomerService {
public void AddEventReminder(string emailAddress, int eventId) {
var customer = new Customer(emailAddress);
customer.AddEmailReminder(eventId);
}
}
How can we verify in a unit test that the AddEmailReminder method was indeed called on the new customer?
My thoughts:
- Use a factory to create the customer. This smells because I thought you were only supposed to use factory where there was some complexity in the object creation.
- Bad code. Maybe there is a better way to do this?
- Moq magic.
On a separate note (maybe it is related), how do we decide which is the aggregate root here? We have arbitrarily decided the customer is, but it could equally be the event. I have read and understand articles on aggregate roots, but it is unclear in this scenario.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
在这种情况下,我会在创建客户的服务中创建一个受保护的方法,在测试中使用匿名内部类覆盖该方法,并使其返回一个模拟 Customer 对象。然后,您可以在模拟 Customer 对象上验证是否调用了 AddEmailReminder。
类似于:
在测试中(假设 C# 知识有限,但它应该说明这一点):
In cases like this I would create a protected method in the service that creates the customer, in the test override that method it with anonymous inner class, and make it return a mock Customer object. Then you can verify on the mock Customer object that AddEmailReminder was called.
Something like:
and in the test (assume limited C# knowledge, but it should illustrate the point):
对 CustomerService API 的思考
您决定将此操作封装在 CustomerService 中是否有任何特殊原因?对我来说,这看起来有点贫血。是否可以直接封装在客户上?
也许您遗漏了一些 CustomerService 代码示例来简化事情...
但是,如果必须,更改签名以采用 Customer 实例可以解决问题:
但话又说回来,Int32 几乎不符合域对象的资格,因此签名应该真正的问题是,
现在的问题是这种方法是否能增加任何价值?
哪一个是聚合根?
我认为都不是。聚合根表示您仅通过根来管理子项,在这种情况下这两种方式都没有意义。
考虑以下选项:
如果将 Event 作为根,则意味着您可能没有 CustomerRepository,并且检索、编辑和保留 Customer 的唯一方法是通过 Event。这对我来说听起来非常错误。
如果将 Customer 作为根,则可以没有 EventRepository,并且检索、编辑和保留事件的唯一方法是通过特定的 Customer。这对我来说同样是错误的。
唯一剩下的可能性是它们是单独的根。这也意味着它们彼此之间只是松散连接,并且您将需要某种领域服务来查找客户的事件或事件的客户。
Thoughts on the CustomerService API
Are there any particular reasons why you have decided to encapsulate this operation in a CustomerService? This looks a little Anemic to me. Could it possibly be encapsulated directly on the Customer instead?
Perhaps you left out something of the CustomerService code example to simplify things...
However, if you must, changing the signature to take a Customer instance solves the problem:
but then again, an Int32 hardly qualifies as Domain Object, so the signature should really be
The question is now whether this method adds any value at all?
Which is the aggregate root?
None of them, I would think. An aggregate root indicates that you manage children only through the root, and that wouldn't make sense either way in this case.
Consider the options:
If you make Event the root, it would mean that you could have no CustomerRepository, and the only way you could retrieve, edit and persist a Customer would be through an Event. That sounds very wrong to me.
If you make Customer the root, you can have no EventRepository, and the only way you could retrieve, edit and persist an Event would be through a specific Customer. That sounds just as wrong to me.
The only remaining possibility is that they are separate roots. This also means that they are only loosely connected to each other, and that you will need some kind of Domain Service to look up Events for a Customer, or Customers for an Event.