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

时间:2011-02-19 09:50:04

标签: php unit-testing simpletest

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

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

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

然后我在CVObject中写了一个__get方法,如下所示:

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,请继续使该属性可用于测试目的,但如果没有,则触发错误。这允许我保持属性私有但能够在测试中使用它,这对我来说对于我将要开始编写的特定私有方法更为重要。

所以,我的问题是,我在这里错过了一些非常糟糕的东西,应该避免使用这种技术吗?

4 个答案:

答案 0 :(得分:10)

如果您发现自己陷入困境,只需必须访问私有/受保护的属性以启用全面测试,至少可以在您的测试或测试框架中放置支持访问的代码。在生产代码中嵌入仅测试代码a)使设计复杂化,b)添加必须测试的更多代码,c)意味着代码在生产中运行不同。

您可以将Ken的子类方法用于受保护的属性,但如果您需要访问private并且在PHP 5.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()不能像从超类继承的属性那样工作,但是循环层次结构以找到声明类并不太难。

答案 1 :(得分:1)

在测试组件时,你必须只测试它的接口(输入,输出,异常),而不考虑甚至不知道它的内部实现(如果一个程序员编写测试用例而另一个程序员执行实现,则更好,请参考XP和TDD技术)。因此,您唯一需要测试的是公共方法。

为确保您的私有(帮助)方法正确编写,只需使用代码覆盖率分析器(请结账Code Coverage tools for PHP)并尽可能多地使用您的测试用例。

您的解决方案将保证您的维护噩梦。测试用例和组件实现不应该以任何方式耦合,因为耦合需要是防弹的,否则你也必须测试它。

答案 2 :(得分:0)

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

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
}

但是正如ljank指出的那样,测试私有方法/实现可能会成为维护的噩梦 - 这可能意味着你正在做的工作应该被培养到其他类。

答案 3 :(得分:0)

通常来说,您的代码不应包含仅用于测试的功能。尽管测试私有/受保护方法是否是一个好习惯值得商bat,但我发现有时候还是想单独测试某个私有/受保护方法。


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

composer require netsilik/base-test-case


要测试的课程:

<?php
namespace App;

class Foo
{
    private $bar;

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

单元测试:     

class MyTestCase extends \Netsilik\Testing\BaseTestCase
{
    public function test_whenProtectedMethodCalled_thenPrivateMemberSet()
    {
        $foo = new Foo();

        self::callInaccessibleMethod($foo, 'setBar', 'abc');

        self::assertEquals('abc', self::getInaccessibleProperty($foo, 'bar'));
    }
}

希望这会有所帮助。