单元测试私有属性和方法的PHP重载

单元测试私有属性和方法的PHP重载,php,unit-testing,simpletest,Php,Unit Testing,Simpletest,我在这里读了一些关于使用单元测试来测试私有方法和属性的问题。我是单元测试新手,希望输入我正在尝试的方法,以便我的测试能够访问私有/受保护的属性和方法 在我正在进行的测试中,我想确认将特定参数传递给对象会导致设置属性。我正在使用SimpleTest进行单元测试教育,我的测试方法如下: function test__Construction_Should_Properly_Set_Tables() { $cv = new CVObject( array( 'tables' => $this

我在这里读了一些关于使用单元测试来测试私有方法和属性的问题。我是单元测试新手,希望输入我正在尝试的方法,以便我的测试能够访问私有/受保护的属性和方法

在我正在进行的测试中,我想确认将特定参数传递给对象会导致设置属性。我正在使用SimpleTest进行单元测试教育,我的测试方法如下:

function test__Construction_Should_Properly_Set_Tables() {
  $cv = new CVObject( array( 'tables' => $this->standardTableDef ) );
  $tables = $cv->tables;
  $this->assertEqual( $tables, $this->standardTableDef );
}
function __get( $name ) {
  $trace = debug_backtrace();
  $caller = $trace[1];
  $inTesting = preg_match( '/simpletest/', $caller['file'] );

  if ( $inTesting ) {
    return $this->$name;
  } else {
    trigger_error( 'Cannot access protected property CVObject::$' .
                     $name . ' in ' . $trace[0]['file'] . ' on line ' .
                     $trace[0]['line'],
                    E_USER_NOTICE );
  }
}
然后我在CVObject中编写了一个_uget方法,如下所示:

function test__Construction_Should_Properly_Set_Tables() {
  $cv = new CVObject( array( 'tables' => $this->standardTableDef ) );
  $tables = $cv->tables;
  $this->assertEqual( $tables, $this->standardTableDef );
}
function __get( $name ) {
  $trace = debug_backtrace();
  $caller = $trace[1];
  $inTesting = preg_match( '/simpletest/', $caller['file'] );

  if ( $inTesting ) {
    return $this->$name;
  } else {
    trigger_error( 'Cannot access protected property CVObject::$' .
                     $name . ' in ' . $trace[0]['file'] . ' on line ' .
                     $trace[0]['line'],
                    E_USER_NOTICE );
  }
}
我的想法是,如果调用文件来自SimpleTest,则继续并使该属性可用于测试目的,如果不是,则触发错误。这允许我保持属性的私有性,但能够在测试中使用它,这对于我将要开始编写的特定私有方法来说更为重要


所以,我的问题是,我是否遗漏了一些非常糟糕的东西,应该避免这种技术?

在测试组件时,您必须只测试它的接口(输入、输出、异常),而不考虑甚至不知道它的内部实现(如果一个程序员编写测试用例,另一个编写实现,那就更好了,请参考XP和TDD技术)

为了确保您的私有(助手)方法编写正确,只需使用代码覆盖率分析器(请签出)并在测试用例中覆盖尽可能多的代码


您的解决方案将为您带来维护噩梦。测试用例和组件实现不应以任何方式耦合,因为耦合需要防弹,否则您也必须对其进行测试。

一个快速而肮脏的解决方案是使用受保护的(而不是私有的)方法,然后使用使被测方法公开的包装器进行测试

class Foo
{
    protected function bar(){} // should really be private but protected will do
}

class FooTestWrapper extends Foo
{
    public function bar{} { return parent::bar(); } // this is testable
}

但正如所指出的,测试私有方法/实现可能会成为维护的噩梦——这可能意味着你正在做的工作应该外包给其他类。

如果你发现自己陷入困境,必须访问私有/受保护的属性才能进行彻底的测试,那么至少在测试中放置允许访问的代码在生产代码中嵌入只测试的代码a)使设计复杂化,b)添加更多必须测试的代码,c)意味着代码在生产中的运行方式不同

对于受保护的属性,您可以使用Ken的子类方法,但是如果您需要访问private并且使用PHP5.3.2+,则可以使用反射

function test__Construction_Should_Properly_Set_Tables() {
    $cv = new CVObject( array( 'tables' => $this->standardTableDef ) );
    $tables = self::getPrivate($cv, 'tables');
    $this->assertEqual( $tables, $this->standardTableDef );
}

static function getPrivate($object, $property) {
    $reflector = new ReflectionProperty(get_class($object), $property);
    $reflector->setAccessible(true);
    return $reflector->getValue($object);
}

请注意,
getPrivate()
不会像编写从超类继承的属性那样工作,但是循环层次结构以查找声明类并不太困难。

一般来说,您的代码不应该包含仅用于测试的功能。虽然测试私有/受保护的方法是否是一种好的实践还存在争议,但我发现有时需要单独测试某个私有/受保护的方法


使用(MIT许可证),您可以设置/获取私有变量并调用私有或受保护函数:

composer需要netsilik/base测试用例


要测试的类:

<?php
namespace App;

class Foo
{
    private $bar;

    protected function setBar(string $bar)
    {
        $this->bar = $bar;
    }
}

希望这能有所帮助。

顺便说一句,SimpleTest不再处于开发阶段。是一个行业标准的xUnit测试框架,它包括代码覆盖率和要引导的模拟对象。另外,
debug\u backtrace()
不是一个便宜的调用,应该在生产代码中避免。