私有/受保护的方法是不是应该进行单元测试? [关闭]

Posted

技术标签:

【中文标题】私有/受保护的方法是不是应该进行单元测试? [关闭]【英文标题】:Should Private/Protected methods be under unit test? [closed]私有/受保护的方法是否应该进行单元测试? [关闭] 【发布时间】:2011-08-01 21:13:51 【问题描述】:

在 TDD 开发中,您通常做的第一件事是创建接口,然后开始针对该接口编写单元测试。随着 TDD 流程的推进,您最终会创建一个实现接口的类,然后在某个时候您的单元测试就会通过。

现在我的问题是关于我可能必须在我的类中编写的私有和受保护方法以支持接口公开的方法/属性:

类中的私有方法是否应该有自己的单元测试?

类中的受保护方法是否应该有自己的单元测试?

我的想法:

尤其是因为我正在对接口进行编码,所以我不应该担心受保护/私有方法,因为它们是黑盒子。

1234563通过调用接口定义的方法/属性的单元测试。

如果我的代码覆盖率没有显示受保护/私有方法受到攻击,那么我没有正确的单元测试,或者我的代码没有被使用,应该被删除。

【问题讨论】:

如果您不从测试中使用受保护的方法,无论是通过覆盖它们还是通过调用它们,为什么它们是受保护的,而不是私有的?通过使它们受到保护,您正在有意识地决定公开扩展点/功能。对我来说,如果你遵循 TDD,这个决定应该由你正在编写的测试驱动。 你应该把关于你自己想法的部分放在一个单独的答案中。当你这样做时让我知道,我会投票。 同样适用于私人:***.com/questions/105007/… 您对活动单元测试是正确的,即那些设置为连续运行的单元测试。对于这些,您只希望测试公共和受保护的接口。您可以也可以从为私有方法编写测试中受益。这些测试不应该成为您的连续套件的一部分,但作为验证您的实施是否良好的一次性工具,它可能是一个非常有价值的工具。 How do I test a class that has private methods, fields or inner classes?的可能重复 【参考方案1】:

不,我不想测试私有或受保护的方法。类的私有和受保护方法不是公共接口的一部分,因此它们不会暴露公共行为。通常,这些方法是由您在测试变为绿色后应用的重构创建的。

因此,这些私有方法由断言公共接口行为的测试隐式进行测试。

从更哲学的角度来说,请记住您是在测试行为,而不是方法。因此,如果您想到被测类可以做的一组事情,只要您可以测试并断言该类的行为符合预期,是否存在该类内部使用的私有(和受保护)方法来实现这种行为是无关紧要的。这些方法是公共行为的实现细节。

【讨论】:

我喜欢你所说的单元测试,测试行为而不是方法!这澄清了很多事情。 我同意@rajah。这应该是每个教程的第一个声明。我一直想知道如何测试我的方法,现在我知道我不需要。 +1 你认为这仍然适用于基类实现受保护的行为,公众期望继承和使用的情况吗?那么受保护的方法仍然是公共接口的一部分,不是吗? 一般来说,支持关注点分离的模式更适合独立的单元测试,而支持封装的模式更易于使用 API。 这并不能解决受保护可见性的情况。似乎受保护的方法也是接口的一部分,通常,它是一个扩展点,故意将其保护为这样。我会说在这些情况下,您也应该对它们进行单元测试。您不希望任何人在未来改变事物并破坏依赖于这些扩展点行为的类。【参考方案2】:

不,你不应该测试私有方法(如果不使用像反射这样可怕的东西,你会怎么做)。使用受保护的方法,在 C# 中稍微不那么明显,您可以使事物在内部受到保护,我认为可以这样做来测试通过模板模式方法实现其所有功能的派生类。

但是,一般来说,如果您认为您的公共方法做得太多,那么是时候将您的类重构为更原子的类,然后测试这些类。

【讨论】:

【参考方案3】:

不!只测试接口。

TDD 的一大好处是确保无论您选择如何实现私有方法,接口都能正常工作。

【讨论】:

【参考方案4】:

当你为你的类编写单元测试时,你不必关心类的功能是直接在公共接口的方法中实现还是在一系列私有方法中实现.所以是的,您应该测试您的私有方法,但您不需要直接从测试代码中调用它们(直接测试私有方法将您的实现与测试紧密耦合,并使重构变得不必要地困难)。

受保护的方法在您的类与其未来的子类之间形成不同的契约,因此您确实应该以与公共接口类似的程度对其进行测试,以确保契约得到良好定义和执行。

【讨论】:

【参考方案5】:

我同意其他所有人的观点:你的问题的答案是“不”。

确实,您的方法和想法是完全正确的,尤其是在代码覆盖率方面。

我还要补充一点,这个问题(以及“否”的答案)也适用于您可能引入类的公共方法。

如果您添加方法(公共/受保护或私有)是因为它们未通过测试通过,那么您或多或少地实现了 TDD 的目标。 如果您只是决定添加方法(公共/受保护或私有),违反了 TDD,那么您的代码覆盖率应该会捕获这些,并且您应该能够改进您的流程。

另外,对于 C++(我应该只考虑 C++),我只使用私有方法实现接口,以表明该类只能通过它实现的接口使用。它阻止了我错误地调用从我的测试中添加到我的实现中的新方法

【讨论】:

【参考方案6】:

你写道:

在 TDD 开发中,第一件事 你通常做的是创建你的 界面,然后开始编写你的 针对该接口的单元测试。作为 您在 TDD 过程中取得进展 你最终会创建一个类 实现接口,然后在 你的单元测试会在某个时候通过。

请让我用BDD 语言改写一下:

在描述一个类为何有价值以及它的行为方式时,您通常首先要做的就是 要做的是创建一个如何使用该类的示例,通常通过其接口*。当你添加 您最终会创建一个提供该值的类,然后在某些 指出您的示例有效。

*可能是一个实际的Interface 或者只是类的可访问API,例如:Ruby 没有接口。

这就是你不测试私有方法的原因——因为测试是如何使用类的一个例子,你实际上不能使用它们。如果您愿意,您可以将私有方法中的职责委托给协作类,然后模拟/存根该助手。

使用受保护的方法,您是说扩展您的类的类应该具有某些特定行为并提供一些价值。然后,您可以使用类的扩展来演示该行为。例如,如果您正在编写一个有序集合类,您可能想证明两个具有相同内容的扩展证明了相等性。

希望这会有所帮助!

【讨论】:

精彩的帖子。澄清了很多。【参考方案7】:

补充上面其他人所说的,我想说受保护的方法是某种接口的一部分:它恰好是暴露于继承而不是组合的方法,这是每个人在考虑接口时倾向于考虑的。

将方法标记为受保护而不是私有意味着它预计会被第三方代码使用,因此需要定义和测试某种契约,就像由公共方法定义的普通接口一样,公共方法对两者都是开放的继承和组合。

【讨论】:

【参考方案8】:

我不同意大多数海报。

最重要的规则是:工作代码胜过关于公共/受保护/私有的理论规则。

您的代码应该经过彻底测试。如果您可以通过为公共方法编写测试来达到目标​​,从而充分利用受保护/私有方法,那就太好了。

如果你不能,那么要么重构以便你可以,要么改变受保护/私有规则。

有一个很棒的故事,讲的是一位心理学家给孩子们做测试。他给每个孩子两块木板,两端都系着绳子,并要求他们尽可能快地穿过一个不让脚接触地板的房间。所有的孩子都像小滑雪板一样使用这些板子,每块板子上一只脚,用绳子抓住它们,然后滑过地板。然后他给了他们同样的任务,但只用了一块板子。他们在地板上旋转/“行走”,一只脚踩在单板的每一端——而且他们更快!

仅仅因为 Java(或任何语言)具有特性(私有/受保护/公共)并不一定意味着您正在编写更好的代码,因为您使用它!

现在,总会有优化/最小化这种冲突的方法。在大多数语言中,您可以使方法受保护(而不是公共),并将测试类放在同一个包中(或其他),该方法将可用于测试。正如其他海报所描述的,有一些注释可以提供帮助。您可以使用反射来获取私有方法(糟糕)。

上下文也很重要。如果您正在编写供外部人员使用的 API,则公共/私有更为重要。如果这是一个内部项目——谁真正在乎?

但归根结底,想想有多少错误是由于缺乏测试造成的。然后比较有多少错误是由“过度可见”的方法引起的。这个答案应该会推动你的决定。

【讨论】:

如果一个方法很关键,并且具有复杂的逻辑,那么断言它的行为对于防止错误非常有用。为这种方法编写单元测试甚至可以帮助您以一种探索性的方式实现该方法。因此,即使它是私有的,我也会说它值得进行单元测试。但是,有一个很大的但是,你必须记住测试是代码耦合。如果您将测试写入一个方法,您将阻止重构。 所以在你开始为私有方法编写测试之前,我想说总是重新考虑你的设计。看看事物是否可以泛化并转化为纯函数方法。如果是这样,您可以将它们提取到自己的构造中。然后,此构造可以拥有自己的公共接口并进行单元测试。请记住,通常情况下,私有方法中的复杂行为可能表明一个类的职责不止一个。所以,请先重新考虑你的设计。 是的,但什么是“工作代码”?测试私有方法并不能说明您的对象是否具有正确的行为。这就是为什么我们只测试公共方法的要点。只有公共方法表现出一段代码的用户关心的行为。 “工作代码”是有效的代码。如果您的私有(或准私有)方法中存在错误,而您的公共方法的测试没有发现该错误,那么就有问题了。也许你的设计是错误的,很公平:我同意最好的解决方案是调用公共方法的测试。但这并不总是可能的,尤其是在您添加或修复遗留代码时。 (我从经验说起,在一个有 100 万行代码的项目上。)经过测试的代码总是比未经测试的代码好。即使我们打破了只测试公共方法的好规则! 关于“测试是代码耦合......防止重构”的位(在顶部)是 100% 错误的。在架构比喻中,测试是脚手架,而不是具体的。事情改变了,测试改变了,被扔掉了,新的测试被写出来了。我同意好的设计可以最大限度地减少测试重写。但变化总会发生,即使是最好的设计。【参考方案9】:

我也同意@kwbeam 关​​于不测试私有方法的回答。但是,我想强调的重要一点 - 受保护的方法是类导出 API 的一部分,因此必须进行测试。

受保护的方法可能无法公开访问,但您肯定为子类提供了一种使用/覆盖它们的方法。类之外的东西可以访问它们,因此您需要确保这些受保护的成员以预期的方式运行。所以不要测试私有方法,而是测试公共和受保护的方法。

如果您认为您有一个包含关键逻辑的私有方法,我会尝试将其提取到一个单独的对象中,将其隔离并提供一种测试其行为的方法。

希望对你有帮助!

【讨论】:

【参考方案10】:

编写测试有两个原因:

    断言预期行为 防止行为回归

(1)断言预期行为:

当您断言预期的行为时,您希望确保代码按您认为的那样工作。这实际上是一种自动执行日常手动验证的方法,任何开发人员在实现任何类型的代码时都会执行:

我刚才写的有用吗? 这个循环真的结束了吗? 它是否按照我认为的顺序循环? 这是否适用于空输入?

这些都是我们在头脑中回答的问题,通常,我们也会尝试在头脑中执行代码,确保它看起来确实有效。对于这些情况,让计算机以明确的方式回答它们通常很有用。所以我们编写了一个单元测试来断言它确实如此。这让我们对自己的代码充满信心,帮助我们及早发现缺陷,甚至可以帮助实际实现代码。

在您认为有必要的地方执行此操作是个好主意。任何难以理解或不重要的代码。即使是微不足道的代码也可以从中受益。这完全取决于你自己的信心。多久做一次,走多远,取决于你自己的满意度。当您可以自信地回答“是”时停止:您确定这有效吗?

对于这种测试,您不需要关心可见性、接口或任何其他内容,您只关心是否有工作代码。因此,是的,如果您认为需要测试私有和受保护的方法以对问题回答“是”,则可以测试它们。

(2)防止行为回归:

一旦您有了工作代码,您就需要建立一种机制来保护此代码免受未来损坏。如果没有人再接触你的源代码和配置,你就不需要这个了,但在大多数情况下,你或其他人会接触软件的源代码和配置。这种内部摆弄很可能会破坏您的工作代码。

大多数语言中已经存在机制来防止这种损害。可见性特征是一种机制。私有方法是隔离和隐藏的。封装是另一种机制,您可以在其中划分事物,以便更改其他部分不会影响其他部分。

对此的一般机制称为:编码到边界。通过在代码的各个部分之间创建边界,您可以保护边界内的所有内容免受边界外的事物的影响。边界成为交互点,以及事物交互的契约。

这意味着对边界的更改,无论是通过破坏其接口还是破坏其预期行为,都会损坏并可能破坏依赖它的其他边界。这就是为什么进行单元测试是个好主意,它针对这些边界并断言它们在语义和行为上不会改变。

这是典型的单元测试,是每个人在提到 TDD 或 BDD 时谈论最多的单元测试。关键是要加强边界并保护它们免受变化。您不想为此测试私有方法,因为私有方法不是边界。受保护的方法是一个受限制的边界,我会保护它们。它们不暴露于世界,但仍暴露于其他隔间或“单元”。

这是怎么回事?

正如我们所见,有充分的理由对公共和受保护方法进行单元测试,以断言我们的接口不会改变。并且有充分的理由测试私有方法,以断言我们的实现有效。那么我们应该对它们都进行单元测试吗?

是和否。

首先:测试您认为需要明确证明它在大多数情况下有效的所有方法,以便能够确信您的代码有效,无论可见性如何。然后,禁用这些测试。他们已经完成了那里的工作。

最后:为你的界限编写测试。对系统的其他单元将使用的每个点进行单元测试。确保此测试断言语义契约、方法名称、参数数量等。并确保测试断言单元的可用行为。您的测试应该演示如何使用该单元,以及该单元可以做什么。保持这些测试处于启用状态,以便它们在每次代码推送时运行。

注意:您禁用第一组测试的原因是允许重构工作发生。主动测试是代码耦合。它可以防止将来修改它正在测试的代码。您只希望为您的接口和交互合同提供此功能。

【讨论】:

你听起来好像你没有明确地单独测试私有方法,它们没有被你的测试覆盖,你不能相信它们可以工作。我声称这是完全错误的。无法通过公共方法测试的私有方法(或其中的任何代码路径)是死代码,应该删除。 TDD 的全部意义在于仅通过测试公共方法来获得全面覆盖,因为您编写了 0 个不存在的 LoC 来使测试通过。测试一个孤立的私有方法只会使重构更加困难,这与 TDD 的(一个)目标完全相反。 @kai 我明确指出你不应该对私有方法进行自动化测试,但有时拥有独立的测试来帮助你实现是很有价值的。这些测试不应该是你的测试套件的一部分,或者应该因为你提到的原因被禁用:重构。何时更喜欢通过程序测试来实现私有方法取决于您自己的信心水平。也许您直到我的回答结束才阅读? 您声称“也有充分的理由测试私有方法,以断言我们的实现有效”。我在帖子中认为没有任何依据。私有方法的测试无法告诉您有关工作实现的任何信息,而公共方法的测试也无法告诉您。私有方法要么有效,要么无效。如果它不起作用,它将使一个或多个公共方法的测试失败,或者它是死代码和/或未经测试的代码。 @kai 你提到:“或者它是死的和/或未经测试的代码”。未经测试的代码就是我所说的。私有方法可以隐藏许多在公共方法中没有执行边缘情况的错误。想象一下一个错误。有时,公共方法的不变量使得这种情况永远不会发生。在这种情况下,我认为私有方法仍然存在错误并且实现有缺陷,但是,它的集成可以防止发现和捕获错误。在这种情况下,您可能需要进行一些测试来尝试边缘情况,以确保您的方法没有错误。 @kai 您可以删除代码并非总是如此。我的一个错误就是一个很好的例子。这是处理所有情况的相同代码,但只有边缘情况会揭示其实现中的缺陷。间接测试这些边缘情况并不总是很明显,因为您需要了解其他函数的哪些输入会导致私有函数的边缘输入。有时,这是不可能的,但随后有人进来并改变了类的行为,假设私有方法可以工作,突然间,边缘情况成为可能,并且一个错误突然出现。【参考方案11】:

如果您的目标是高代码覆盖率(我建议您应该这样做),您应该测试所有方法,无论它们是私有的还是受保护的。

Protected 是一种不同的讨论点,但总而言之,它根本不应该存在。它要么破坏已部署代码的封装,要么强制您从该类继承,只是为了对其进行单元测试,即使有时您不需要继承。

仅仅向客户端隐藏一个方法(私有化)并不赋予它不受审计的特权。因此,它们可以通过前面提到的公共方法进行测试。

【讨论】:

【参考方案12】:

一个好的设计意味着将应用程序拆分为多个可测试的单元。完成此操作后,一些单元会暴露给公共 API,但其他一些可能不会。此外,公开单元和这些“内部”单元之间的交互点也不属于公共 API。

我认为一旦我们有了可识别的单元,无论是否通过公共 API 公开,都会从单元测试中受益。

【讨论】:

【参考方案13】:

简单的答案 - 否。

说明: 为什么要测试私有函数?当您测试正在使用它的功能/方法 - 私有函数时,无论如何都会自动对其进行测试(并且必须进行测试)。

【讨论】:

以上是关于私有/受保护的方法是不是应该进行单元测试? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如何通过phpunit对一个方法进行单元测试,该方法具有多个内部调用保护/私有方法?

是否可以为具有 1)返回类型 void、2)访问说明符私有或受保护的方法编写单元测试?

用于私有/受保护方法的 JavaDoc? [关闭]

受保护插槽的单元测试[关闭]

C#:在密封类中模拟和测试受保护(或私有)方法——方法

如何在 C++ 中对受保护的方法进行单元测试?