For the last few days I’ve been using phpspec to build an application. Inevitably comparisons will be drawn between phpspec and PHPUnit, but I think that one clear distinction is found by considering how phpspec prevents the direct testing of a private method.
Testing a private method in PHPUnit is easy. Sebastian Bergmann provides a concise example using PHP’s reflection library. Conversely, phpspec prevents this. Because all the calls in your phpspec examples are proxied to an instance of the object under test, you are forced to interact with the object through its public interface.
So what’s wrong with exposing your privates?
This is an argument that I have at least once a month. There are several reasons that breaking encapsulation to facilitate unit testing is a bad idea. What follows is not a concise list, and I would love additional ammunition for future arguments in the comments. Counter arguments are also welcome!
Testing implementation details prevents effective refactoring
One of the biggest reasons to support your code with tests is to know when a change to the code has broken some functionality. This is especially true in the TDD paradigm of red, green, refactor. The problem is, if you have specific tests that target your implementation rather than just testing that your object delivers on the promises of its interface, then when you change an implementation detail you may also need to change your tests. For this reason, if you really must test a private method you should take care to keep those tests separate.
Code coverage reports are devalued
This is because by explicitly testing encapsulated code, you prevent the code coverage tool from proving that all the lines of code in an object are used by your public methods. Dead code may go unnoticed because you cover it by testing directly.
For me, code coverage is most useful when tests only penetrate an object though its public interface. If your code demonstrates all the requirements of the business and this is verified by tests, then unused code should be safe to delete.
Not SOLID code
Often an excuse for testing a private method is that it contains important business logic. In my experience this is a sign that the design is not quite right. Placing valuable logic in another object is likely to be a violation of the Single Responsibility Principle. If a piece of logic is mission critical, it should be represented by its own object, and an explicit part of your domain language. To do less is to fail to emphasise the importance of that logic in the application.
How do I know what’s broken?
In the article I linked to previously, Sebastian calls the act of testing private functionality though the public API “indirect testing”. He then says:
The problem with this approach is that when the test fails we do not know directly where the root cause for the failure is. It could be in either testDoSomething() or testDoSomethingPrivate(). This makes the test less valuable.
I do not fully agree with Sebastian on this point, and he does also go on to say that he agrees with Dave Thomas and Andy Hunt that:
In general, you don’t want to break any encapsulation for the sake of testing … If there is significant functionality that is hidden behind private or protected access, that might be a warning sign that there’s another class in there struggling to get out.
Although we do not immediately know the root cause of the failed test, the code under test should not be so complicated as to make locating the cause of a failure an issue. In my opinion, what makes an indirect test less valuable is when it covers a large and/or complex implementation. Rather than breaking encapsulation, take the hint that the code needs refactoring into smaller, less complex objects. And guess what…you’ll need tests to perform that work safely!
Is it possible to test privates with phpspec?
No, not directly. phpspec is about defining the behaviour of your objects, not testing their implementation. That an implementation can change completely without affecting your specs at all is a great strength of testing, and phpspec helps you achieve that. If someone was so inclined they could hack reflection into phpspec to make it interact directly with private methods and properties, but really, why would you?