我应该测试私有方法还是仅测试公共方法?

时间:2008-09-19 19:56:21

标签: unit-testing testing language-agnostic

我已阅读this post有关如何测试私有方法的信息。我通常不测试它们,因为我一直认为只测试从对象外部调用的公共方法会更快。你测试私人方法吗?我应该经常测试它们吗?

30 个答案:

答案 0 :(得分:299)

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

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

答案 1 :(得分:278)

测试的目的是什么?

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

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

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

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

答案 2 :(得分:139)

我倾向于遵循Dave Thomas和Andy Hunt的建议实用单位测试

  

一般情况下,您不希望破坏任何封装   测试(或者像妈妈常说的那样,“不要暴露你的私人!”)。最   当时,你应该能够通过锻炼来测试课程   公共方法。如果隐藏了重要功能   在私人或受保护的访问后面,这可能是一个警告信号   还有另一个班级在努力摆脱。

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

答案 3 :(得分:58)

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

  

每个功能cyclomatic complexity不超过10个。

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

这实际上很酷,因为callstack现在更容易阅读(而不是大型函数中的bug,我在子子函数中有一个错误,在callstack中有以前函数的名称来帮助我明白'我怎么到那里')

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

只需2美分。

答案 4 :(得分:50)

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

为什么我们有私人方法?

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

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

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

<强> 1。您可以通过公共方法测试您的私人资料

优点

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

缺点

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

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

优点

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

缺点

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

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

优点

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

缺点

  • 您正在更改对受保护的私人访问权限,这意味着它是 可以通过它的孩子们访问
  • 您仍需要在测试类中使用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来测试我们以前的私有函数。

答案 5 :(得分:24)

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

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

答案 6 :(得分:19)

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

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

稍后加快调试速度。

- 亚当

答案 7 :(得分:17)

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

答案 8 :(得分:12)

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

答案 9 :(得分:11)

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

Trumpi对你所关联的帖子的回答是最好的。

答案 10 :(得分:10)

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

答案 11 :(得分:9)

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

答案 12 :(得分:7)

我已经在这个问题上熬了一段时间,特别是在TDD上尝试一下。

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

  1. Testing private methods, TDD and Test-Driven Refactoring
  2. Test-Driven Development Isn’t Testing

  3. 摘要:

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

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

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

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

    一旦一切运行良好,私有方法将会出现,我们正在考虑可读性清洁度

答案 13 :(得分:6)

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

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

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

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

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

答案 14 :(得分:5)

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

答案 15 :(得分:3)

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

答案 16 :(得分:2)

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

答案 17 :(得分:2)

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

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

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

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

  • 是从不同地方多次调用的方法吗?
  • 是否足够复杂,需要进行测试?

答案 18 :(得分:1)

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

答案 19 :(得分:0)

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

答案 20 :(得分:0)

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

答案 21 :(得分:0)

一个要点是

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

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

<强>相反

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

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

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

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

答案 22 :(得分:0)

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

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

以下是一个例子:

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

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

最后,让我们说你的主要对象超重,方法很小,你真的需要确保输出是正确的。你在想,&#34;我必须测试这种私人方法!&#34;。也许你可以通过传递一些繁重的工作作为初始化参数来使你的对象更轻?然后你可以传递更轻松的东西并对其进行测试。

答案 23 :(得分:0)

不,你不应该测试私有方法why?,而且像Mockito这样流行的模拟框架并不能为测试私有方法提供支持。

答案 24 :(得分:0)

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

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

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

答案 25 :(得分:0)

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

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

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

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

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

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

B)如何测试实施细节

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

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

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

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

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

C)摘要

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

答案 26 :(得分:0)

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

请考虑以下稍作设计的示例。假设我们要公开一个接受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而不是isPrimeandAll

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

另一方面,如果允许我们测试私有功能,我们可以用更少的测试用例覆盖尽可能多的领域。我们只需要5个测试用例就可以将isPrime测试到与之前的直觉相同的水平。根据丹尼尔·杰克逊(Daniel Jackson)提出的small scope hypothesis,我们只需要测试andAll函数的最小长度,例如3或4。最多可以进行16个测试。总共进行了21次测试。而不是125。当然,我们可能想对allPrime进行少量测试,但是我们没有必要如此详尽地涵盖我们说过的所有125种输入方案组合关心。几条快乐的路。

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

答案 27 :(得分:0)

您还可以将您的方法设置为包私有(即默认),并且您应该能够对其进行单元测试,除非要求将其私有。

答案 28 :(得分:0)

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

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

因此,请使用工具来确保覆盖范围,并设计测试以使成本最低(短期和长期)。有时,您只需测试整个应用程序的主要入口点就可以轻松获得所需的所有覆盖范围。通常,您将通过在各个内部入口点进行测试来降低实现覆盖的成本。这个概念并不止于方法的可见性。

通常,良好的模块设计使对声明为public的入口点进行测试的成本最低,但这不能保证。

私有与公共无关紧要。

不要通过直接测试所有私有方法来使测试变得更加昂贵,也不要通过避免直接调用某些方法来使测试变得更加昂贵。

答案 29 :(得分:-1)

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