如何避免需要对私有方法进行单元测试


15

我知道您不应该测试私有方法,并且如果您看起来需要测试私有方法,那么那里可能会有一个类在等待发布。

但是,我不想拥有大量的类来测试它们的公共接口,我发现对于许多类来说,如果仅测试公共方法,最终我就不得不模拟很多依赖,而单元测试就是巨大且难以遵循。

我更喜欢在测试公共方法时嘲笑私有方法,而在测试私有方法时嘲笑外部依赖关系。

我疯了吗?


全面的单元测试应隐式涵盖给定类的所有私有成员,因为尽管您不能直接调用它们,但它们的行为仍会影响输出。如果他们不这样做,那么为什么他们首先出现在这里?请记住,在单元测试中,您关心的是结果,而不是结果的得出方式。
GordonM '18

Answers:


24

您是部分正确的人-您不应该直接测试私有方法。类上的私有方法应该由一个或多个公共方法调用(也许是间接的-由公共方法调用的私有方法可以调用其他私有方法)。因此,在测试公共方法时,您还将测试私有方法。如果您有未测试的私有方法,则测试用例不足或私有方法未使用且可以删除。

如果您采用白盒测试方法,则在围绕公共方法构建单元测试时,应考虑私有方法的实现细节。如果您采用的是黑盒方法,则不应针对公共或私有方法中的任何实现细节进行测试,而应针对预期的行为进行测试。

就个人而言,我更喜欢在单元测试中使用白盒方法。我可以进行测试,以将要测试的方法和类置于不同的状态,这些状态会在我的公共和私有方法中引起有趣的行为,然后断言结果是我所期望的。

所以-不要嘲笑您的私有方法。使用它们来了解您需要测试的内容,以便更好地覆盖所提供的功能。在单元测试级别上尤其如此。


1
“不要嘲笑您的私有方法”,是的,我可以理解测试,当您嘲笑它们时,您可能已经跨越了罚款线,变得疯狂了
Ewan

是的,但是就像我说的那样,白盒测试的问题在于,通常模拟公共和私有方法的所有依赖关系会导致真正的大型测试方法。任何想法如何解决?
弗兰·塞维利亚诺

8
@FranSevillano如果您必须进行大量存根或模拟,那么我来看看您的总体设计。感觉有些不对劲。
Thomas Owens

一个代码覆盖率工具可以帮助您。
安迪

5
@FranSevillano:一个类做很多事情都需要大量依赖的理由并不多。如果您有大量的依赖关系,那么您可能有一个上帝课。
Mooing Duck,

4

我认为这是一个非常糟糕的主意。

创建私有成员的单元测试的问题在于,它与产品生命周期的适应性很差。

您选择将这些方法设为私有的原因是,它们对于您的类试图做的事情并不重要-只是您当前如何实现该功能的帮助者。重构时,这些私有细节很可能会更改,并且在重构时会引起摩擦。

同样,公共成员和私有成员之间的主要区别是,您应该仔细考虑公共API,并对其进行良好的文档编制和检查(断言等)。但是,使用私有API时,仔细考虑是毫无意义的(浪费了精力,因为它的使用是如此本地化)。将私有方法放入单元测试中等于在这些方法上创建了外部依赖关系。意味着API需要稳定且有据可查(因为有人必须弄清楚为什么/何时使这些单元测试失败)。

我建议你:

  • 重新思考那些方法是否应该私有
  • 编写一次性测试(只是为了确保它现在正确,暂时将其公开,以便您可以进行测试,然后删除该测试)
  • 使用#ifdef和断言将条件测试内置到实现中(测试仅在调试版本中完成)。

感谢您倾向于测试此功能,并且很难通过当前的公共API对其进行测试。但是我个人比测试范围更重视模块化。


6
我不同意。IMO,对于集成测试,这是100%正确的。但是对于单元测试,情况有所不同。单元测试的目标是查明错误的位置,并缩小到可以迅速修复的范围。我经常遇到这种情况:我几乎没有公共方法,因为那确实是我班级的核心价值(正如您所说的那样)。但是,我也避免编写400行方法,无论是公共方法还是私有方法。因此,我的几种公共方法只能在数十种私有方法的帮助下实现其目标。太多的代码无法“快速修复”。我要开始调试等
marstato

6
@marstato:建议:首先开始编写测试,并改变您对单元测试的看法:它们没有发现错误,但请验证代码是否按开发人员的预期工作。
Timothy Truckle '18

@marstato谢谢!是。当您签入东西时(否则您将不会签入!),测试始终会在第一次通过。当您开发代码时,它们很有用,当您破坏某些内容时会给您一个提示,如果您进行了良好的回归测试,那么它们会为您提供在哪里寻找问题的舒适/指导(而不是在稳定的地方) ,并进行了良好的回归测试)。
刘易斯·普林格

@marstato“单元测试的目标是查明错误的位置”-这正是导致OP问题的误解。单元测试的目的是验证API的预期(并最好记录在案)的行为。
StackOverthrow

4
@marstato名称“集成测试”来自于多个组件可以协同工作(即它们正确集成)的测试。单元测试是测试单个组件是否可以独立完成其预期的工作,这基本上意味着其公共API可以按文档/要求的方式工作。无论这些术语说,关于是否包括内部实现逻辑的测试,任何一部分的保证整合工作,或单件作品。

3

UnitTests测试 公共可观察到的行为,而不是代码,其中“ public”表示:返回值和与依赖项的通信。

“单位”是解决相同问题(或更确切地说:具有相同变更理由)的任何代码。那可能是单个方法或一堆类。

您不想测试的主要原因private methods是:它们是实现细节,您可能希望在重构期间进行更改(通过应用OO原理来改进代码,而无需更改功能)。正是在这种情况下,您不希望更改单元测试,从而可以保证重构期间CuT 的行为不变。

但是,我不想拥有大量的类来测试它们的公共接口,我发现对于许多类来说,如果仅测试公共方法,最终我就不得不模拟很多依赖,而单元测试就是巨大且难以遵循。

我通常会遇到相反的情况:类越小(它们承担的责任越少),它们具有的依赖性越少,并且单元测试的编写和读取就越容易。

理想情况下,您将相同级别的抽象模式应用于类。这意味着您的类要么提供一些业务逻辑(最好是“纯函数”,仅对它们的参数起作用而不保持其自身的状态)(x),要么对其他对象调用方法,而不能同时对两者进行调用。

通过这种方式对业务行为进行单元测试是轻而易举的事,并且“委托”对象通常太简单而无法失败(无分支,无状态更改),因此无需进行单元测试,并且可以将其测试留给集成模块测试进行。


1

当您是唯一从事代码工作的程序员时,单元测试会有所价值,尤其是在应用程序非常大或非常复杂的情况下。当您拥有大量使用同一代码库的程序员时,单元测试就变得至关重要。引入了单元测试的概念来解决在这些较大的团队中工作的一些困难。

单元测试帮助大型团队的原因全在于合同。如果我的代码正在调用其他人编写的代码,那么我就假设其他人的代码在各种情况下将要做什么。如果这些假设仍然成立,我的代码仍然可以使用,但是我如何知道哪些假设有效,以及如何知道这些假设何时更改?

这就是单元测试的用处。类的作者创建单元测试以记录其类的预期行为。单元测试定义了使用类的所有有效方式,并且运行单元测试可以验证这些用例是否按预期工作。另一个想要利用您的类的程序员可以阅读您的单元测试,以了解他们对类的期望行为,并将其用作他们对类工作方式的假设的基础。

这样,类和单元测试的公共方法签名共同构成了类作者和其他在其代码中使用该类的程序员之间的契约。

在这种情况下,如果包括私有方法的测试会怎样?显然,这没有任何意义。

如果您是唯一处理代码的程序员,并且希望将单元测试用作调试代码的方式,那么我认为这没有什么害处,它只是一个工具,您可以将其用于任何可用于您,但这不是引入单元测试的原因,并且不能提供单元测试的主要好处。


我为此苦苦挣扎,因为我模拟了依赖项,以便如果任何依赖项发生变化,我的测试都不会失败。是否应该在集成测试中而不是单元测试中发生此故障?
托德

该模拟应该具有与实际实现相同的行为,但没有依赖性。如果实际的实现发生变化,以至于现在它们有所不同,则这将显示为集成测试失败。我个人认为模拟的开发和实际实现是一项任务。我不会在编写单元测试时构建模拟程序。这样,当我更改类的行为并更改模拟以使其匹配时,运行单元测试将识别出此更改所破坏的所有其他类。
bikeman868

1

在回答这样的问题之前,您需要确定您实际想要达到的目标。

您编写代码。您希望它履行合同(换句话说,它可以完成应做的事情。写下它应做的事情对某些人来说是巨大的进步)。

要合理地确信代码可以完成预期的工作,您可以盯着它足够长时间,或者编写测试代码以测试足够的情况,以说服您“如果代码通过所有这些测试,那么它是正确的”。

通常,您只对某些代码的公共定义接口感兴趣。如果我使用您的库,则不在乎您是如何使其正常工作的,仅是它能够正常工作。我通过执行单元测试来验证您的库是否正确。

但是您正在创建库。使它正常工作可能很难实现。假设我只关心该库是否正确执行X,所以我对X进行了单元测试。您,负责创建该库的开发人员,通过将步骤A,B和C组合在一起来实现X,这两个步骤都不是很简单的。为了使您的库正常工作,您需要添加测试以验证A,B和C均正常工作。您需要这些测试。说“您不应该对私有方法进行单元测试”是毫无意义的。需要测试这些私有方法。也许有人告诉您单元测试私有方法是错误的。但这仅意味着您可能不将它们称为“单元测试”,而将其称为“私有测试”或您喜欢称呼它们的任何东西。

Swift语言解决了您不想将A,B,C公开为公共方法的问题,因为您想通过给函数赋予属性“可测试”来对其进行测试。编译器允许从单元测试而不是非测试代码中调用私有可测试方法。


0

是的,你疯了。...就像狐狸!

有两种测试私有方法的方法,其中一些取决于语言。

  • 反射!违反规则!
  • 使它们受到保护,继承和覆盖
  • 朋友/ InternalsVisibleTo类

总的来说,如果您要测试私有方法,则可能希望将它们移至依赖项的公共方法并对其进行测试/注入。


我不知道,在我眼中,您解释说这不是一个好主意,但仍在回答问题
Liath

我以为我已经涵盖了所有基地:(
伊万

3
我赞成,因为将您的私有帮助器方法暴露给公共API只是为了测试它们的想法很疯狂,我一直认为。
罗伯特·哈维

0

保持务实。通过设置实例的状态和公共方法的参数以使这些情况在那里发生而在私有方法中测试特殊情况通常过于复杂。

为了测试那些“私有”方法,我添加了一个额外的internal访问器(与InternalsVisibleTo测试程序集的标志一起使用),命名明确DoSomethingForTesting(parameters)

当然,实现有时可能会发生变化,包括测试访问器在内的那些测试已过时。这仍然比未测试的案例或不可读的测试要好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.