更重要的是,代码的可测试性或遵守OOP原则?

时间:2008-11-14 17:27:24

标签: oop mocking tdd

我的团队TDD的演变包括似乎偏离了传统的oop。

  1. 远离自给自足的课程 我们仍然在适当的地方封装数据。但是为了模拟任何辅助类,我们通常会创建一些通过构造函数或mutator在外部设置它们的方法。

  2. 我们永远不会使用私有方法。 为了利用我们的模拟框架(RhinoMocks),这些方法不能是私有的。这是向我们的传统开发者“销售”的最大的一个。在某种程度上,我看到了他们的观点。我只是更看重测试。

  3. 你有什么想法?

15 个答案:

答案 0 :(得分:24)

OOP只是众多可能的范例之一。它本身不是目标,它是达到目的的手段。您不必编程面向对象,而不是其他范例更适合您。

无数聪明的人在你说过单元测试在面向函数的语言中比面向对象的语言更容易,只是因为测试的自然单元是一个函数。它不是一个类(可能有私有方法和各种奇怪的状态),而是一个函数。

另一方面,可测性本身确实有价值。如果你的代码不可测试,你就无法测试它(显然),如果你无法测试它,你怎么知道它的工作原理? 所以如果你必须选择一个极端或另一个极端,我肯定会选择可测试性。

但一个显而易见的问题是,你真的需要测试每个私有方法吗?这些不是班级公共合同的一部分,可能没有单独的意义。公共方法对于测试很重要,因为它们具有特定的目的,它们必须满足这个非常具体的契约,并使对象保持一致状态等等。 它们本身就是可测试的,其方式可能不是私有方法。 (谁关心私人方法的作用?这不是课堂合同的一部分)

或许更好的解决方案是将一些其他私有的东西重构成单独的类。也许测试私有方法的需求并不像你想的那么大。

另一方面,其他模拟框架也可以让你模拟私有东西。

编辑: 在进一步考虑之后,我想强调只是让私人成员公开可能是一个可怕的想法。 我们首先拥有私人会员的原因是: 必须始终保持类不变量。外部代码必须使您的类无法进入无效状态。这不仅仅是OOP,它也是常识。私有方法只是允许您在类内部更精细的粒度,将一些任务分解为多个方法等,但它们通常保留类不变量。他们做了一半的工作,然后依靠其他一些私人方法被召唤来做另一半。这是安全的,因为它们通常不可访问。只有其他类方法可以调用它们,所以只要它们保留不变量,一切都很好。

所以,虽然是的,你可以通过将私有方法公之于众来使私有方法可测试,但你也会引入一些错误来源,这些错误可以通过单元测试轻松捕捉到 。您可以使用“错误”类。无论外部代码如何使用,设计良好的类始终保持其不变性。一旦你把一切都公之于众,那就再也不可能了。外部代码可以调用内部帮助函数,这些函数可能不会在该上下文中使用,并且会使类处于无效状态。

单元测试无法保证不会发生这种情况。所以我会说你冒着引入比你预期更大的错误来源的风险。

当然,鉴于私有成员(不保留类不变量的那些)的上述定义,可以安全地将许多其他方法公开,因为它们保留不变量,所以没有必要将它们从外部代码中隐藏起来。 通过为您提供更少的私有方法,但不允许外部代码破坏您的类,这可能会减少您的问题,如果所有是公开的那样。

答案 1 :(得分:14)

我对rhinomocks不熟悉,事实上从未使用或需要一个嘲弄工具,所以我可能会离开这里,但是:

  • OO原则和TDD之间应该存在 no 冲突,因为
  • 私有方法不需要进行单元测试,只需要公开方法

答案 2 :(得分:9)

您所遇到的是测试会对设计产生影响。这实际上是TDD主要是设计策略的原因 - 编写测试会强制实现更好的解耦设计,如果你注意并知道如何阅读标志。

将“辅助对象”注入类中实际上是一件好事。但是,您不应将它们视为帮助程序对象。它们是常规对象,希望处于不同的抽象层次。它们基本上是策略,用于配置如何填充更高级别算法的细节。(我通常使用构造函数注入它们,并且还提供另一个自动使用默认生产实现的构造函数,如果有的话。)请注意 - 根据我的经验,嘲笑也可能过头了。看看http://martinfowler.com/articles/mocksArentStubs.html对该主题的一些有趣想法。

关于私有方法,我不完全理解这与你的模拟框架有什么关系 - 通常,你应该模拟接口,它们只有公共方法,无论如何,它们都是公共合同的一部分。

无论如何,在我看来,复杂的私人方法是一种代码味道 - 这表明该类可能承担了过多的责任,违反了单一责任原则。考虑一下它实际上会违反封装以使其公开的类型。将方法移动到该类(可能在途中创建它)可能会在耦合和内聚方面改善您的设计。

答案 3 :(得分:5)

良好的OO设计不仅仅是封装,而是拥有私有方法。

主要设计气味之一是系统不同部分之间的耦合。在将辅助类注入对象以测试它们之前,您的对象如何访问帮助程序?

我的猜测是,通过切换到依赖注入,你已经降低了系统中的耦合。

还有Open/Closed principle。通过注入帮助程序类,您可以允许多态替换行为。

如果有人担心在类上看到非私有方法,可以通过它的接口使用该类,不管怎么说这是个好主意,对吗?

答案 4 :(得分:5)

可维护性更重要。

不要错过单元测试和OOP都有共同目标的观点。

答案 5 :(得分:3)

OOP是一种工具,而非目标

答案 6 :(得分:1)

依赖注入&控制反转是获得两全其美的相当好的方法。好的设计不应该限制你以不同的方式使用代码的能力(测试),它应该改进它。

我们现在正在重新编写一大堆单身人士来使用DI / IOC,这样我们就可以对它们进行测试(即将推出到您附近的有线电视盒)。

答案 7 :(得分:1)

您还可以调查dependency injection作为外部化某些帮助程序类的方法

答案 8 :(得分:1)

如果有一个框架限制我使用类似私有方法的东西,我放弃它。期。这是大多数语言中需要的基本内容之一。

答案 9 :(得分:1)

我使用Rhino Mocks,我使用了很多私有方法。我通常编写我的类来依赖于接口而不是其他类,因为这使得模拟变得更容易。

答案 10 :(得分:1)

关于类范围,您可以使用内部来简化框架(对于最终用户)并在框架项目中指定AssemblyInfo.cs

[assembly: InternalsVisibleTo("MyAssembly.Tests")]

答案 11 :(得分:1)

我不熟悉他们之间的任何冲突。高内聚和低耦合是OO的核心,也恰好是测试的核心。

答案 12 :(得分:0)

作为Intellisense的常规用户,所有方法都是公开的要求会让我感到疯狂。我个人会在此基础上坚持OOP。

答案 13 :(得分:0)

为什么不使用宏而不是私有关键字。当您使用“testmode”进行编译时,这些方法是公开的。否则他们是私人的。使用宏,一旦您在测试模式下编译,当您公开使用私有方法时,仍然会收到编译器警告。有趣的是,让你的私有方法失败他们的单元测试并不意味着你的程序中存在错误,尽管它可能等同于一个函数,“CauseBSOD”从未调用过。这是一个严重破坏的功能(假设不会导致BSOD),但就用户而言,它不是一个错误。

答案 14 :(得分:0)

为什么不能使用仅属于测试类的朋友类?或者,您的测试类派生自具有私有成员的父类。

在我看来,公开一切以迎合糟糕的测试工具是一个错误。正如其他人所说 - 这不应该是一个选择。设计好的软件。测试好并使用好的工具。如果你必须打破一些测试工具的知名最佳实践,那么现在是时候重新考虑你的测试工具......