我应该测试私有方法还是仅测试公共方法?[关闭]


347

我已阅读这篇文章如何测试私有方法。我通常不测试它们,因为我一直认为仅测试将从对象外部调用的公共方法会更快。您是否测试私有方法?我应该一直测试它们吗?



“我应该测试私人助手吗?” 是。“我应该直接测试私人助手吗?” 通常,这取决于您是否可以通过公共界面轻松对其进行测试,为什么还要直接对其进行测试?如果通过公共接口测试助手的所有方面变得很复杂,那么该组件是否已不再存在于一个单一的单元中?
Mihai Danila

Answers:


328

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

如果我发现私有方法庞大,复杂或重要到需要进行自己的测试,则将其放在另一个类中并在其中公开(方法对象)。然后,我可以轻松地测试以前生活在其自己的类中的以前私有但现在公开的方法。


88
我不同意。理想情况下,您在开始编写函数之前先编写一个快速测试。考虑典型的输入以及输出将是什么。编写测试(不超过几秒钟的时间)并编写代码,直到正确通过测试为止。没有理由为了私人方法而放弃这种工作方式。
Frank

254
说私有方法不需要测试就好比说汽车还可以,只要它开着就可以了,而且引擎盖下的东西也没关系。但是知道内部的一些电缆开始松动不是很好吗-即使用户没有注意任何事情呢?当然,您可以将所有内容公开,但是有什么意义呢?您将始终需要一些私有方法。
Frank

37
“私有方法是实现的细节,应该对类的用户隐藏。” 但是测试是否真的与“常规”(运行时)用户在类界面的同一侧?;)
mlvljr 2012年

34
将要测试的任何内容拖到另一个类中的危险是,您可能会承担过度设计产品的负担,并拥有一百万个永远不会重复使用的可重复使用组件。
occulus

44
将一段代码与汽车进行比较是错误的。代码不会随着时间的流逝而变坏,它是永恒的。如果您对公共接口的测试仅能确定它“ 看起来还不错 ”,那么您对公共代码的测试是不够的。在这种情况下,无论您如何努力,单独测试私有方法都不会使整体测试完成。使用代码内部工作的知识来创建正确的方案,集中精力全面测试您的整个公共代码。
rustyx

293

测试的目的是什么?

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

就个人而言,我进行代码测试的主要用途是确保将来的代码更改不会引起问题,并在可能的情况下帮助我进行调试。我发现对私有方法的测试与对公共接口的测试一样彻底(如果不是,那么!)进一步达到了这个目的。

考虑:您有一个公用方法A,它调用私有方法B。A和B都使用方法C。C被更改了(也许由您,也许由供应商更改),导致A开始测试失败。即使B是私有的,也对B进行测试不是有用的,这样您就知道问题出在A是使用C,B是使用C还是两者都存在?

在私有接口的测试范围不完整的情况下,测试私有方法还可以增加价值。虽然这是我们通常要避免的情况,但效率单元测试取决于发现错误的测试以及这些测试的相关开发和维护成本。在某些情况下,可能会判定100%测试覆盖率的好处不足以保证这些测试的成本,从而在公共界面的测试覆盖率上造成了缺口。在这种情况下,对专用方法进行有针对性的测试可能是对代码库的非常有效的补充。


72
这里的问题是,那些“未来代码更改”总是意味着重构某些类的内部工作方式。这种情况经常发生,以至于编写测试会影响重构。
Outlaw程序员

40
另外,如果您不断更改单元测试,那么您将失去测试的所有一致性,甚至可能在单元测试本身中创建错误。

6
@ 17如果测试和实现是同步修改的(看起来应该是这样),那么问题就会少很多。
mlvljr 2012年


3
@Pacerier在测试您的代码和进行连续的自动化测试过程之间还有一个区别。显然,您应该确保私有方法有效,但是您不应进行将您与私有方法结合的测试,因为它不是软件用例的一部分。
Didier A.

150

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

通常,您不想为了测试而破坏任何封装(或者就像妈妈曾经说过的,“不要暴露您的私人信息!”)。大多数时候,您应该能够通过使用公共方法来测试一个类。如果私有或受保护的访问背后隐藏着重要的功能,则可能是一个警告信号,表明其中还有另一个类别正在努力摆脱困境。

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


9
我建议禁用针对私有方法的单元测试。它们是代码耦合,将负担将来的重构工作,甚至有时会妨碍功能的添加或修改。在实现它们时为它们编写测试是一种好方法,这是一种自动的方法来断言您正在实现的工作,但是将测试保持为回归是无益的。
Didier A.

61

在执行项目中越来越多的最新质量检查建议之一时,我感到不得不测试私有功能:

每个函数的圈复杂度不超过10 。

现在,执行此策略的副作用是,我的许多大型公共职能被划分为许多更集中,名称更好的私有职能。
公共功能仍然存在(当然),但本质上被简化为称为所有这些私有“子功能”

这实际上很酷,因为调用栈现在更易于阅读(而不是大型函数中的错误,我在子子函数中有一个错误,它具有调用栈中先前函数的名称,以帮助我理解'我怎么到达那里')

但是,现在似乎可以直接对那些私有功能进行单元测试,而将大型公共功能的测试留给某种需要解决方案的“集成”测试似乎更容易。

只是我的2美分。


2
为了对@jop做出反应,我觉得不需要将那些私有函数(由于将一个过于庞大的复杂的复杂公共函数划分出来而创建)导出到另一个类中。我喜欢在同一个班级中让它们仍然与公共功能紧密结合。但是仍然经过单元测试。
VonC

2
我的经验是,那些私有方法只是那些公用方法正在重用的实用程序方法。有时,将原始类分为两个(或三个)内聚性类更为方便,从而使这些私有方法在其自己的类中公开,因此可测试。
08年

7
以我为例,这些新的私有函数实际上是公共函数所代表的较大算法的一部分。该功能分为较小的部分,这些部分不是实用程序,而是较大过程的步骤。因此,需要对它们进行单元测试(而不是立即对整个算法进行单元测试)
VonC

对于那些对圈复杂度感兴趣的人,我添加了一个关于以下主题的问题:stackoverflow.com/questions/105852/…–
VonC

糟糕,由于标题中有错字,问题的网址已更改! stackoverflow.com/questions/105852/…–
VonC

51

是的,我确实在测试私有功能,因为尽管私有功能已通过您的公共方法进行了测试,但是在TDD(测试驱动设计)中很好地测试了应用程序的最小部分。但是,当您处于测试单元类中时,私有功能将无法访问。这是我们测试私有方法的工作。

为什么我们有私有方法?

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

在编写代码时,我们使用测试驱动设计(TDD)。这意味着有时我们偶然发现了一项私有的功能,需要测试。私有函数无法在phpUnit中进行测试,因为我们无法在Test类中访问它们(它们是私有的)。

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

1.您可以通过公开方法测试私人身份

优点

  • 简单的单元测试(无需“ hacks”)

缺点

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

2.如果私有非常重要,那么为它创建一个新的单独的类也许是一种代码技巧

优点

  • 您可以将其重构为新的类,因为如果这很重要,其他类也可能需要它
  • 可测试单元现在是公共方法,因此可测试

缺点

  • 您不需要创建一个不需要的类,而只供该方法来自的类使用
  • 由于增加了开销,可能导致性能损失

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

优点

  • 您正在测试应用程序中最小的可测试部分。使用最终保护时,该功能将不可覆盖(就像私有的一样)
  • 无性能损失
  • 没有额外的开销

缺点

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

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来测试我们以前的私有函数。


eddy147,我真的很喜欢通过模拟测试受保护方法的概念。谢谢!!!!
西奥多·史密斯

15
我只想指出,在TDD的原始描述中,在单元测试中,单元,而不是方法/函数。因此,当您提到“测试应用程序的最小部分”时,将最小的可测试部分称为方法是错误的。如果使用该逻辑,那么您最好只讲一行代码而不是整个代码块。
Matt Quigley 2015年

@Matt一个工作单元可以指向一个类,也可以指向一个方法。
eddy147

4
@ eddy147单元测试来自“测试驱动开发”,其中单元被定义为一个类。就像Internet一样,语义已经扩展为意味着很多事情(即问2个人单元测试和集成测试之间的区别是什么,您将获得7个答案)。TDD是一种使用SOLID原则编写软件的方法,其中包括Single Responsibility(单一职责),在该职责中,一个班级承担单一责任,并且不应具有很高的循环复杂性。私有方法被封装没有相应的单元测试。
Matt Quigley 2015年

“当我们编写代码时,我们使用测试驱动设计(TDD)。这意味着有时我们偶然发现了一项私有的功能,想要进行测试。” 我非常不同意此声明,请参阅下面的答案以获取更多详细信息。TDD并不意味着您被迫测试私有方法。您可以选择测试私有方法:这是您的选择,但这并不是TDD导致您执行此类操作。
Matt Messersmith

41

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

  1. 通常,当您想测试类的私有方法时,这是一种设计气味。
  2. 您可以通过公共接口测试它们(这就是您要测试它们的方式,因为这是客户端调用/使用它们的方式)。通过查看私有方法所有通过测试的绿灯,您可能会得到错误的安全感。通过公共接口在私有功能上测试边缘情况会更好/更安全。
  3. 通过测试专用方法,您可能会面临严重的测试重复(看起来/感觉非常相似的测试)的风险。当需求改变时,这将产生严重的后果,因为过多的测试将被打破。它还可能使您因测试套件而难以重构……这是最终的讽刺意味,因为测试套件可帮助您安全地进行重新设计和重构!

我将用一个具体的例子来解释每一个。事实证明2)和3)有点错综复杂,所以它们的示例相似,尽管我认为它们是您不应该测试私有方法的单独原因。

有时候测试私有方法是合适的,了解上面列出的缺点很重要。我将在后面详细介绍。

我还将讨论为什么TDD最终并不是测试私有方法的有效借口。

重构出不良设计的出路

我看到的最常见的(反)模式之一是Michael Feathers所说的“ Iceberg”课(如果您不知道Michael Feathers是谁,那就去买/读他的书“使用Legacy Code有效地工作”。一个值得了解您是否是专业软件工程师/开发人员的人)。还有其他(反)模式会使此问题浮出水面,但这是我偶然发现的最常见的模式。“ Iceberg”类有一个公共方法,其余的是私有方法(这就是为什么要尝试测试私有方法的原因)。之所以称为“ Iceberg”类,是因为通常会出现一个单独的公共方法,但是其余功能以私有方法的形式隐藏在水下。

规则评估者

例如,您可能希望GetNextToken()通过在字符串上依次调用它并返回预期结果来进行测试。像这样的函数确实需要进行测试:这种行为并非微不足道,尤其是在分词规则复杂的情况下。让我们假装它并不是那么复杂,我们只想捆绑以空格分隔的令牌。因此,您编写了一个测试,也许看起来像这样(某些语言不可知的伪代码,希望这个想法很清楚):

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()私有功能!因此,我们无法像这样测试它,因为它甚至不会编译(假设我们使用的是某种实际上强制执行public / private的语言,这与某些脚本语言(如Python)不同)。但是,如何改变RuleEvaluator班级以遵循单一责任原则(Single Responsibility Principle)呢?例如,我们似乎有一个解析器,标记器和评估器被卡在一个类中。仅将这些职责分开会更好吗?最重要的是,如果您创建一个Tokenizer类,则它的公共方法将是HasMoreTokens()and GetNextTokens()。本RuleEvaluator类可以有Tokenizer对象为成员。现在,我们可以保持与上面相同的测试,除了我们要测试的是Tokenizer类而不是RuleEvaluator类。

这是UML中的样子:

重构规则评估器

请注意,这种新设计提高了模块化,因此您有可能在系统的其他部分重用这些类(在定义之前,私有方法不可重用)。这是将RuleEvaluator分解的主要优点,并且具有更高的可理解性/局部性。

该测试看起来非常相似,不同之处GetNextToken()在于,由于该方法现已在Tokenizer该类上公开,因此这次实际上将进行编译:

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该类的以下可能实现:

在此处输入图片说明

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

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)所说的摸索工具。此工具可让您触摸其他人的私处。一个示例FRIEND_TEST来自googletest,如果语言支持,则进行反射。

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类来处理作业。自然,您会期望一项测试能够通过,但是当您测试私有功能时,这种痛苦会增加。

什么时候可以测试私有方法?

软件中没有“一刀切”的功能。有时“违反规则”是可以的(并且实际上是理想的)。我强烈建议不要在可能的情况下测试私有功能。我认为可以的主要情况有两种:

  1. 我已经与遗留系统进行了广泛的合作(这就是为什么我是Michael Feathers的忠实拥护者),并且我可以肯定地说,有时仅测试私有功能最安全。这对于将“特性测试”纳入基线特别有用。

  2. 您很着急,现在和现在都必须做最快的事情。从长远来看,您不想测试私有方法。但是我会说,重构通常需要一些时间来解决设计问题。有时您必须在一周内发货。没关系:如果您认为这是完成工作的最快,最可靠的方法,则可以快速而又肮脏地使用摸索工具测试私有方法。但是要了解,从长远来看,您所做的工作不是最佳的,请考虑重新使用它(或者,如果忘记了它,但是稍后看到它,请修复它)。

可能还有其他情况也可以。如果您认为还可以,并且您有充分的理由,那么请这样做。没有人阻止你。请注意潜在的成本。

TDD借口

顺便说一句,我真的不喜欢使用TDD作为测试私有方法的借口。我练习TDD,但我认为TDD不会强迫您这样做。您可以先编写测试(针对公共接口),然后编写满足该接口的代码。有时我会为公共接口编写测试,而我也会通过编写一个或两个较小的私有方法来满足它(但我不会直接测试私有方法,但我知道它们可以工作,否则我的公共测试会失败)。如果我需要测试该私有方法的边缘情况,我将编写一堆测试,这些测试将通过我的公共接口进行测试。如果您不知道如何处理极端情况,则这是一个强烈的信号,您需要将其重构为每个都有自己的公共方法的小组件。这表明您私有函数做得太多,超出了类的范围

另外,有时我发现我编写的测试现在无法满足需要,因此我认为“嗯,等我有更多的API可以使用时,我会稍后再返回该测试。” '将其注释掉并保留在我的脑海中)。在这里,我遇到的许多开发人员将开始使用TDD作为替罪羊为其私有功能编写测试。他们说:“哦,我还需要其他测试,但是要编写该测试,我需要这些私有方法。因此,由于如果不编写测试就无法编写任何生产代码,因此我需要编写测试用于私有方法。” 但是他们真正需要做的是将其重构为更小且可重用的组件,而不是在当前类中添加/测试一堆私有方法。

注意:

不久前,我回答了一个有关使用GoogleTest测试私有方法的类似问题。我在这里大部分修改了该答案,使其与语言无关。

PS这是迈克尔·费瑟斯(Michael Feathers)关于冰山课程和摸索工具的相关演讲:https : //www.youtube.com/watch?v=4cVZvoFGJTU


我列出“您可能会在系统的其他部分中潜在地重用这些类”的优点是,有时候我标记一个函数为私有的原因是因为我希望它被其他部分使用系统。这是一个特定于语言的问题:理想情况下,这是“模块”的专用,但是如果该语言不支持该模块(例如PHP),则我的类表示模块,而不是单元:专用方法是可重用的代码拥有自己的合同,但只能在该类中重用。
IMSoP

我理解您在说什么,但是我喜欢Python社区处理该问题的方式。如果您用引号命名有问题的“私人”成员_,则表示“嘿,这是'私人'。您可以使用它,但要完全披露,它不是为重用而设计的,只有在您确实需要时才使用它知道你在做什么“。您可以使用任何一种语言使用相同的方法:将这些成员公开,但是要在他们前面加上领导_。或者,也许这些功能确实应该是私有的,并且只是通过公共接口进行了测试(有关更多详细信息,请参见答案)。视情况而定,没有一般规则
Matt Messersmith

26

我认为最好只是测试对象的公共接口。从外部世界的角度来看,仅公共接口的行为很重要,这是单元测试应针对的目标。

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


21

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


3
谢谢。这是一个令人惊讶地被低估的评论,而且即使在撰写了将近8年之后,仍然特别有意义。
Sauronlord

1
出于同样的原因,人们可能会争辩说只能从用户界面测试软件(系统级测试),因为某种程度上该软件中的每个功能都将从那里执行。
德克·赫尔曼

18

如果私有方法定义明确(即,它具有可测试的功能,并且不打算随时间变化),则可以。我会在有意义的地方测试所有可测试的东西。

例如,加密库可能掩盖了它使用私有方法执行块加密的事实,该私有方法一次仅加密8个字节。我会为此编写一个单元测试-即使它被隐藏了,也并不意味着要改变,并且如果它确实崩溃了(例如,由于将来性能的提高),那么我想知道是私有函数崩溃了,而不仅仅是一项公共职能中断了。

稍后可以加快调试速度。

-亚当


1
在这种情况下,将私有方法移到另一个类,然后将其设为公共或公共静态不是很有意义吗?
Outlaw程序员

+1如果您不测试私有成员函数,而对公共接口的测试失败,那么您将得到的结果是,等同于某些东西被破坏了,却不知道这是什么东西。
Olumide

12

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



4
并非如此,您测试您的公共方法,一旦测试通过,便在执行“清理”步骤之前将公共方法中的代码提取到私有方法中。测试私有方法不是imo的一个好主意,因为它使更改实现方式变得更加困难(如果某天您想要更改操作方式,您应该能够更改它并运行所有测试,并且如果采用新的方法事情是正确的,他们应该通过,我不想为此更改所有私人测试)。
Tesseract

1
@Tesseract,如果我能多次投票支持您的评论,我会。“ ...您应该能够更改它并运行所有测试,如果您的新操作方法正确,则它们应该通过”。这是单元测试的主要优点之一。它们使您能够自信地进行重构。您可以完全更改类的内部私有工作,并且(无需重写所有单元测试)有信心您不会破坏任何内容,因为(在公共接口上)所有(现有)单元测试仍然可以通过。

不同意,请参阅下面的答案
Matt Messersmith,

11

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


然后在哪里测试实现?如果某些功能使用缓存,那么这是否是实现细节,并且未测试缓存?
德克·赫尔曼

11

我们通过推论来测试私有方法,这意味着我们寻求的总类测试覆盖率至少为95%,但只有我们的测试调用到公共或内部方法中。为了获得覆盖,我们需要根据可能发生的不同情况多次致电给公共/内部人员。这使我们的测试更加专注于测试代码的目的。

Trumpi对您所链接帖子的回答是最好的。


9

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


7

我一直在解决这个问题有一段时间了,尤其是尝试在TDD方面。

我遇到过两篇文章,我认为在TDD的情况下,可以彻底解决此问题。

  1. 测试私有方法,TDD和测试驱动的重构
  2. 测试驱动的开发不是测试

综上所述:

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

  • 从过程的本质上讲,从经过全面测试的功能中提取的任何简单实现功能都将对其进行自我测试(即间接测试覆盖率)。

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

因此,这些方法将是公开的,并且对其进行测试将非常容易。

一旦一切正常,私有方法将在稍后出现,为了便于阅读整洁,我们正在重构。


6

如上所述,“如果不测试私有方法,您怎么知道它们不会中断?”

这是一个主要问题。单元测试的重点之一就是要知道在哪里,何时以及如何尽快破坏某些内容。因此减少了大量的开发和质量检查工作。如果所有经过测试的都是公开的,那么您就不会对课程内部有诚实的报道和描述。

我发现最好的方法之一就是将测试引用添加到项目中,然后将测试放入与私有方法平行的类中。放入适当的构建逻辑,以使测试不会构建到最终项目中。

这样,您便具有测试这些方法的所有好处,并且可以在数秒而不是数分钟或数小时内发现问题。

综上所述,是的,对您的私有方法进行单元测试。


2
我不同意。“如果不测试您的私有方法,您怎么知道它们不会中断?” :我知道这是因为,如果我的私有方法被破坏,那么测试依赖于那些私有方法的公共方法的测试将失败。我不想每次改变主意如何实现公共方法时都必须更改测试。我还认为,单元测试的主要目的不是特别地了解哪一行代码是错误的,而是让您或多或少地确信自己在进行更改(对私有方法)时没有破坏任何内容。
Tesseract

6

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


3

如果您不测试自己的私有方法,您怎么知道它们不会中断?


19
通过编写测试您的公共方法。
scubabbl

3
那些私有方法据称是由该类的公共方法调用的。因此,只需测试调用私有方法的公共方法。
08年

1
如果您的公共方法运行正常,那么显然他们访问的私有方法运行正常。

如果您的公共方法的测试失败,您会立即知道对象/组件/等较低级别的东西不正确。
罗布

3
这是真的不错,但是,要知道,它是一个内部的功能,而不仅仅是一个爆发(或相反的是,内部功能都很好,你可以专注于外部)的外部函数。
亚当·戴维斯

2

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


5
朋友关键字让我难过。
罗布

如果测试类是在另一个项目中实现的,那么这不是问题。重要的是生产代码不引用测试类。
Olumide

2

我理解将私有方法视为实现细节,而不必进行测试的观点。如果我们只需要在对象之外进行开发,我会坚持这条规则。但是我们,我们是否是某种受限的开发人员,他们仅在对象外部进行开发,仅调用其公共方法?还是我们实际上也在开发该对象?由于我们不必对外部对象进行编程,因此我们可能必须将这些私有方法称为我们正在开发的新公共方法。知道私有方法可以抵抗一切困难,这不是很好吗?

我知道有人会回答,如果我们要在该对象中开发另一种公共方法,那么应该对此对象进行测试,就是这样(私有方法可以在未经测试的情况下进行)。但这对于对象的任何公共方法也是如此:开发Web应用程序时,对象的所有公共方法都是从controllers方法中调用的,因此可以视为控制器的实现细节。

那么为什么要对对象进行单元测试?因为这确实很困难,所以要确保我们正在使用适当的输入来测试控制器的方法,这将触发基础代码的所有分支,这并不是说不可能。换句话说,我们在堆栈中的位置越高,测试所有行为的难度就越大。私有方法也是如此。

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

  • 从不同地方多次调用该方法?
  • 该方法是否足够复杂以需要测试?

1

如果我发现私有方法庞大,复杂或重要到足以需要自己的测试,就将其放在另一个类中并在其中公开(方法对象)。然后,我可以轻松地测试以前存在于私有类中但现在私有的方法。


1

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

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

单元测试更多是数据透视测试 您标记了一些任意数据透视,并且数据透视的结果应保持不变。


1

公共与私有不是从测试中调用什么api的有用区别,方法与类也不是有用的区别。大多数可测试单元在一种情况下可见,而在其他情况下则隐藏。

重要的是覆盖范围和成本。您需要在实现项目的覆盖范围目标(行,分支,路径,块,方法,类,对等类,用例……无论团队决定如何)的同时最小化成本。

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

不要使测试变得不必要的昂贵。如果仅测试公共入口点最便宜,则可以这样做。如果测试私有方法最便宜,请执行此操作。

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


0

如果该方法足够重要/足够复杂,我通常会使其受到“保护”并进行测试。一些方法将被保留为私有方法,并作为公共/受保护方法的单元测试的一部分进行隐式测试。


1
@VisibleForTesting是对此的注释。我不会放松测试的封装,而是使用dp4j.com
simpatico

0

我看到许多人的想法一致:在公共级别进行测试。但是这不是我们的质量检查小组的工作吗?他们测试输入和预期输出。如果作为开发人员我们仅测试公共方法,那么我们只是在重做QA的工作,而不是通过“单元测试”增加任何价值。


当前的趋势是减少或没有质量检查团队。这些单元测试成为工程师每次在master分支上推送代码时都会运行的自动化测试。即使进行质量检查,他们也无法像自动化测试一样快速地测试整个应用程序。
Patrick Desjardins

0

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

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

这是一个例子:

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你必须更新重构,但你的对象的功能真的没有改变。因此,我们可以说事情比这更复杂,并且您有一些方法可以定义输出的顺序,例如:

def some_string_positioner
  if some case
  elsif other case
  elsif other case
  elsif other case
  else one more case
  end
end

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

最后,假设您的主要对象非常沉重,并且该方法非常小,您确实需要确保输出正确。您在想,“我必须测试这种私有方法!”。您是否可以通过传递一些繁重的工作作为初始化参数来减轻物体的重量?然后,您可以传递一些较轻的内容并进行测试。


0

否,您不应该测试私有方法,为什么?而且,流行的模拟框架(例如Mockito)不提供对测试私有方法的支持。


0

一个要点是

如果我们进行测试以确保逻辑的正确性,并且私有方法正在承载逻辑,则应该对其进行测试。是不是 那么为什么我们要跳过呢?

基于方法的可见性编写测试完全无关紧要。

反过来

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

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

可以解决的一项工作是(如果您要为私有方法编写测试用例)

将那些私有方法声明为受保护的。但这在某些情况下可能并不方便。


0

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

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

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

考虑一下出于性能原因的排序函数,如果存在多达10个元素,则使用BubbleSort的私有实现;如果存在10个以上的元素,则使用其他排序方法(例如堆排序)的私有实现。公共API是排序功能的API。但是,您的测试套件更好地利用了以下知识:实际上使用了两种排序算法。

当然,在此示例中,您可以在公共API上执行测试。但是,这将需要多个测试用例来执行具有超过10个元素的排序功能,以使堆排序算法得到充分的测试。仅存在这种测试用例就表明测试套件已连接到该功能的实现细节。

如果排序功能的实现细节发生变化,可能是通过改变两种排序算法之间的限制,或者是用mergesort或其他方式替换了heapsort:现有测试将继续起作用。但是,它们的价值仍然值得怀疑,并且可能需要对其进行重新设计以更好地测试更改后的排序功能。换句话说,尽管测试是在公共API上进行的,但仍需要进行维护。

B)如何测试实施细节

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

现在,假设接口背后的实现包含较大的私有部分,可以选择对内部接口进行单独测试。有人认为,这些部分在私有时不应该进行测试,而应该变成公开的东西。一旦公开,对该代码进行单元测试就可以了。

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

但是,这背后还有一些真相:当测试实现细节时,尤其是使用内部接口时,应该努力使用可能保持稳定的接口。但是,不能简单地根据是公共接口还是私有接口来确定某个接口是否可能稳定。在我从事过一段时间的世界项目中,公共接口也经常进行足够的更改,许多私有接口已经使用了很长时间。

不过,使用“前门优先”是一个很好的经验法则(请参阅http://xunitpatterns.com/Principles%20of%20Test%20Automation.html)。但是请记住,它被称为“前门优先”,而不是“仅前门”。

C)总结

还测试实施细节。最好在稳定的接口(公共或专用)上进行测试。如果实现细节发生变化,则还需要修改对公共API的测试。将某些东西私有化不会神奇地改变其稳定性。


0

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

请考虑以下略作设计的示例。假设我们要公开公开一个函数,该函数接受3个整数,并且仅当这3个整数都是质数时才返回true。我们可以这样实现:

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而不能进行isPrimeor andAll

作为测试者,我们可能会感兴趣的每个参数五种可能性:< 0= 0= 1prime > 1not prime > 1。但要彻底,我们还必须查看参数的每种组合如何一起发挥作用。因此5*5*5,根据直觉,这= 125个测试用例,我们需要彻底测试此功能。

另一方面,如果允许我们测试私有功能,则可以用更少的测试用例覆盖尽可能多的领域。我们只需要5个测试用例就可以测试isPrime到与之前的直觉相同的水平。根据丹尼尔·杰克逊(Daniel Jackson)提出的小范围假设,我们只需要对andAll函数进行最小长度的测试即可,例如3或4。最多可以进行16个测试。总共进行了21次测试。而不是125。当然,我们可能希望对进行一些测试allPrime,但是我们没有必要全面介绍我们关心的所有125种输入方案组合。只是一些快乐的道路。

当然,这是一个人为的例子,但是对于一个清晰的示范是必要的。而且模式扩展到了真正的软件。私有功能通常是最低级别的构建块,因此经常被组合在一起以产生更高级别的逻辑。在较高级别上,由于各种组合,我们对较低级别的内容有更多的重复。


首先,您不必使用已显示的纯函数来测试类似的组合。呼叫isPrime是真正独立的,因此盲目测试每个组合都是毫无目的的。其次,标记一个纯函数叫做isPrimeprivate违反了很多设计规则,我什至不知道从哪里开始。isPrime显然应该是公共职能。话虽这么说,但不管这个极差的例子,我都会明白你的意思。但是,它是在您进行组合测试的前提下构建的,而在真正的软件系统中,这通常不是一个好主意。
Matt Messersmith

马特,是的,这个例子并不理想,我会给你的。但是原理应该很明显。
Colm Bhandal,

前提并不完全是您要进行组合测试。如果您只限于测试难题的公共部分,那就必须这样做。在某些情况下,您希望将纯函数私有化以遵守正确的封装原则。而且此纯私有功能可以由公共功能使用。以组合的方式,也许还有其他纯私有功能。在那种情况下,遵循您不应该测试私有的原则,您将被迫对公共功能进行组合测试,而不是对私有组件进行模块化测试。
Colm Bhandal

0

您还可以将方法设置为package-private,即default,并且您应该能够对其进行单元测试,除非需要将其私有。

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.