Logo

dev-resources.site

for different kinds of informations.

Chat: Unit test of private methods

Published at
10/26/2023
Categories
php
phpunit
unittest
chatgpt
Author
fredbouchery
Categories
4 categories in total
php
open
phpunit
open
unittest
open
chatgpt
open
Author
12 person written this
fredbouchery
open
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.

phpunit Article's
30 articles in total
Favicon
6 Steps to Master PHPUnit Testing with Ease!
Favicon
Mastering Unit Testing in PHP: Tools, Frameworks, and Best Practices
Favicon
PHP: Should I mock or should I go?
Favicon
Focusing your tests on the domain. A PHPUnit example
Favicon
Understanding Mock Objects in PHPUnit Testing
Favicon
Wrote a book (And it is not about coding, and yeap it is in Greek)
Favicon
How to run a phpunit unit test with a specific dataset
Favicon
Test Your DOM in Laravel with PHPUnit
Favicon
PHP: Mocking Closures and performing assertions
Favicon
Run PHPUnit locally in your WordPress Plugin with DDEV
Favicon
Setting up for Drupal's Functional JavaScript tests
Favicon
Fix Memory Exhausted Issue in Laravel Tests
Favicon
Another way to create test module configuration
Favicon
Testando filas em projetos Laravel
Favicon
Checklist to Become Software Developer
Favicon
Chat : Test unitaire sur des méthodes privées
Favicon
Chat: Unit test of private methods
Favicon
Upgrading to Laravel 10, PHPUnit 10, and Pest 2
Favicon
Testing an external api using PHPUnit
Favicon
Installing PHP Unit
Favicon
Code coverage for PHPUnit in VSCode
Favicon
Behavior-Driven Testing with PHP and PHPUnit
Favicon
Run Laravel test multiple times
Favicon
Using PHP anonymous classes to test collection services
Favicon
Test coverage: did you set Xdebug's coverage mode?
Favicon
Using github actions to execute your PHP tests after every push
Favicon
Writing a basic Feature Test with PhpUnit in Laravel
Favicon
Fix Symfony tests with PHPUnit 10
Favicon
The most efficient way to debug problems with PHPUnit mocks
Favicon
Why I am unable to assert that method has been called on my mocked service?

Featured ones: