当遵循测试驱动开发范式时,我是否应该永远不使用静态方法、类和单例?

18

我读到过一些关于在项目中实现单元测试时,静态方法,静态类和单例模式是有问题的。当遵循TDD范例时,我应该忘记它们并且永远不再使用它们吗?还是有时可以使用它们?


2
重复的问题?https://dev59.com/nG865IYBdhLWcg3wIa7M - goenning
@Oenning,这个也包括单例模式。 - wheaties
4个回答

10

永远不要说永远——静态类和静态方法在您的工具箱中有其用处。

然而,如果您正在尝试隔离和测试的类(测试主题或SUT)依赖于静态类或方法,则无法编写可以将SUT与静态依赖项隔离的测试——当您的测试代码运行时,它仍将使用静态调用。有时这是可以接受的,但有时您想创建一个隔离的测试,仅测试您的SUT逻辑而没有依赖关系(通常通过模拟或类似技术)。

总体上,我个人相对节俭地使用静态类和方法。

由于单例的实现方式,它们对于隔离SUT进行单元测试也存在类似的问题。此外,GOF单例模式的概念被认为是某些软件开发人员的一种不良实践。我碰巧同意这种观点,但在这个问题上几乎没有共识。在谷歌上快速搜索可能会让你对GOF单例模式的优缺点有一个相当好的了解。


好的,作为经验法则,可以说如果你不认为Static 类或Singleton 是反模式,那么当你需要一个不需要保存状态信息(或者至少只保存非常少的状态信息,并且用适当的锁定)的单个实例时,它们可以被使用。例如,工厂类/辅助类/实用程序类是实现为静态类的好候选对象? - Mr Grok
总的来说,我同意这个陈述。关于测试,如果实用程序/工厂/助手不会干扰创建孤立的测试用例,那么它是可以的(有许多情况会干扰)。我之前问过一个有些相关的问题:https://dev59.com/_HM_5IYBdhLWcg3wn0rT - Phil Sandler
克隆单例并通过依赖注入将其传递到测试中怎么样?甚至可以使用模拟数据进行初始化。这将解决副作用问题。 - Evan Plaice
你能举个例子说明什么情况下无法将SUT与静态部分隔离开是可以接受的吗?你如何做出这样的判断? - Didier A.
当然。基本上任何核心实用程序或计算方法都可以。以可能最简单的例子为例,想象一个静态类/方法,它将两个数字相加。 - Phil Sandler

3
  1. 你应该将它们完全忘记吗?不应该。
  2. 你应该确保它们被透明地并入你的代码中,以使它们不影响类的功能吗?是的。

为了进一步解释上述内容,与其在代码中尝试从单例中检索值,不如在构造函数参数中初始化该值。如果构造函数变得过于庞大,可以创建一个工厂方法进行创建,这样可以在不使用单例的情况下测试类。如果这种方法有问题或者您的单例具有可变状态(我会感到惊恐,但是那是我的个人意见),那么请尽量让您的单例易于集成到您的测试中。

您不希望为了测试一个计算股票报价块的标准差的类中的方法而创建整个配置文件。那太麻烦了。但是,如果您的单例被编写成可以填充数据,并且另一个类负责读取配置文件并填充该数据,则您已经取得了巨大的进展。

关于静态方法,我认为它们是最容易测试的方法,前提是您不需要担心全局状态。静态方法相当于y = f(x),虽然看起来很简单,但这表明对于给定的x,您将始终得到相同的y,而且没有局部状态转换能够改变这一不变量。


静态方法易于测试,但使用静态方法的方法难以进行隔离测试。实际上,您将始终将静态方法的逻辑视为调用静态方法的方法单元的一部分。 - Didier A.

2

与任何软件工程实践一样,没有一种明确的解决方案适用于所有情况。因此,您不应排除静态方法、静态类和单例模式。TDD的目的是使您的生活更轻松,您会发现随着使用TDD的次数增多,您的代码将是模块化的,这意味着即使您使用静态方法(...等等),您的代码仍然可以进行测试。

我喜欢遵守的规则是:只要您的代码易于阅读且设计优雅,您就可以使用任何东西。如何实现这两点取决于您。如果您仍然能够交付优雅的代码,那么您甚至可以使用GOTO语句。


-3
我曾经读到过静态方法在实现单元测试时会出现问题,被认为是有害的。
但我个人并没有这样的看法。你能给出参考资料吗?我认为静态方法(函数)可以被使用和单元测试,并且不会出现问题。

编辑

谢谢提到静态方法对可测试性的杀伤力的参考文献: 那篇文章真是一派胡言。

静态方法的基本问题在于它们是过程化代码。我不知道如何单元测试过程化代码。

这表明作者对单元测试了解有限。当然你可以对过程化代码进行单元测试。

在实例化过程中,我使用模拟/友好对象来替换真实依赖项进行依赖注入。

这是一个关键的错误观点:认为单元测试需要使用模拟对象。事实并非如此。无论如何,你可以将模拟对象作为参数传递给正在测试的静态方法:依赖注入并不需要构造函数。更多信息,请参阅问题“静态方法:何时使用,何时不使用”的被接受答案

如果静态方法调用另一个静态方法,就无法覆盖被调用方法的依赖项。

虽然正确但不相关。如果静态方法A调用静态方法B,那就是方法A实现细节。所以你没有理由试图拦截对B的调用。只需将A视为一个单元。

假设您的应用程序除了静态方法之外什么都没有

这是一个草人论点。显然,在现代单元测试的背景下,我们正在谈论仅具有一些静态方法的OO程序。


我遇到了很多,但以下是一些例子:http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/http://www.thoughtshapes.com/Blog/?p=13 - Mr Grok
这个回答https://dev59.com/xXRB5IYBdhLWcg3wAjJH#752805是相关的好答案。 - Raedwald
2
顺便提一下,这里所说的“作者”是谷歌自己的测试专家,Angular.js的创造者,他之前曾在Adobe、Sun、Intel和Xerox工作过。所以,不是要不尊重你,但很可能他比你更了解单元测试。 - Didier A.
我不会忽略类/方法/其他的依赖关系并说它只是实现细节。你想进行单元测试,而不是集成测试。你使用黑盒测试,却不知道你的依赖项是否无缺陷。在你的示例中,你总是使用A来测试B。因此,如果忽略依赖关系的实现细节,则无法对A进行单元测试。 - Binarian
这并没有回答问题,只是批评了一篇回答问题的博客文章。 - Omnimike
这个答案混淆了过程式代码和纯函数(如FP)的概念。 - Siva Sankaran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接