【问题标题】:PHP Overloading to Unit Test Private Properties and Methods用于单元测试私有属性和方法的 PHP 重载
【发布时间】:2011-06-30 07:42:24
【问题描述】:

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

在我正在进行的测试中,我想确认将特定参数传递给对象会导致设置属性。我正在使用 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,请继续并使该属性可用于测试目的,但如果不是,则触发错误。这使我可以将属性保持为私有,但可以在测试中使用它,这对于我即将开始编写的特定私有方法而言将变得更加重要。

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

【问题讨论】:

  • 顺便说一句,SimpleTest 不再处于开发阶段。 PHPUnit 是一个行业标准的 xUnit 测试框架,它包括代码覆盖率和要启动的模拟对象。此外,debug_backtrace() 的调用并不便宜,应该在生产代码中避免使用。

标签: php unit-testing simpletest


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

    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】:

      如果您发现自己陷入困境并且只是必须访问私有/受保护的属性以启用全面测试,至少将启用访问的代码放在您的测试或测试框架中。在生产代码中嵌入仅测试代码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() 不会像为从超类继承的属性所写的那样工作,但循环层次结构以查找声明类并不难。

      【讨论】:

        【解决方案4】:

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


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

        希望这会有所帮助。

        【讨论】:

          猜你喜欢
          • 2015-08-14
          • 2014-08-10
          • 2018-01-15
          • 2012-04-07
          • 1970-01-01
          • 1970-01-01
          • 2013-08-16
          相关资源
          最近更新 更多