我的团队TDD的演变包括似乎偏离了传统的oop.
远离自给自足的类我们仍然在适当的地方封装数据.但是为了模拟任何辅助类,我们通常会创建一些通过构造函数或mutator在外部设置它们的方法.
我们永远不会使用私有方法.为了利用我们的模拟框架(RhinoMocks),这些方法不能是私有的.这是向我们的传统开发者"销售"的最大的一个.在某种程度上,我看到了他们的观点.我只是更看重测试.
你的想法是什么?
OOP只是众多可能的范例之一.它本身不是目标,它是达到目的的手段.您不必编程面向对象,而不是其他范例更适合您.
在你注意到单元测试在面向函数的语言中比面向对象的语言更容易进行单元测试时,无数聪明的人,仅仅因为测试的自然单元是一个函数.它不是一个类(可能有私有方法和各种奇怪的状态),而是一个函数.
另一方面,可测试性本身就具有价值.如果你的代码不可测试,你就无法测试它(显然),如果你无法测试它,你怎么知道它的工作原理?因此,如果你必须选择一个极端或另一个极端,我肯定会选择可测试性.
但一个显而易见的问题是,你真的需要测试每个私有方法吗?这些不是班级公共合同的一部分,可能没有单独的意义.公共方法对于测试很重要,因为它们具有特定的目的,它们必须满足这个非常具体的契约,并使对象保持一致状态等等.它们本身就是可测试的,其方式可能不是私有方法.(谁关心私人方法的作用?这不是课堂合同的一部分)
也许更好的解决方案是将一些其他私有的东西重构成单独的类.也许测试私有方法的需求并不像你想的那么大.
另外,其他模拟框架也允许你模拟私有东西.
编辑:在进一步考虑之后,我想强调只是让私人成员公开可能是一个可怕的想法.我们首先拥有私有成员的原因是:必须始终维护类不变量.外部代码必须使您的类无法进入无效状态.这不仅仅是OOP,它也是常识.私有方法只是为了让你在类内部更精细的粒度,将一些任务分解为多个方法等,但它们通常不会保持类不变量.他们做了一半的工作,然后依靠其他一些私人方法被召唤来做另一半.这是安全的,因为它们通常不可访问.只有其他类方法可以调用它们,所以只要它们保留不变量,一切都很好.
所以,虽然是的,你通过将它们公开来使私有方法可测试,你也会引入一些错误来源,单元测试无法轻易捕获这些错误.您可以使用"错误"类.无论外部代码如何使用,设计良好的类始终保持其不变性.一旦你把一切都公之于众,就不再可能了.外部代码可以调用内部帮助函数,这些函数可能不会在该上下文中使用,并且会将类置于无效状态.
单元测试无法保证不会发生这种情况.所以我说你冒着引入比你预期更大的错误来源的风险.
当然,鉴于私有成员的上述定义(那些不保留类不变量的成员),有可能安全地将许多其他方法公开,因为它们确实保留了不变量,因此不需要隐藏他们来自外部代码.这可能会减少你的问题,通过减少私人方法,但不允许外部代码打破你的课程,如果一切都是公开的话.
我不熟悉rhinomocks,事实上从来没有使用或需要一个嘲弄工具,所以我可能会离开这里,但是:
OO原则和TDD之间不应该存在冲突,因为
私有方法不需要进行单元测试,只需要公开方法
您所经历的是测试对设计施加压力.这实际上是TDD主要是设计策略的原因- 如果你注意并知道如何阅读标志,编写测试会强制实现更好的解耦设计.
将"辅助对象"注入类中实际上是一件好事.但是,您不应该将它们视为辅助对象.它们是常规对象,希望处于不同的抽象层次.它们基本上是策略,用于配置如何填充更高级别算法的细节.(我通常使用构造函数注入它们,并且还提供另一个自动使用默认生产实现的构造函数,如果有的话.)请注意 -根据我的经验,嘲笑也可能过头了.请查看http://martinfowler.com/articles/mocksArentStubs.html,了解有关该主题的一些有趣想法.
关于私有方法,我不完全理解这与你的模拟框架有什么关系 - 通常,你应该模拟接口,它们只有公共方法,无论如何,它们都是公共合同的一部分.
无论如何,在我看来,复杂的私人方法是一种代码味道 - 这表明该类可能承担了过多的责任,违反了单一责任原则.考虑什么样的课程实际上不会违反封装以使其公开.将方法移动到该类(可能在途中创建它)可能会在耦合和内聚方面改善您的设计.
良好的OO设计不仅仅是封装,而不是私有方法.
其中一个主要的设计气味是系统不同部分之间的耦合.在将辅助类注入对象以测试它们之前,您的对象如何访问帮助程序?
我的猜测是,通过切换到依赖注入,你已经降低了系统中的耦合.
还有开放/封闭原则.通过注入帮助程序类,您可以允许多态替换行为.
如果有一些关于在类上看到非私有方法的问题,可以通过它的接口使用该类,无论如何这是一个好主意,对吧?
可维护性更重要.
不要错过单元测试和OOP都有共同目标的观点.