私人/受保护的方法应该进行单元测试吗?

时间:2011-04-08 23:22:50

标签: unit-testing tdd

在TDD开发中,您通常首先要创建接口,然后开始针对该接口编写单元测试。当您逐步完成TDD过程时,您最终会创建一个实现该接口的类,然后在某些时候您的单元测试将通过。

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

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

  • 班级中受保护的方法是否有自己的单元测试?

我的想法:

  • 特别是因为我正在编写接口,我不应该担心受保护/私有方法,因为它们是黑盒子。

  • 因为我正在使用接口,所以我正在编写单元测试来验证所定义的合同是否由实现接口的不同类正确实现,所以我再也不担心私有/受保护的方法而应该运行它们通过调用接口定义的方法/属性的单元测试。

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

12 个答案:

答案 0 :(得分:98)

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

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

在更哲学的说明中,请记住,您正在测试行为,而不是方法。因此,如果您考虑被测试的类可以执行的事情集,只要您可以测试并断言该类的行为与预期一致,是否存在由类在内部使用的私有(和受保护)方法来实现这种行为无关紧要。这些方法是公共行为的实施细节。

答案 1 :(得分:35)

我不赞同大多数海报。

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

您的代码应该经过全面测试。如果您可以通过编写公共方法的测试来实现这一目标,那么就可以充分运用受保护/私有方法,这很棒。

如果你不能,那么要么重构,要么弯曲受保护/私人规则。

这是一个关于心理学家给孩子们考试的好故事。他给每个孩子两块木板,两端各有一根绳子,要求他们尽可能快地穿过房间,不要将脚碰到地板。所有的孩子都使用小板滑雪板,每块板子一只脚,用绳子抓住它们,然后滑过地板。然后他给了他们同样的任务,但只使用了一块板。他们转过身去"走了"在地板上,单板的每一端都有一只脚 - 而且它们更快![/ p>

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

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

背景也很重要。如果您正在编写供外部人员使用的API,则公共/私有更重要。如果它是一个内部项目 - 谁真正关心?

但是在一天结束时,请考虑缺少测试导致的错误数量。然后比较过多可见的"过度可见的错误"方法。那个答案应该会推动你的决定。

答案 2 :(得分:34)

您写道:

  

在TDD开发中,第一件事   你通常做的是创建你的   界面然后开始写你的   对该接口进行单元测试。如   您将逐步完成TDD流程   你最终会创建一个类   实现接口然后在   某些时候你的单元测试会通过。

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

  

在描述为什么一个类是有价值的以及它的行为方式时,通常是第一件事   做的是创建一个如何使用该类的示例,通常通过其接口*。随你添加   期望的行为,你最终创建一个提供该值的类,然后在一些   指出你的例子有效。

     

*可以是实际的Interface,也可以只是该类的可访问API,例如:Ruby   没有接口。

这就是为什么你不测试私有方法 - 因为测试是如何使用类的一个例子,你实际上不能使用它们。如果你愿意,你可以做的事情是将私有方法中的职责委托给协作类,然后模拟/存储帮助程序。

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

希望这有帮助!

答案 3 :(得分:13)

当您为类编写单元测试时,您不必关心类的功能是否直接在公共接口上的方法中实现,或者是否通过一系列私有方法实现。所以是的,您应该测试您的私有方法,但是您不需要直接从您的测试代码中调用它们(直接测试私有方法将您的实现紧密地耦合到您的测试并且不必要地进行重构)。

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

答案 4 :(得分:12)

没有!只测试接口。

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

答案 5 :(得分:8)

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

将方法标记为受保护而非私有意味着它应该被第三方代码使用,因此需要定义和测试某种契约,就像公共方法定义的普通接口一样,这些接口都是继承和组成。

答案 6 :(得分:5)

编写测试有两个原因:

  1. 断言预期的行为
  2. 防止行为回归
  3. 接受(1)断言预期行为:

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

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

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

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

    对于这种测试,您不关心可见性,接口或其中任何一种,您只关心工作代码。所以是的,如果您认为需要对您进行测试以回答问题的答案,那么您将测试私有和受保护的方法。

    采取行动(2)防止行为回归:

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

    大多数语言都存在机制,以防止这种损害。可见性功能是一种机制。私有方法被隔离并隐藏。封装是另一种机制,您可以在其中划分内容,以便更改其他隔离专区不会影响其他隔离专区。

    这种方法的一般机制称为:编码到边界。通过在代码的各个部分之间创建边界,可以保护边界内的所有内容不受其外部内容的影响。边界成为交互点,以及事物相互作用的契约。

    这意味着通过破坏界面或破坏其预期行为来改变边界会损坏并可能破坏依赖它的其他边界。这就是为什么进行单元测试是一个好主意,它针对这些边界并断言它们不会在语义和行为上发生变化。

    这是您典型的单元测试,是大家提及TDD或BDD时最常谈的。重点是加强边界并保护它们免受变化。您不希望为此测试私有方法,因为私有方法不是边界。受保护的方法是限制边界,我会保护它们。它们不会暴露于世界,但仍然暴露于其他隔间或“单位”。

    该怎么做?

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

    是和否。

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

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

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

答案 7 :(得分:4)

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

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

答案 8 :(得分:2)

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

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

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

希望它有所帮助!

答案 9 :(得分:1)

我同意其他人的意见:你的问题的答案是'不'。

事实上,您对自己的方法和想法完全正确,尤其是代码覆盖率。

我还要补充一点,问题(和答案'不')也适用于您可能会引入课程的公共方法。

  • 如果您添加方法(公共/受保护或私有),因为它们通过了失败的测试,那么您或多或少都达到了TDD的目标。
  • 如果您添加方法(公共/受保护或私有),因为您只是决定违反TDD,那么您的代码覆盖应该抓住这些并且您应该能够改进您的流程。

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

答案 10 :(得分:1)

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

受保护是一种不同的讨论点,但总的来说,它根本不应该存在。要么它破坏了已部署代码的封装,要么它会强制你从该类继承,只是为了对它进行单元测试,即使有时你也不需要继承。

将方法隐藏到客户端(私有)并不会使其具有不被审计的权限。因此,它们可以通过前面提到的公共方法进行测试。

答案 11 :(得分:0)

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

我认为,一旦我们拥有可识别单元,无论是否通过公共API公开,都将受益于单元测试。