17

我的团队对 TDD 的演变包括似乎与传统 oop 的不同之处。

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

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

你怎么认为?

4

15 回答 15

24

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

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

另一方面,可测试性本身确实具有价值。如果你的代码不可测试,你就不能测试它(很明显),如果你不能测试它,你怎么能说它有效呢?因此,如果您必须选择一个极端或另一个,我当然会选择可测试性。

但一个明显的问题是,你真的需要测试每一个私有方法吗?这些不是一个类的公共契约的一部分,孤立起来可能没有意义。公共方法对测试很重要,因为它们有特定的目的,它们必须履行这个非常特定的契约,并使对象保持一致的状态等等。它们本质上是可测试的,而私有方法可能不是。(谁在乎私有方法的作用?它不是类契约的一部分)

也许更好的解决方案是将一些原本私有的东西重构到单独的类中。也许测试私有方法的需求并不像您想象的那么大。

另一方面,其他模拟框架也允许你模拟私有的东西。

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

因此,虽然是的,您通过将私有方法公开来使它们可测试,但您也引入了单元测试无法轻易捕获的错误来源。您可以使用“错误”类。一个设计良好的类总是保持它的不变性,不管它如何被外部代码使用。一旦你把所有东西都公开了,那就不可能了。外部代码可以调用可能不会在该上下文中使用的内部辅助函数,这将使类进入无效状态。

并且单元测试并不能真正保证这不会发生。因此,我会说您可能会引入比您预期的更大的错误来源。

当然,鉴于上述私有成员的定义(那些不保留类不变量的成员),可能可以安全地将许多其他方法公开,因为它们确实保留了不变量,因此无需隐藏它们来自外部代码。因此,通过为您提供更少的私有方法,但不允许外部代码破坏您的类,这可能会减轻您的问题,如果一切都是公共的,这将是可能的。

于 2008-11-14T17:41:53.457 回答
14

我对 rhinomocks 不熟悉,实际上从未使用过或需要过模拟工具,所以我可能离这里很远,但是:

  • OO原则和TDD之间不应该有冲突,因为
  • 私有方法不需要进行单元测试,只有公共方法
于 2008-11-14T17:33:12.880 回答
9

您正在经历的是测试对设计施加了力量。这实际上就是为什么 TDD 主要是一种设计策略的原因 -如果您注意并知道如何阅读这些标志,那么编写测试会强制进行更好的解耦设计。

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

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

无论如何,在我看来,复杂的私有方法是一种代码味道——这表明该类可能承担了太多责任,违反了单一责任原则。想想什么样的类实际上不会违反封装来公开。将方法移动到该类(可能在途中创建它)可能会在耦合和内聚方面改进您的设计。

于 2008-11-14T20:57:58.240 回答
5

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

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

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

还有开闭原则。通过注入辅助类,您可以对行为进行多态替换。

如果担心在类上显示非私有方法,请通过其接口使用该类,这无论如何都是个好主意,对吧?

于 2008-11-14T18:21:36.137 回答
5

可维护性更重要。

不要错过单元测试和 OOP 的共同目标这一点。

于 2008-11-14T18:28:46.090 回答
3

OOP 是一个工具,而不是一个目标

于 2008-11-14T18:42:05.987 回答
1

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

我们现在正在重写一大堆单例以使用 DI/IOC,以便我们可以测试它们(即将在您附近的有线电视盒中使用)。

于 2008-11-14T17:35:16.760 回答
1

您还可以研究依赖注入作为外部化一些帮助类的一种方式

于 2008-11-14T17:36:23.933 回答
1

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

于 2008-11-14T17:40:26.190 回答
1

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

于 2008-11-14T17:44:58.893 回答
1

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

[assembly: InternalsVisibleTo("MyAssembly.Tests")]
于 2008-11-14T17:55:52.990 回答
1

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

于 2008-11-14T18:41:18.423 回答
0

作为 Intellisense 的普通用户,所有方法都公开的要求会让我发疯。我个人会在此基础上坚持 OOP。

于 2008-11-14T17:33:37.980 回答
0

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

于 2008-11-14T19:25:27.910 回答
0

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

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

于 2008-11-14T21:04:15.217 回答