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


当前回答

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

考虑一下下面略显做作的例子。假设我们想公开一个函数,该函数接受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,而不允许测试isPrime或andAll。

作为测试人员,我们可能对每个参数的五种可能性感兴趣:< 0,= 0,= 1,质数> 1,而不是质数> 1。但为了彻底,我们还必须看看每个参数的组合是如何发挥作用的。根据我们的直觉,我们需要5*5*5 = 125个测试用例来彻底测试这个函数。

On the other hand, if we were allowed to test the private functions, we could cover as much ground with fewer test cases. We'd need only 5 test cases to test isPrime to the same level as our previous intuition. And by the small scope hypothesis proposed by Daniel Jackson, we'd only need to test the andAll function up to a small length e.g. 3 or 4. Which would be at most 16 more tests. So 21 tests in total. Instead of 125. Of course, we probably would want to run a few tests on allPrime, but we wouldn't feel so obliged to cover exhaustively all 125 combinations of input scenarios we said we cared about. Just a few happy paths.

当然,这是一个虚构的例子,但为了清晰地演示,这是必要的。这种模式可以扩展到真实的软件中。私有函数通常是最低级别的构建块,因此经常组合在一起以产生更高级别的逻辑。也就是说,在较高的层次上,由于不同的组合,我们对较低层次的东西有更多的重复。

其他回答

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

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

如果私有方法定义良好(即,它有一个可测试的函数,并且不会随着时间的推移而改变),那么是的。我在有意义的地方测试所有可测试的东西。

例如,加密库可能会隐藏这样一个事实,即它使用一次仅加密8个字节的私有方法执行块加密。我会为此写一个单元测试——它并不意味着要改变,即使它是隐藏的,如果它真的坏了(例如,由于未来的性能增强),那么我想知道是私有函数坏了,而不仅仅是某个公共函数坏了。

它加快了以后的调试。

亚当

是的,我确实测试私有函数,因为尽管它们是由你的公共方法测试的,但在TDD(测试驱动设计)中测试应用程序的最小部分是很好的。但是在测试单元类中不能访问私有函数。下面是我们测试私有方法的方法。

为什么我们有私有方法?

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

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

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

1. 你可以通过你的公共方法来测试你的私处

优势

简单的单元测试(不需要“hack”)

缺点

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

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

优势

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

缺点

如果一个类是不需要的,并且只被 方法来自的类 由于增加的开销而造成潜在的性能损失

3.将访问修饰符更改为(final) protected

优势

您正在测试应用程序中最小的可测试部分。当 使用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来测试之前的私有函数。

我已经为这个问题苦恼了一段时间,尤其是在尝试TDD的时候。

我曾经看到过两篇文章,我认为它们在TDD的情况下已经足够彻底地解决了这个问题。

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


总而言之:

当使用测试驱动开发(设计)技术时,私有方法应该只在重构已经工作和测试过的代码的过程中出现。 根据这个过程的本质,任何从经过彻底测试的功能中提取出来的简单实现功能都将是自我测试的(即间接测试覆盖率)。

对我来说,在编码的开始部分,大多数方法都是高级函数,因为它们封装/描述了设计。

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

私有方法将在一切正常工作后出现,并且为了可读性和清洁度我们正在进行重构。