DEV Community

Frédéric Bouchery
Frédéric Bouchery

Posted on

Chat: Unit test of private methods

(Traduction française: Chat : Test unitaire sur des méthodes privées)

I present to you here the conversation I had with ChatGPT (version 4) regarding unit testing of private methods, as I found the exercise interesting.
The text in bold was added by me afterward to add some emphasis.

A sentence that I really like:

The goal of unit tests is to make sure each part of your code works as expected, not to ensure each method passes a test

Human debating with a robot


Fred

I want to setup unit tests with PHPUnit. In PHP, what do you think about testing private methods?

ChatGPT

Typically, it is not recommended to directly test private methods in PHPUnit. Private methods are considered implementation details of the class and should only be indirectly tested through the public methods that use them.
If you feel the need to test a private method directly, it could indicate a design problem. Maybe the class is doing too much and could be refactored to move this functionality to a new class where it would be public and could be tested directly.
However, if you absolutely must test a private method for some reason, you can use reflection in PHP to make a private method accessible and then test it. But again, this should be an exception rather than the rule in most cases.

Fred

There's a portion of the algorithmic code that I would like to validate, but I don't want it to be accessible from the outside. If I place this code in a separate class, it would be like making it accessible, even though it has no advantages outside the context of my class. The tests are only for the public methods, forcing me to build mock objects and making the tests complex. If I test my private method, I will have very simple tests.

ChatGPT

In this case, if you think that testing your private methods directly brings you value and improves the quality of your code, then you should do it.
After all, the goal of unit tests is to ensure that your code does what it's supposed to do and help you identify and fix bugs. If testing a private method directly helps you achieve these goals, then there is no reason not to do it.
As mentioned before, you can use reflection to access private methods and test them. Here is an example of how you can do it in PHP:

$className = new YourClass();
$reflector = new ReflectionClass($className);
$method = $reflector->getMethod('yourPrivateMethod');
$method->setAccessible(true);
$result = $method->invokeArgs($className, [$arg1, $arg2]);
// now you can make assertions on $result

However, this can make your tests a bit more complex and harder to maintain. You should also be aware that this is a kind of "hack" and there's a reason why private methods are not normally accessible - it's about maintaining encapsulation and hiding implementation details which could change. By directly testing private methods, you're coupling your tests to these specific implementation details and any changes to these methods could require a rewrite of your tests.
Ultimately, it's up to you to decide if the benefits outweigh the drawbacks in your specific case.

Fred

A good solution would be to have a concept of friend classes in PHP, but it doesn't exist.

ChatGPT

Indeed, PHP does not have a concept of "friend classes" as C++ does. In C++, a friend class can access private and protected methods of the class that declares it as a friend, which could be used to test these methods.
Without this feature in PHP, you have to find other ways to test your private methods if you decide that's necessary. That's why some people choose reflection despite its drawbacks.
If you have a case where you have complex private methods you want to test directly, another option is to make them public just for testing purposes and clearly document that they are intended for testing only. This is far from ideal but it's another option to consider.
In the end, it's important to remember that the goal of unit tests is to make sure each part of your code works as expected, not to ensure each method passes a test. If you can test the behavior of your class in a way that makes sense without needing to test every private method, that might be the most efficient approach.

Fred

I wonder if protected methods wouldn't make more sense.

ChatGPT

Using protected methods in PHP can indeed be an alternative for testability reasons. Protected methods can be called by the class itself, and by all the classes that inherit from it.
So in cases of algorithmic complexity that should not be publicly exposed while wishing to be able to unit test it, creating a subclass just for testing to test these specific algorithms could be a solution.
This is what it could look like:

class MyClass {
    protected function methodToTest($input) {
        // Your complex algorithm here
    }
}
class MyClassTestVersion extends MyClass {
    public function methodToTestWrapper($input) {
        return $this->methodToTest($input);
    }
}

And this is how you could use it in your PHPUnit tests:

public function testMethodToTest() {
    $testObject = new MyClassTestVersion();
    $result = $testObject->methodToTestWrapper('input');
    // Your assertions here
}

This is a way to access the method without exposing it as public and without using PHP reflection. However, this solution has its own limitations and drawbacks. One of them is that if the protected methods are too many, it can burden the test class and make it more difficult to manage. Furthermore, if the logic of protected methods changes, the tests must be adjusted in the same way. And if a private method is used by several protected methods, then it will be tested repeatedly, which makes the tests less effective. The appropriate balance therefore depends on each particular case.

Fred

A protected method should not change because it's a contract with the classes that inherit from it, and testing them makes a lot of sense, in fact.

ChatGPT

Absolutely, you're right. Protected methods are indeed part of the contract of the class with its subclasses, just like public methods are a contract with any external code that uses the class.
Testing protected methods, therefore, makes sense, as it ensures that the class fulfills its contract even as it evolves. Strictly speaking, it's even necessary when you follow a Test-Driven Development (TDD) methodology where every part of the code must be covered by tests.
It's important to keep this contract as stable as possible so as not to break the subclasses that depend on her methods. If you need to change the behavior of these methods, it's important to do so in a way that doesn't break this contract. This is where unit testing of the protected methods can be very useful - to verify that you haven't introduced regressions when modifying these methods.
So it's justified to test your protected methods if it matches your logic of code organization and your testing strategy. However, bear in mind that it is always beneficial to work on decoupling and simplifying your classes, avoiding as much as possible a great number of protected methods, in accordance with the principles of SOLID and object-oriented development.

Fred

On private methods, a developer can easily tell himself: "I can change this method, or even delete it, it's not a big deal", it's private. But by doing that, it's going to break my tests and maybe that will lead him to notice certain cases of use that he had not identified. Maybe an annotation on my method would warn the developer that there are associated tests before he finds out later. An "@see" annotation for example.

ChatGPT

Your suggestion to use an "@see" annotation is an excellent idea to signal that a private method has associated tests. This can help reinforce communication within the development team and make someone who modifies this method understand that they need to also check and potentially modify the associated tests.
This could look like this in your code:

/**
 * @see \Tests\Unit\YourClassTest::testYourPrivateMethod
 */
private function yourPrivateMethod()
{
    // code
}

However, note that this annotation must be maintained: if the test is renamed or moved, the annotation must be updated.
It's also important to note that good test coverage of public methods should also catch cases where a modification of a private method leads to errors.
In any case, the decision to test or not test private methods should be made based on the specific context of the project, the team, and the code itself. The idea is to test what is necessary to have confidence in the correct functioning of your system, while balancing this effort with development time and code maintainability.

Fred

Yes, I agree, this should remain marginal, and prioritize tests of public methods. I was wondering about this practice to simplify tests and possibly facilitate the addition of tests on poorly designed legacy code. I prefer tests on private methods than no test at all.

ChatGPT

Quite right, I agree with you and you have very good thinking on this subject.
Indeed, when working with legacy code, you don't always have the opportunity to refactor everything you would like. It is therefore crucial to have tests to ensure the proper functioning of your application, even if it means testing private methods.
And as you rightly said, in certain cases the test of a private method can be less complex and quicker to set up than the testing of the public method that uses it, notably if it avoids having to mock numerous objects.
In summary, testing private methods can make sense in certain situations, notably in the framework of legacy code and when this allows the tests to be simpler and more reliable. It is a practice to use sparingly and discerningly, keeping in mind the main objective of unit tests: to ensure the reliability and quality of your code.

Top comments (0)