开发者

PHP Overloading to Unit Test Private Properties and Methods

I've been reading here a few questions regarding the use of unit testing to test private methods and properties. I'm new to unit testing and would like input on the method I'm trying so that my testing can access private/protected properties and methods.

In the test I was working on I wanted to confirm that passing a particular parameter to the object resulted in a property being set. I'm using SimpleTest for my unit testing education and my test method is as follows:

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

Then I wrote a __get method in CVObject as follows:

function __get( $name ) {
  $trace = debug_backtrace();
  $caller = $trace[1];
  $inTesting = preg_m开发者_如何学运维atch( '/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 );
  }
}

My idea in this is that if the calling file is from SimpleTest, go ahead and make the property available for the testing purposes, but if not, trigger the error. This allows me to keep the property private but be able to use it in testing, which is going to be more important to me with a particular private method I'm about to begin writing.

So, my question is, am I missing something really bad here and should avoid this technique?


If you find yourself stuck and simply must access a private/protected property to enable thorough testing, at least place the code that enables access in your test or testing framework. Embedding testing-only code in production code a) complicates the design, b) adds more code that must be tested, and c) means the code runs differently in production.

You can use Ken's subclass method for protected properties, but if you need to access private and are on PHP 5.3.2+ you can use reflection.

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);
}

Note that getPrivate() won't work as written for properties inherited from superclasses, but it's not too hard to loop up the hierarchy to find the declaring class.


While testing a component, you have to test only it's interface (input, output, exceptions), without considering or even knowing it's internal implementation (even better if one programmer writes test cases and the other does the implementation, please refer to XP and TDD techniques). So, the only thing you have to test are public methods.

To ensure, that your private (helper) methods are written correctly, simply use code coverage analyzer (please checkout Code Coverage tools for PHP) and cover as much code as possible with your test cases.

Your solution will guarantee you a maintenance nightmare. Test cases and component implementation should not be coupled in any way, because coupling would need to be bulletproof or otherwise you'll have to test it too.


A quick and dirty solution is to use protected (instead of private) methods, and then test using a wrapper that makes the methods under test public.

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
}

But as ljank points out, testing private methods/implementation can become a maintenance nightmare - it probably means you are doing work that should be farmed out to other classes.


Generally speaking, your code should not contain features just for testing. And while it is debatable if testing private/protected methods is good practice, I find that sometimes and want to test a certain private/protected method in isolation.


Using Netsilik/BaseTestCase (MIT License) you can set/get private variable and call private or protected functions:

composer require netsilik/base-test-case


Class to test:

<?php
namespace App;

class Foo
{
    private $bar;

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

Unit test:

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

Hope this helps.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜