TDD,DDD和封装

经过几年跟随我工作地点的“建筑师”传下来的不良做法并认为必须有更好的方法,我最近一直在阅读TDD和DDD,我认为原则和实践将是一个非常适合我们编写的软件的复杂性。

但是,我见过的许多TDD示例都在域对象上调用一个方法,然后测试对象的属性以确保正确执行行为。

另一方面,业内几位受人尊敬的人(Greg Young最着名的是关于CQRS的讨论)主张通过删除所有“getters”来完全封装每个域对象。

因此,我的问题是:如果禁止检索域状态,如何测试域对象的function?

我相信我错过了一些基本的东西所以请随时称我为白痴并启发我 – 任何指导都将不胜感激。

你所描述的是状态validation,其中你断言域对象的状态。 TDD的一个分支称为行为validation ,它利用了Mock对象。

行为validation允许您指定应调用哪些方法,如果需要,还可以指定不调用哪些方法。

请查看Martin Fowler的这篇文章了解更多细节: 模拟不是存根 。

好的,这个答案是一年太晚了;-)

但是,当您想要测试CQRS模型时,可以对触发的域事件进行断言,而不是对实体状态进行断言。

例如,如果您想测试是否调用:customer.Rename(“Foo”)会导致正确的行为。

而不是测试customer.Name是否等于“foo”,而是测试在待处理事件存储中是否存在值为“Foo”的待处理CustomerRename事件。 (根据实施情况,在您的uow或您的实体事件列表中)

如果你真的要去禁止检索状态,那么你将仅限于行为测试,可能是通过一个模拟框架,如TypeMock,它有能力跟踪你的对象的行为。 如果你能够做纯BDD,那么理论上你可以通过它的行为方式断言整个系统的正确性。

在实践中,我发现BDD在很多情况下比仅有状态测试更脆弱。 虽然有些人可能会要求某种理论,但只有在适合你的情况下才有效。 基于状态的测试仍占我们编写的所有unit testing的90%,我们对团队中的BDD了如指掌。

做什么最适合你。

一些事情。

首先,当您执行TDD之类的操作以使您的代码可测试时,您最终会使用较小的类。 如果你有一个有很多私有属性的类你无法检查,那么它很有可能被分成多个类并且更容易测试。

其次,oldschool OO架构试图通过使用语言保护来防止事物被访问,从而使软件安全。 TDD体系结构通过编写validation代码实际function的测试来使软件更加健壮,而不太强调使用语言结构来确保程序不执行的操作。

最后,检查属性并不是validation代码执行它应该执行的操作的唯一方法。 xUnit Design Patterns一书记录了其他方法: http : //xunitpatterns.com/Result%20Verification%20Patterns.html

我调用系统的公共输入方法(即我将输入数据推送到系统中),然后我得到(并断言)系统的输出。 我不测试系统的内部状态,而是测试其公共/可见行为: 应该测试内部实现,还是仅测试公共行为?

你提到的是状态测试。 还有行为测试。 用于此的技术是dependency injection,控制反转和模拟:

您的类的所有副作用都是作为其“依赖项”的方法调用实现的 – 即从外部提供的对象,通常在构造函数中。 然后,在您的unit testing中,您提供假对象而不是真实对象。 假对象可以记住它的’某个方法是否被调用,这就是你在测试中断言的内容。

存在许多Mocking Frameworks,它们通过动态生成实现给定接口的类来自动化模拟对象创建。 最受欢迎的是Rhino.Mocks和Moq。

嘿Justin,和你一样,我最近考虑将getter添加到我的只写域对象中,以便进行unit testing,但现在我确信我错了。 假设你首先想到了一个只写域的想法,那么如果你有吸气剂,你就会遇到麻烦。 只写域原则要求您从域对象触发事件,或从域对象写入的投影中读取,或者类似的东西。 一旦你暴露了getter,你就会开始暴露对象的“形状”,正如Greg Young所说,“Domain对象有行为,而不是Shape”。

话虽如此,我正在努力解决同样的问题…你如何对只写域对象进行unit testing? 这是我目前的计划:我正在考虑让我的域对象触发一个域事件,说“这些属性发生了变化”,在我的unit testing中,我会在发送“EditCommand”之前注册它。 查看Udi Dahan关于域事件的post,还可以看看Eric Evans关于域事件的内容 。

在我最终偶然发现以下文件之前,我一直想知道同样的事情。 我发现它们是执行行为validation的绝佳引子,特别是第一个为我提供了几个“aha时刻”:

  1. 使用模拟和测试来设计基于角色的对象
  2. 模拟角色,不是对象