关于tdd:私有/受保护的方法应该进行单元测试吗?

Should Private/Protected methods be under unit test?

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

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

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

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

我的想法:

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

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

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


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

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

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


我不赞同大多数海报。

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

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

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

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

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

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

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

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


你写了:

In TDD development, the first thing
you typically do is to create your
interface and then begin writing your
unit tests against that interface. As
you progress through the TDD process
you would end-up creating a class that
implements the interface and then at
some point your unit test would pass.

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

When describing why a class is valuable and how it behaves, the first thing you typically
do is to create an example of how to use the class, often via its interface*. As you add
desired behavior you end up creating a class which provides that value, and then at some
point your example works.

*May be an actual Interface or simply the accessible API of the class, eg: Ruby
doesn't have interfaces.

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

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

希望这可以帮助!


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

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


没有!只测试接口。

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


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

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


编写测试有两个原因:

  • 断言预期的行为
  • 防止行为回归
  • 承担(1)断言预期的行为:

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

  • 我写的东西有用吗?
  • 这个循环真的结束了吗?
  • 它按照我认为的顺序循环吗?
  • 这是否适用于空输入?
  • 这些是我们在脑海中回答的问题,通常,我们也会尝试在头脑中执行代码,确保它看起来确实有效。对于这些情况,让计算机以明确的方式回答它们通常很有用。所以我们编写一个断言它的单元测试。这使我们对代码充满信心,帮助我们尽早发现缺陷,甚至可以帮助实际实现代码。

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

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

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

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

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

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

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

    这是您典型的单元测试,在提及TDD或BDD时,每个人都会讨论这个问题。重点是加强边界并保护它们免受变化。您不希望为此测试私有方法,因为私有方法不是边界。受保护的方法是限制边界,我会保护它们。它们不暴露于世界,但仍暴露于其他隔间或"单位"。

    该怎么做?

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

    是和否。

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

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

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

    好。


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

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


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

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

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


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

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

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

    希望能帮助到你!


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

    事实上,你的方法和想法是完全正确的,特别是关于代码覆盖率。

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

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

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


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

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