He summarizes the ways to access private fields and methods of an object from a PHPUnit test case, and he concludes that being able to test them does not mean it is a good thing. Today I am going to explain why testing private methods explicitly is considered a bad practice.
Why do we limit the scope of a method to private or protected to begin with? To be able to subsequently refactor the code and change the signature or the behavior of the method accordingly to new requirements or what we have learned about the problem domain.
The same reasoning is even more valid for field references, which cannot encapsulate a computation and are simply stored variables. I haven't created public fields in production code for years.
Thus the first reason is: a test that exercises a private member couples the private member to code that is external to the system under test. This means you cannot safely delete or modify a private method simply by modifying a class source file: you will have to update the test even if the external behavior of the SUT did not change.
Sometimes you find a private method which contains purposeful logic, and you may want to cover it with specific testing. If this is the case, I suggest to move such method in an extracted class whose instance is stored as a private reference. This way you'll be able to test both the extracted class and the original one, maybe even mocking out the private method in the latter's unit tests. The abstraction provided by the original class is left untouched.
This solution is useful particularly when testing a private method becomes simpler than testing a public one of the same class; it is the sign that another interface is needed between the class and its private member. You're establishing a contract anyway, by writing a test for it: so why do not make this contract explicit and give it citizenship with a class?
The Single Responsibility Principle is the most abstract and the most overlooked of all object-oriented techniques.
Now let's tackle the same problem in the contest of Test-Driven Development. Remember what TDD means: writing code only to satisfy a unit test, while not being allowed to write more code than the really mandatory lines to get a green bar.
How do you get to write a method if you're doing TDD? There are two possible cases:
- the method is written to make a test pass directly; only public methods can serve this purpose if you do not let test code tinker with class internals.
- The method is written as a consequence of internal refactoring; but if you're refactoring, there are tests that cover the interested functionalities, so the method is tested anyway via public methods.
The moral of this analysis is: Test-Driven Development once again saves the day and makes sure our code is covered; unit testing becomes really hard only if you do not write tests first.