8

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

在我正在进行的测试中,我想确认将特定参数传递给对象会导致设置属性。我正在使用 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

4 回答 4

11

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

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

于 2011-02-20T00:28:06.553 回答
0

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

为确保正确编写您的私有(辅助)方法,只需使用代码覆盖分析器(请查看 PHP 的代码覆盖工具)并在您的测试用例中覆盖尽可能多的代码。

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

于 2011-02-19T10:40:25.400 回答
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指出的那样,测试私有方法/实现可能成为维护的噩梦——这可能意味着您正在做的工作应该外包给其他类。

于 2011-02-19T14:04:29.463 回答
0

一般来说,您的代码不应包含仅用于测试的功能。虽然测试私有/受保护方法是否是一种好的做法值得商榷,但我发现有时并想单独测试某个私有/受保护方法。


使用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'));
    }
}

希望这可以帮助。

于 2019-08-14T09:46:37.793 回答