经过几年跟随我工作地点“建筑师”传下来的不良做法并认为必须有更好的方法,我最近一直在阅读TDD和DDD,我认为原则和做法非常适合我们编写的软件的复杂性。
但是,我见过的许多TDD示例都在域对象上调用一个方法,然后测试对象的属性以确保行为正确执行。
另一方面,业内几位受人尊敬的人(Greg Young最着名的是关于CQRS的讨论)主张通过删除所有“getters”来完全封装每个域对象。
因此,我的问题是:如果禁止检索域状态,那么如何测试域对象的功能呢?
我相信我错过了一些基本的东西,所以请随时称我为白痴并启发我 - 任何指导都将不胜感激。
答案 0 :(得分:17)
您所描述的是状态验证,其中您断言域对象的状态。 TDD的一个分支被称为行为验证,它利用了Mock对象。
行为验证允许您指定应调用哪些方法,以及是否需要调用哪些方法。
请参阅Martin Fowler的这篇文章了解更多详情:Mocks Aren't Stubs。
答案 1 :(得分:9)
好的,这个答案已经晚了一年; - )
但是当你想测试CQRS模型时,可以对被触发的域事件进行断言,而不是对实体状态进行断言。
e.g。 如果你想测试是否调用:customer.Rename(“Foo”)会导致正确的行为。
而不是测试customer.Name是否等于“foo”,而是测试在待处理事件存储中是否存在值为“Foo”的待处理CustomerRename事件。 (根据实施情况,在您的uow或实体事件列表中)
答案 2 :(得分:4)
如果你真的要去禁止检索状态,那么你将被限制在行为测试中,可能是通过一个模拟框架,如TypeMock,它有能力跟踪你的对象的行为。如果你能够做纯BDD,那么理论上你可以通过它的行为方式断言你整个系统的正确性。
在实践中,我发现BDD在很多情况下比仅有状态测试更脆弱。虽然有些人可能会要求某种理论,但它只适用于你的工作。基于状态的测试仍占我们编写的所有单元测试的90%,我们对团队中的BDD了如指掌。
做最适合你的事情。
答案 3 :(得分:2)
一些事情。
首先,当您执行TDD之类的操作以使您的代码可测试时,您最终会使用较小的类。如果你有一个有很多私有属性的类你无法检查,那么它很有可能被分成多个类并且更容易测试。
其次,oldschool OO架构试图通过使用语言保护来防止事物被访问,从而使软件安全。 TDD体系结构通过编写验证代码实际功能的测试来使软件更加健壮,不太重视使用语言结构来确保程序不执行的操作。
最后,检查属性并不是验证代码执行操作的唯一方法。 xUnit Design Patterns一书记录了其他方法:http://xunitpatterns.com/Result%20Verification%20Patterns.html
答案 4 :(得分:2)
我调用系统的公共输入方法(即我将输入数据推送到系统中),然后我得到(并断言)系统的输出。我没有测试系统的内部状态,而是测试其公开/可见行为:Should one test internal implementation, or only test public behaviour?
答案 5 :(得分:2)
你提到的是状态测试。还有行为测试。用于此的技术是依赖注入,控制反转和模拟:
您的类的所有副作用都是作为其“依赖项”的方法调用实现的 - 即从外部提供的对象,通常是在构造函数中。然后,在您的单元测试中,您提供虚假对象而不是真实对象。假对象可以记住它的'某个方法是否被调用,这就是你在测试中断言的内容。
存在许多模拟框架,它们通过动态生成实现给定接口的类来自动创建模拟对象。最受欢迎的是Rhino.Mocks和Moq。
答案 6 :(得分:2)
嘿贾斯汀,和你一样,我最近考虑为了单元测试而将getter添加到我的只写域对象中,但现在我确信我错了。假设你首先考虑了一个只写域的想法,那么如果你有吸气剂,你就会遇到麻烦。只写域原则要求您从域对象触发事件,或从域对象写入的投影中读取,或者类似的东西。一旦暴露了getter,你就会开始暴露对象的“形状”,正如Greg Young所说,“Domain对象有行为,而不是Shape”。
话虽如此,我正在努力解决同样的问题......你如何对只写域对象进行单元测试?这是我目前的计划:我正在考虑让我的域对象触发一个域事件,说“这些属性发生了变化”,在我的单元测试中,我会在发送“EditCommand”之前注册它。查看Udi Dahan关于域名活动here的帖子,并查看what Eric Evans says about Domain Events。
答案 7 :(得分:1)
我一直想知道同样的事情,直到我最后偶然发现了以下文件。我发现它们是执行行为验证的绝佳引子,尤其是第一个为我提供了几个“aha时刻”的引物: