views:

3355

answers:

7

Hello,

I found the discussion on Do you test private method informative.

I have decided, that in some classes, I want to have protected methods, but test them. Some of these methods are static and short. Because most of the public methods make use of them, I will probably be able to safely remove the tests later. But for starting with a TDD approach and avoid debugging, I really want to test them.

I thought of the following:

  • Method Object as adviced in an answer seems to be overkill for this.
  • Start with public methods and when code coverage is given by higher level tests, turn them protected and remove the tests.
  • Inherit a class with a testable interface making protected methods public

Which is best practice? Is there anything else?

It seems, that JUnit automatically changes protected methods to be public, but I did not have a deeper look at it. PHP does not allow this via reflection.

A: 

I'm just throwing ideas in the air, so I hope nobody gets upset if my idea doesn't work.

But. You could try if a __call()-hack would work, assuming you are using PHP5 or later. My hypothesis is that if you call a private method, PHP just hides it and doesn't call it (instead of explicitly saying that you don't have access to that method). Therefore, your __call() magic method would catch that call, and try to call it internally. You could then have a constant to turn toggle between "unit test mode" and normal mode.

As said, not at all sure it would work, but it might be worth investigating.

Update: This doesn't work! As GrGr noted (and I now confirm, as I had time to test this) that calling a private method indeed causes a fatal error.

Henrik Paul
No, this does not work. Calling an existing protected method causes a fatal error.
GrGr
+11  A: 

You seem to be aware already, but I'll just restate it anyway; It's a bad sign, if you need to test protected methods. The aim of a unit test, is to test the interface of a class, and protected methods are implementation details. That said, there are cases where it makes sense. If you use inheritance, you can see a superclass as providing an interface for the subclass. So here, you would have to test the protected method (But never a private one). The solution to this, is to create a subclass for testing purpose, and use this to expose the methods. Eg.:

class Foo {
  protected function stuff() {
    // secret stuff, you want to test
  }
}

class SubFoo extends Foo {
  public function exposedStuff() {
    return $this->stuff();
  }
}

Note that you can always replace inheritance with composition. When testing code, it's usually a lot easier to deal with code that uses this pattern, so you may want to consider that option.

troelskn
You can just directly implement stuff() as public and return parent::stuff(). See my response. It seems I'm reading things too quickly today.
Michael Johnson
You're right; It's valid to change a protected method into a public one.
troelskn
So the code suggests my third option and "Note that you can always replace inheritance with composition." goes in the direction of my first option or http://www.refactoring.com/catalog/replaceInheritanceWithDelegation.html
GrGr
Yes to the first. How finely grained your objects should be is a matter of style. I generally create much smaller objects than most of my colleagues would.
troelskn
I don't agree that it is a bad sign. Let's make a difference between TDD and Unit Testing. Unit testing should test private methods imo, since these are units and would benefit just in the same way as unit testing public methods benefit from unit testing.
koen
+2  A: 

I think troelskn is close. I would do this instead:

class ClassToTest
{
   protected testThisMethod()
   {
     // Implement stuff here
   }
}

Then, implement something like this:

class TestClassToTest extends ClassToTest
{
  public testThisMethod()
  {
    return parent::testThisMethod();
  }
}

You then run your tests against TestClassToTest.

It should be possible to automatically generate such extension classes by parsing the code. I wouldn't be surprised if PHPUnit already offers such a mechanism (though I haven't checked).

Michael Johnson
Heh... it seems I'm saying, use your third option :)
Michael Johnson
Yes, that is exactly my third option. I am pretty sure, that PHPUnit does not offer such a mechanism.
GrGr
+2  A: 

I suggest following workaround for "Henrik Paul"'s workaround/idea :)

You know names of private methods of your class. For example they are like _add(), _edit(), _delete() etc.

Hence when you want to test it from aspect of unit-testing, just call private methods by prefixing and/or suffixing some common word (for example _addPhpunit) so that when __call() method is called (since method _addPhpunit() doesn't exist) of owner class, you just put necessary code in __call() method to remove prefixed/suffixed word/s (Phpunit) and then to call that deduced private method from there. This is another good use of magic methods.

Try it out.

Anirudh Zala
A: 

You can indeed use __call() in a generic fashion to access protected methods. To be able to test this class

class Example {
    protected getMessage() {
        return 'hello';
    }
}

you create a subclass in ExampleTest.php:

class ExampleExposed extends Example {
    public function __call($method, array $args = array()) {
        if (!method_exists($this, $method))
            throw new BadMethodCallException("method '$method' does not exist");
        return call_user_method_array($method, $this, $args);
    }
}

Note that the __call() method does not reference the class in any way so you can copy the above for each class with protected methods you want to test and just change the class declaration. You may be able to place this function in a common base class, but I haven't tried it.

Now the test case itself only differs in where you construct the object to be tested, swapping in ExampleExposed for Example.

class ExampleTest extends PHPUnit_Framework_TestCase {
    function testGetMessage() {
        $fixture = new ExampleExposed();
        self::assertEquals('hello', $fixture->getMessage());
    }
}

I believe PHP 5.3 allows you to use reflection to change the accessibility of methods directly, but I assume you'd have to do so for each method individually.

David Harkness
The __call() implementation works great! I tried to vote up, but I unset my vote until after I tested this method and now I'm not allowed to vote due to a time limit in SO.
Adam Franco
+13  A: 

If you're using PHP5 (>= 5.3.2) with PHPUnit, you can test your private and protected methods by using reflection to set them to be public prior to running your tests:

protected static function getMethod($name) {
  $class = new ReflectionClass('MyClass');
  $method = $class->getMethod($name);
  $method->setAccessible(true);
  return $method;
}

public function testFoo() {
  $foo = self::getMethod('foo');
  $obj = new MyClass();
  $foo->invokeArgs($obj, array(...));
  ...
}
uckelman
Simple and elegant, I like it.
Drew
Nice solution; no need to change the code that is being tested, I like it.
bouke
Very nice solution. Might want to add though that this is php >= 5.3.2 only ;)
flungabunga
A: 

I'm going to throw my hat into the ring here:

I've used the __call hack with mixed degrees of success. The alternative I came up with was to use the Visitor pattern:

1: generate a stdClass or custom class (to enforce type)

2: prime that with the required method and arguments

3: ensure that your SUT has an acceptVisitor method which will execute the method with the arguments specified in the visiting class

4: inject it into the class you wish to test

5: SUT injects the result of operation into the visitor

6: apply your test conditions to the Visitor's result attribute

sunwukung