关于单元测试:我应该测试私有方法还是仅测试公共方法?

Should I test private methods or only public ones?

我读过这篇关于如何测试私有方法的帖子。 我通常不测试它们,因为我一直认为只测试从对象外部调用的公共方法会更快。 你测试私人方法吗? 我应该经常测试吗?


我没有单元测试私有方法。私有方法是应该为类的用户隐藏的实现细节。测试私有方法会破坏封装。

如果我发现私有方法很庞大或复杂或非常重要,需要自己的测试,我只需将它放在另一个类中并在那里公开(Method Object)。然后我可以轻松地测试现在存在于自己的类中的先前私有但现在公开的方法。


测试的目的是什么?

到目前为止,大多数答案都说私有方法是实现细节,只要公共接口经过充分测试和工作,它们就不会(或至少不应该)起作用。如果您测试的唯一目的是保证公共接口有效,那绝对是正确的。

就个人而言,我主要用于代码测试是为了确保未来的代码更改不会导致问题并帮助我调试工作。我发现像公共接口一样彻底地测试私有方法(如果不是这样的话!)进一步推动了这个目的。

考虑一下:你有公共方法A,它调用私有方法B.A和B都使用方法C.C被更改(可能由您,可能是供应商),导致A开始失败其测试。对B进行测试也不是很有用,即使它是私有的,所以你知道问题是在A中使用C,B使用C还是两者兼而有之?

在公共接口的测试覆盖不完整的情况下,测试私有方法也会增加价值。虽然这是我们通常希望避免的情况,但效率单元测试取决于测试发现错误以及这些测试的相关开发和维护成本。在某些情况下,100%测试覆盖率的好处可能被认为不足以保证这些测试的成本,从而在公共接口的测试覆盖范围中产生差距。在这种情况下,对私有方法进行有针对性的测试可能是代码库的一个非常有效的补充。


我倾向于遵循Dave Thomas和Andy Hunt在他们的实用单位测试一书中的建议:

In general, you don't want to break any encapsulation for the sake of
testing (or as Mom used to say,"don't expose your privates!"). Most
of the time, you should be able to test a class by exercising its
public methods. If there is significant functionality that is hidden
behind private or protected access, that might be a warning sign that
there's another class in there struggling to get out.

但有时我无法阻止自己测试私有方法,因为它让我感到放心,我正在构建一个完全健壮的程序。


我觉得有必要测试私人功能,因为我在我们的项目中越来越多地遵循我们最新的QA建议:

No more than 10 in cyclomatic complexity per function.

现在,执行这项政策的副作用是,我的许多大型公共职能部门都被分成了许多更集中,更好命名的私人职能部门。
公共职能仍然存在(当然),但基本上被简化为所谓的私人"子职能"

这实际上很酷,因为callstack现在更容易阅读(而不是大函数中的bug,我在子子函数中有一个错误,其中包含callstack中先前函数的名称,以帮助我理解"我怎么到那里"

但是,现在似乎更容易直接对这些私有函数进行单元测试,并将大型公共函数的测试留给需要解决方案的某种"集成"测试。

只需2美分。


是的我测试私有函数,因为虽然它们是通过您的公共方法测试的,但在TDD(测试驱动设计)中测试应用程序的最小部分是很好的。但是当您进入测试单元课程时,无法访问私有函数。这是我们测试私有方法的方法。

为什么我们有私人方法?

私有函数主要存在于我们的类中,因为我们想在公共方法中创建可读代码。
我们不希望这个类的用户直接调用这些方法,而是通过我们的公共方法。此外,我们不希望在扩展类时(在受保护的情况下)更改其行为,因此它是私有的。

当我们编码时,我们使用测试驱动设计(TDD)。这意味着有时我们会偶然发现一些私有且想要测试的功能。私有函数在phpUnit中是不可测试的,因为我们无法在Test类中访问它们(它们是私有的)。

我们认为这里有3个解决方案:

你可以通过公共方法测试你的私人

好处

  • 直接的单元测试(不需要'hacks')

缺点

  • 程序员需要了解公共方法,而他只想测试私有方法
  • 您没有测试应用程序中最小的可测试部分

2.如果私有是如此重要,那么为它创建一个新的单独类可能是一个代码

好处

  • 你可以将它重构为一个新类,因为如果是这样的话
    重要的是,其他课程也可能需要它
  • 可测试单元现在是一种公共方法,因此是可测试的

缺点

  • 如果不需要,你不想创建一个类,只使用它
    方法来自的类
  • 由于增加的开销导致潜在的性能损失

3.将访问修饰符更改为(最终)受保护

好处

  • 您正在测试应用程序中最小的可测试部分。什么时候
    使用final protected,该函数不会被覆盖(只是
    像一个私人)
  • 没有性能损失
  • 没有额外的开销

缺点

  • 您正在更改受保护的私人访问权限,这意味着它
    它的孩子可以访问
  • 您仍然需要在测试类中使用Mock类来使用它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Detective {
  public function investigate() {}
  private function sleepWithSuspect($suspect) {}
}
Altered version:
class Detective {
  public function investigate() {}
  final protected function sleepWithSuspect($suspect) {}
}
In Test class:
class Mock_Detective extends Detective {

  public test_sleepWithSuspect($suspect)
  {
    //this is now accessible, but still not overridable!
    $this->sleepWithSuspect($suspect);
  }
}

所以我们的测试单元现在可以调用test_sleepWithSuspect来测试我们以前的私有函数。


出于几个原因,我不喜欢测试私有功能。它们如下(这些是TLDR人员的要点):

  • 通常当你想要测试一个类的私有方法时,
    这是一种设计气味。
  • 你可以通过公众测试它们
    接口(这是你想要测试它们的方式,因为那是怎么回事
    客户端将调用/使用它们)。你可以通过以下方式获得虚假的安全感
    看到你私人的所有通过测试的绿灯
    方法。通过公共接口测试私有函数的边缘情况要好得多/更安全。
  • 您冒着严重的测试重复风险(测试外观/感觉)
    非常相似)通过测试私有方法。这很重要
    当需求发生变化时,会产生更多的测试结果
    必要的会打破。它也可以让你处于一个合适的位置
    因为你的测试套件难以重构......这是最终的
    具有讽刺意味的是,因为测试套件可以帮助您安全地重新设计
    和重构!
  • 我将用一个具体的例子解释每一个。事实证明,2)和3)有点错综复杂地连接,所以他们的例子是相似的,虽然我认为它们是你不应该测试私有方法的独立原因。

    有时候测试私有方法是合适的,重要的是要注意上面列出的缺点。我稍后会更详细地介绍它。

    我还说明为什么TDD不是最后测试私有方法的有效借口。

    重构出糟糕设计的方法

    我看到的最常见的(反)模式之一是Michael Feathers所谓的"冰山"课程(如果你不知道Michael Feathers是谁,去买/读他的书"有效地使用遗产代码"。他是如果您是专业的软件工程师/开发人员,那么值得了解的人。还有其他(反)模式导致这个问题突然出现,但这是迄今为止我偶然发现的最常见的模式。"Iceberg"类有一个公共方法,其余的都是私有的(这就是测试私有方法的原因)。它被称为"冰山"类,因为通常会有一个单独的公共方法,但其余功能以私有方式的形式隐藏在水下。它可能看起来像这样:

    Rule Evaluator

    例如,您可能希望通过在字符串上连续调用GetNextToken()并看到它返回预期结果来测试GetNextToken()。像这样的函数确实需要进行测试:这种行为并不简单,特别是如果您的标记化规则很复杂。让我们假装它并不是那么复杂,我们只想把空间划分为令牌。所以你写了一个测试,也许它看起来像这样(一些语言不可知的伪代码,希望这个想法很清楚):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    TEST_THAT(RuleEvaluator, canParseSpaceDelimtedTokens)
    {
        input_string ="1 2 test bar"
        re = RuleEvaluator(input_string);

        ASSERT re.GetNextToken() IS"1";
        ASSERT re.GetNextToken() IS"2";
        ASSERT re.GetNextToken() IS"test";
        ASSERT re.GetNextToken() IS"bar";
        ASSERT re.HasMoreTokens() IS FALSE;
    }

    嗯,这实际上看起来很不错。我们希望确保在进行更改时保持这种行为。但GetNextToken()是一个私人功能!所以我们不能像这样测试它,因为它甚至不会编译(假设我们使用的某些语言实际上强制执行公共/私有,不像Python这样的脚本语言)。但是如何改变RuleEvaluator类以遵循单一责任原则(单一责任原则)?例如,我们似乎有一个解析器,标记器和评估器卡在一个类中。将这些责任分开是不是更好?最重要的是,如果你创建一个Tokenizer类,那么它的公共方法将是HasMoreTokens()GetNextTokens()RuleEvaluator类可以将Tokenizer对象作为成员。现在,我们可以保持与上面相同的测试,除了我们测试Tokenizer类而不是RuleEvaluator类。

    这是UML中的样子:

    Rule Evaluator Refactored

    请注意,这种新设计增加了模块化,因此您可能会在系统的其他部分中重复使用这些类(在您不能之前,私有方法根??据定义不可重用)。这是打破RuleEvaluator的主要优势,同时增加了可理解性/局部性。

    测试看起来非常相似,除了它实际上会编译,因为GetNextToken()方法现在在Tokenizer类上是公共的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
    {
        input_string ="1 2 test bar"
        tokenizer = Tokenizer(input_string);

        ASSERT tokenizer.GetNextToken() IS"1";
        ASSERT tokenizer.GetNextToken() IS"2";
        ASSERT tokenizer.GetNextToken() IS"test";
        ASSERT tokenizer.GetNextToken() IS"bar";
        ASSERT tokenizer.HasMoreTokens() IS FALSE;
    }

    通过公共接口测试私有组件并避免测试重复

    即使你认为你不能将你的问题分解为更少的模块化组件(如果你只是尝试这样做,你可以95%的时间),你可以通过公共接口简单地测试私有函数。很多时候私人成员不值得测试,因为他们将通过公共界面进行测试。很多时候,我看到的测试看起来非常相似,但测试两种不同的功能/方法。最终发生的事情是,当需求发生变化时(他们总是这样做),你现在有2个破坏的测试而不是1.如果你真的测试了所有的私有方法,你可能会有更多像10个破坏的测试而不是1个。简而言之。 ,测试私有函数(通过使用FRIEND_TEST或使它们公开或使用反射)否则可以通过公共接口进行测试可能导致测试重复。你真的不想要这个,因为没有什么比你的测试套件更让你失望的伤害。它应该减少开发时间并降低维护成本!如果您测试通过公共接口进行测试的私有方法,那么测试套件可能会做相反的事情,并积极地增加维护成本并增加开发时间。当你公开一个私有函数,或者你使用像FRIEND_TEST和/或反射这样的东西时,你通常会在长期内后悔。

    考虑Tokenizer类的以下可能实现:

    enter image description here

    假设SplitUpByDelimiter()负责返回一个数组,使得数组中的每个元素都是一个标记。此外,我们只是说GetNextToken()只是这个向量的迭代器。所以你的公开测试看起来像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
    {
        input_string ="1 2 test bar"
        tokenizer = Tokenizer(input_string);

        ASSERT tokenizer.GetNextToken() IS"1";
        ASSERT tokenizer.GetNextToken() IS"2";
        ASSERT tokenizer.GetNextToken() IS"test";
        ASSERT tokenizer.GetNextToken() IS"bar";
        ASSERT tokenizer.HasMoreTokens() IS false;
    }

    让我们假装我们拥有Michael Feather所称的摸索工具。这是一个工具,可以让您触摸其他人的私人部分。一个例子是来自googletest的FRIEND_TEST,或者如果语言支持它的反射。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    TEST_THAT(TokenizerTest, canGenerateSpaceDelimtedTokens)
    {
        input_string ="1 2 test bar"
        tokenizer = Tokenizer(input_string);
        result_array = tokenizer.SplitUpByDelimiter("");

        ASSERT result.size() IS 4;
        ASSERT result[0] IS"1";
        ASSERT result[1] IS"2";
        ASSERT result[2] IS"test";
        ASSERT result[3] IS"bar";
    }

    那么,现在让我们说需求发生变化,令牌化变得更加复杂。您决定一个简单的字符串分隔符是不够的,并且您需要一个Delimiter类来处理该作业。当然,你会期望一个测试中断,但是当你测试私有函数时疼痛会增加。

    何时可以测试私有方法?

    软件中没有"一刀切"。有时候"打破规则"是可以的(而且实际上是理想的)。我强烈主张不在可能的情况下测试私有功能。当我认为没关系时,有两种主要情况:

  • 我已经与遗留系统进行了广泛的合作(这就是为什么我是Michael Feathers的忠实粉丝),我可以肯定地说,有时候测试私有功能是最安全的。将"特征测试"纳入基线可能特别有用。

  • 你很匆忙,不得不在这里和现在做最快的事情。从长远来看,您不希望测试私有方法。但我会说重构通常需要一些时间才能解决设计问题。有时你必须在一周内发货。没关系:快速而肮脏并使用摸索工具测试私有方法,如果这是您认为最快,最可靠的完成工作的方法。但要明白,从长远来看,你所做的事情并不是最理想的,请考虑回到它(或者,如果它被遗忘但你稍后再看,请修复它)。

  • 可能还有其他情况可以。如果你认为这没关系,并且你有充分的理由,那就去做吧。没有人阻止你。请注意潜在的成本。

    TDD借口

    顺便说一句,我真的不喜欢使用TDD作为测试私有方法的借口的人。我练习TDD,我不认为TDD强迫你这样做。您可以先编写测试(针对您的公共接口),然后编写代码以满足该接口。有时我会为公共接口编写一个测试,我也会通过编写一个或两个较小的私有方法来满足它(但是我不直接测试私有方法,但我知道它们有效或者我的公共测试会失败)。如果我需要测试该私有方法的边缘情况,我会编写一大堆测试,通过我的公共接口来测试它们。如果你无法弄清楚如何击中边缘情况,这是一个强有力的迹象,你需要使用自己的公共方法重构为小组件。这是一个标志,你是私人功能做得太多,超出了课堂范围。

    此外,有时候我发现我写了一个目前咀嚼得太大的测试,所以我想"呃我以后会有更多的API可以用来进行测试"(我我会把它评论出来并保留在我的脑海里。这是我遇到的很多开发人员将开始为他们的私人功能编写测试的地方,使用TDD作为替罪羊。他们说"哦,我需要一些其他测试,但为了编写测试,我需要这些私有方法。因此,因为我不编写测试就不能编写任何生产代码,所以我需要写一个测试对于私人方法。"但他们真正需要做的是重构为更小和可重用的组件,而不是在当前类中添加/测试一堆私有方法。

    注意:

    我刚回答了一个关于使用GoogleTest测试私有方法的类似问题。我大多修改了这个答案,在这里更加语言无关。

    附:以下是Michael Feathers关于冰山课程和摸索工具的相关讲座:https://www.youtube.com/watch?v = 4cVZvoFGJTU

    好。


    我认为最好只测试一个对象的公共接口。从外部世界的角度来看,只有公共界面的行为很重要,这就是你的单元测试应该针对的。

    一旦你为一个对象编写了一些可靠的单元测试,你就不想再回过头来改变那些测试,因为接口背后的实现发生了变化。在这种情况下,您已经破坏了单元测试的一致性。


    如果您的私有方法未通过调用您的公共方法进行测试,那么它在做什么?
    我说私人不受保护或朋友。


    如果私有方法定义良好(即,它具有可测试的功能并且不打算随时间改变)那么是。我测试了一切有意义的东西。

    例如,加密库可能会隐藏它使用私有方法执行块加密的事实,该方法一次只加密8个字节。我会为此编写一个单元测试 - 它不是要改变,即使它是隐藏的,如果它确实中断(例如由于未来的性能增强),那么我想知道它是私有函数破坏了,而不仅仅是其中一个公共职能破裂了。

    它可以加快调试速度。

    -亚当


    我不是这个领域的专家,但是单元测试应该测试行为,而不是实现。私有方法严格地是实现的一部分,因此不应该测试恕我直言。


    我们通过推理测试私有方法,我的意思是我们寻找至少95%的总类测试覆盖率,但只有我们的测试调用公共或内部方法。为了获得覆盖,我们需要根据可能发生的不同场景对公共/内部进行多次调用。这使得我们的测试更加关注他们正在测试的代码的目的。

    特朗普对你所关联的帖子的回答是最好的。


    如果您正在开发测试驱动(TDD),您将测试您的私有方法。


    我认为单元测试用于测试公共方法。您的公共方法使用您的私有方法,因此它们也间接地进行测试。


    我一直在讨论这个问题,特别是在试用TDD的时候。

    我发现在TDD的情况下,我认为可以彻底解决这个问题。

  • 测试私有方法,TDD和测试驱动的重构
  • 测试驱动开发不是测试
  • 综上所述:

    • 当使用测试驱动的开发(设计)技术时,私有方法应该仅在已经工作和测试的代码的重新分解过程中出现。

    • 根据该过程的本质,从经过彻底测试的功能中提取的任何简单实现功能都将是自我测试的(即间接测试覆盖)。

    对我而言,似乎很清楚,在编码的开始部分,大多数方法将是更高级别的功能,因为它们封装/描述了设计。

    因此,这些方法将是公开的,并且测试它们将是足够容易的。

    私有方法将在一切运行良好之后出现,我们为了可读性和清洁而重新考虑因素。


    如上所述,"如果你不测试你的私人方法,你怎么知道他们不会破坏?"

    这是一个重大问题。单元测试的一个重点是知道事情在何时,何时以及如何破坏。从而减少了大量的开发和QA工作量。如果测试的所有内容都是公开的,那么您就没有诚实的报道和对班级内部的描述。

    我发现执行此操作的最佳方法之一是将测试引用添加到项目中,并将测试放在与私有方法并行的类中。放入适当的构建逻辑,以便测试不会构建到最终项目中。

    然后,您将拥有测试这些方法的所有好处,您可以在几秒钟内找到问题,而不是几分钟或几小时。

    总而言之,是的,单元测试你的私有方法。


    你不应该。如果您的私有方法具有必须测试的足够复杂性,则应将它们放在另一个类上。保持高凝聚力,一个班级应该只有一个目的。类公共接口应该足够了。


    如果你不测试你的私人方法,你怎么知道他们不会破坏?


    我理解私有方法被视为实现细节的观点,然后不必进行测试。如果我们不得不在对象之外进行开发,我会坚持这条规则。但是我们,我们是某种受限制的开发人员,他们只是在对象之外开发,只调用他们的公共方法吗?或者我们实际上还在开发那个对象?由于我们不一定要编写外部对象,我们可能不得不将这些私有方法称为我们正在开发的新公共方法。知道私人方法能够抵御一切困难并不是很好吗?

    我知道有些人可以回答,如果我们正在开发另一个公共方法到那个对象那么这个应该被测试,就是这样(私有方法可以继续生活而不进行测试)。但是对于对象的任何公共方法也是如此:在开发Web应用程序时,对象的所有公共方法都是从控制器方法调用的,因此可以被视为控制器的实现细节。

    那么为什么我们要测试对象呢?因为它真的很难,更不用说不可能确定我们用适当的输入测试控制器的方法,这将触发底层代码的所有分支。换句话说,我们在堆栈中越高,测试所有行为就越困难。私有方法也是如此。

    对我来说,私人和公共方法之间的边界是测试时的心理标准。对我更重要的标准是:

    • 是不同地方不止一次调用的方法?
    • 方法是否足够复杂,需要测试?

    这显然取决于语言。在过去使用c ++时,我已经将测试类声明为友元类。不幸的是,这确实需要您的生产代码了解测试类。


    对于从测试中调用的apis,公共与私有不是一个有用的区别,方法与类也不是。大多数可测试单元在一个上下文中可见,但在其他上下文中隐藏。

    重要的是保险和费用。在实现项目的覆盖目标(线,分支,路径,块,方法,类,等价类,用例......团队决定的任何内容)时,您需要最小化成本。

    因此,使用工具来确保覆盖率,并设计测试以降低成本(短期和长期)。

    不要让测试比必要的更昂贵。
    如果只测试公共入口点是最便宜的。
    如果测试私有方法最便宜,那就去做吧。

    随着您越来越有经验,您将更好地预测何时值得重构以避免测试维护的长期成本。


    如果我发现私有方法很庞大或复杂或非常重要,需要自己的测试,我只需将它放在另一个类中并在那里公开(Method Object)。然后我可以轻松地测试以前私有但现在公开的方法,它现在存在于自己的类中。


    我看到很多人都在思考:在公共层面进行测试。但这不是我们的QA团队所做的吗?他们测试输入和预期输出。如果作为开发人员我们只测试公共方法,那么我们只是重做QA的工作而不是通过"单元测试"添加任何值。


    你也可以使你的方法包私有,即默认,你应该能够对它进行单元测试,除非它是私有的。


    是的,你应该尽可能测试私人方法。为什么?为了避免测试用例的不必要的状态空间爆炸,最终只是在相同的输入上重复隐式地测试相同的私有函数。让我们用一个例子来解释原因。

    考虑以下略有设想的例子。假设我们想要公开一个占用3个整数的函数,并且当且仅当这3个整数都是素数时才返回true。我们可以像这样实现它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public bool allPrime(int a, int b, int c)
    {
      return andAll(isPrime(a), isPrime(b), isPrime(c))
    }

    private bool andAll(bool... boolArray)
    {
      foreach (bool b in boolArray)
      {
        if(b == false) return false;
      }
      return true;
    }

    private bool isPrime(int x){
      //Implementation to go here. Sorry if you were expecting a prime sieve.
    }

    现在,如果我们采用严格的方法只测试公共函数,我们只允许测试allPrime而不是isPrimeandAll

    作为测试人员,我们可能对每个参数的五种可能性感兴趣:< 0= 0= 1prime > 1not prime > 1。但要彻底,我们还必须看到各种论点的组合如何相互作用。根据我们的直觉,这是5*5*5 = 125个测试用例,我们需要彻底测试这个函数。

    另一方面,如果允许我们测试私有函数,我们可以用更少的测试用例覆盖尽可能多的地面。我们只需要5个测试用例来测试isPrime到与之前的直觉相同的水平。通过丹尼尔·杰克逊提出的小范围假设,我们只需要测试andAll函数的长度,例如3或4.最多16次测试。共计21项测试。当然,我们可能希望在allPrime上运行一些测试,但我们不会觉得有必要详尽地介绍我们所说的所有125种输入场景组合。只是一些快乐的道路。

    一个人为的例子,当然,但有必要进行明确的示范。并且模式扩展到真实的软件。私有函数通常是最低级别的构建块,因此通常组合在一起以产生更高级别的逻辑。在更高级别的含义,由于各种组合,我们有更多重复的低级别的东西。


    它不仅涉及公共或私有方法或功能,而是关于实现细节。私有函数只是实现细节的一个方面。

    毕竟,单元测试是一种白盒测试方法。例如,无论是谁使用覆盖率分析来识别到目前为止在测试中被忽略的代码部分,都会进入实现细节。

    A)是的,您应该测试实施细节:

    考虑一个排序函数,出于性能原因,如果有多达10个元素,则使用BubbleSort的私有实现,如果有超过10个元素,则使用不同排序方法的私有实现(比如,heapsort)。公共API是排序函数的API。但是,您的测试套件更好地利用了实际使用两种排序算法的知识。

    在这个例子中,您肯定可以在公共API上执行测试。然而,这将需要具有多个测试用例,这些测试用例执行具有多于10个元素的排序函数,使得堆测试算法得到充分测试。仅存在此类测试用例表明测试套件已连接到该功能的实现细节。

    如果sort函数的实现细节发生变化,可能会改变两个排序算法之间的限制或者使用mergesort或其他任何东西替换heapsort:现有的测试将继续有效。然而,它们的价值是值得怀疑的,并且它们可能需要重新设计以更好地测试改变的排序函数。换句话说,尽管测试是在公共API上进行的,但仍会有维护工作。

    B)如何测试实现细节

    许多人认为不应该测试私有功能或实现细节的一个原因是,实现细节更有可能发生变化。这种更高的变化可能性至少是隐藏接口背后的实现细节的原因之一。

    现在,假设接口后面的实现包含更大的私有部分,内部接口上的单独测试可能是一个选项。有些人争辩说,这些部分不应该在私有时进行测试,它们应该变成公开的东西。一旦公开,单元测试该代码就行了。

    这很有趣:虽然界面是内部的,但它很可能会改变,作为一个实现细节。采用相同的界面,将其公开进行一些神奇的转变,即将其转变为不太可能改变的界面。显然这个论证存在一些缺陷。

    但是,背后还有一些事实:在测试实现细节时,特别是使用内部接口时,应该努力使用可能保持稳定的接口。然而,某些界面是否可能是稳定的,不仅仅是根据它是公共的还是私人的可判定的。在我已经工作了一段时间的世界项目中,公共接口也经常发生变化,许多私有接口长期保持不变。

    尽管如此,使用"前门优先"是一个很好的经验法则(参见http://xunitpatterns.com/Principles%20of%20Test%20Automation.html)。但请记住,它被称为"前门首先"而不是"前门"。

    C)总结

    还测试实现细节。更喜欢在稳定的接口(公共或私有)上进行测试。如果实现细节发生变化,则还需要修改对公共API的测试。把一些私人事物公之于众并不会神奇地改变它的稳定性。


    我从不理解单元测试的概念,但现在我知道它的目标是什么。

    单元测试不是一个完整的测试。因此,它不是QA和手动测试的替代品。 TDD在这方面的概念是错误的,因为您无法测试所有内容,包括私有方法,还有使用资源的方法(尤其是我们无法控制的资源)。 TDD的基础是它的所有质量都是无法实现的。

    单元测试更多是一个枢轴测试你标记一些任意的枢轴,枢轴的结果应该保持不变。


    不,您不应该测试私有方法为什么?此外,流行的模拟框架(如Mockito)不支持测试私有方法。


    一个要点是

    如果我们测试以确保逻辑的正确性,并且私有方法携带逻辑,我们应该测试它。不是吗?那么我们为什么要跳过这个呢?

    基于方法的可见性编写测试是完全不相关的想法。

    反过来

    另一方面,在原始类之外调用私有方法是一个主要问题。在某些模拟工具中模拟私有方法也有局限性。 (例如:Mockito)

    虽然有一些像Power Mock这样的工具支持它,但这是一个危险的操作。原因是它需要破解JVM才能实现这一点。

    可以做的一件事就是(如果你想为私有方法编写测试用例)

    将这些私有方法声明为受保护。但是对于几种情况可能不方便。


    如果方法足够重要/足够复杂,我通常会将其"保护"并进行测试。一些方法将保密,并作为公共/受保护方法的单元测试的一部分隐式测试。


    答案是"我应该测试私有方法吗?"是".......有时"。通常,您应该针对类的接口进行测试。

    • 其中一个原因是因为您不需要对功能进行双重覆盖。
    • 另一个原因是,如果更改私有方法,则必须为它们更新每个测试,即使对象的接口根本没有更改。

    这是一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    class Thing
      def some_string
        one + two
      end

      private

      def one
        'aaaa'
      end

      def two
        'bbbb'
      end

    end


    class RefactoredThing
    def some_string
        one + one_a + two + two_b
      end

      private

      def one
        'aa'
      end

      def one_a
        'aa'
      end

      def two
        'bb'
      end

      def two_b
        'bb'
      end
    end

    RefactoredThing中,您现在有5个测试,其中2个必须更新以进行重构,但您的对象的功能确实没有改变。所以让我们说事情比这更复杂,你有一些定义输出顺序的方法,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    def some_string_positioner
      if some case
      elsif other case
      elsif other case
      elsif other case
      else one more case
      end
    end

    这不应该由外部用户运行,但是您的封装类可能会很重,以便一遍又一遍地运行那么多逻辑。在这种情况下,您可能宁愿将其提取到一个单独的类中,为该类提供一个接口并对其进行测试。

    最后,假设您的主要对象非常重,并且方法非常小,您确实需要确保输出正确。你在想,"我必须测试这种私人方法!"。也许你可以通过传递一些繁重的工作作为初始化参数来使你的对象更轻?然后你可以传递更轻的东西并对其进行测试。


    绝对没错。这是单元测试的重点,你测试单位。私有方法是一个单位。没有测试私有方法TDD(测试驱动开发)是不可能的,