Chat : Test unitaire sur des méthodes privées
Tags: php, phpunit, unittest, chatgpt
Published: 2023-10-26 15:11:48

(English translation: [Chat: Unit test of private methods](https://dev.to/fredbouchery/chat-unit-test-of-private-methods-1ig2))
Je vous livre ici, l'échange que j'ai eu avec ChatGPT (version 4) au sujet des tests unitaires de méthodes privées, car je trouvais l'exercice intéressant.
Les mises en gras sont de mon fait à posteriori pour mettre un peu de relief.

Une phrase que je retiens et que j'aime beaucoup :

> Le but des tests unitaires est de s'assurer que chaque partie de votre code **fonctionne comme prévu**, et non de s'assurer que chaque méthode passe un test.

![Humain débattant avec un robot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pj0bawoym16686f7vyip.png)
---

Fred

> Je souhaite mettre en place des tests unitaires avec PHPUnit. En PHP, que penses-tu des tests de **méthodes privées** ?

ChatGPT

> Il est généralement recommandé de ne **pas** tester directement les méthodes privées dans PHPUnit. Les méthodes privées sont considérées comme des détails d'implémentation de la classe et ne devraient être testées qu'**indirectement** par le biais des méthodes publiques qui les utilisent. 
> Si vous ressentez le besoin de tester une méthode privée directement, cela pourrait indiquer un **problème de conception**. Peut-être que la classe fait trop de choses et qu'elle pourrait être refactorisée pour déplacer cette fonctionnalité vers une nouvelle classe où elle serait publique et pourrait être testée directement.
> Cependant, si vous devez **absolument** tester une méthode privée pour une raison ou une autre, vous pouvez utiliser la réflexion en PHP pour rendre une méthode privée accessible et ensuite la tester. Mais encore une fois, cela devrait être **l’exception** plutôt que la règle dans la plupart des cas.

Fred

> J'ai une partie du code avec du traitement algorithmique que j'aimerais bien valider, mais je **ne souhaite pas** qu'il soit accessible de l'extérieur. Si je plaçais ce code dans une classe à part, cela reviendrait à le rendre accessible, hors il n'a **aucun** intérêt en dehors du contexte de ma classe. Quant-aux tests uniquement des méthodes publiques, cela me **force à mocker beaucoup** de choses, et les tests deviennent **complexes**. En testant  ma méthode privée, j'ai des tests très simples

ChatGPT

> Dans ce cas, si vous pensez que tester vos méthodes privées directement vous **apporte de la valeur** et améliore la qualité de votre code, alors vous devriez le faire. 
> En fin de compte, l'objectif des tests unitaires est de s'assurer que votre code **fait ce qu'il est censé faire** et de vous aider à identifier et corriger les bugs. Si tester directement une méthode privée vous aide à atteindre ces objectifs, alors il n'y a pas de raison de ne pas le faire.
> Comme mentionné précédemment, vous pouvez utiliser la réflexion pour accéder aux méthodes privées et les tester. Voici un exemple de comment vous pouvez faire cela en PHP :
> ```php
> $className = new YourClass();
> $reflector = new ReflectionClass($className);
> $method = $reflector->getMethod('yourPrivateMethod');
> $method->setAccessible(true);
> $result = $method->invokeArgs($className, [$arg1, $arg2]);
> // maintenant vous pouvez faire des assertions sur $result
> ```
> Cependant, cela peut rendre vos tests un peu plus complexes et plus difficiles à maintenir. Vous devriez également être conscient que c'est une sorte de "_hack_" et qu'il y a une raison pour laquelle les méthodes privées ne sont pas normalement accessibles : il s'agit de maintenir l'encapsulation et de masquer **les détails d'implémentation**, qui pourraient changer. En testant directement les méthodes privées, vous couplez vos tests à ces détails d'implémentation spécifiques et tout changement dans ces méthodes pourrait nécessiter une réécriture de vos tests.
> En fin de compte, c'est à vous de décider si les avantages l'emportent sur les inconvénients dans votre cas spécifique.

Fred

> Une bonne solution serait d'avoir un concept de **classe amie** en PHP, hors, cela n'existe pas

ChatGPT

> Effectivement, PHP n'a pas de concept de "classe amie" comme le fait C++. Dans C++, une classe amie peut **accéder aux méthodes privées et protégées** de la classe qui l'a déclarée comme amie, ce qui pourrait être utilisé pour tester ces méthodes.
> Sans cette fonctionnalité en PHP, vous devez trouver d'autres moyens de tester vos méthodes privées si vous décidez que c'est nécessaire. C'est une des raisons pour lesquelles certains optent pour la réflexion malgré ses inconvénients.
> Si vous avez un cas où vous avez des méthodes privées complexes que vous voulez tester directement, une autre option est de les rendre publiques juste pour des raisons de test et de documenter clairement qu'elles sont destinées à être utilisées à des fins de test uniquement. C'est loin d'être idéal, mais c'est une autre option à considérer.
>   
> En fin de compte, il est important de se rappeler que le but des tests unitaires est de s'assurer que chaque partie de votre code **fonctionne comme prévu**, et non de s'assurer que chaque méthode passe un test. Si vous pouvez tester le comportement de votre classe d'une manière qui a du sens sans avoir besoin de tester chaque méthode privée, cela pourrait être l'approche la plus efficace.

Fred

> Je me demande si des méthodes **protégées** n'auraient pas plus de sens

ChatGPT

> Utiliser des méthodes protégées en PHP peut effectivement être une alternative pour des raisons de testabilité. Les méthodes protégées peuvent être appelées par la classe elle-même, ainsi que par toutes les classes qui héritent de celle-ci. 
> Ainsi, en cas de complexité algorithmique qui ne doit pas être exposée en public tout en souhaitant pouvoir la tester unitairement, créer une sous-classe **uniquement** pour les tests pour tester ces algorithmes spécifiques pourrait être une solution. 
> Voici un exemple d'à quoi cela pourrait ressembler :
> ```php
> class MyClass {
>     protected function methodToTest($input) {
>         // Votre algorithme complexe ici
>     }
> }
> class MyClassTestVersion extends MyClass {
>     public function methodToTestWrapper($input) {
>         return $this->methodToTest($input);
>     }
> }
> ```
> Et voici comment vous pourriez utiliser cela dans vos tests PHPUnit :
> ```php
> public function testMethodToTest() {
>     $testObject = new MyClassTestVersion();
>     $result = $testObject->methodToTestWrapper('input');
>     // Vos assertions ici
> }
> ```
> C'est un moyen d'accéder à la méthode sans l'exposer en tant que publique et sans utiliser la réflexion PHP. Cependant, cette solution a ses propres limites et inconvénients. L'un d'eux est que si les méthodes protégées sont **trop nombreuses**, cela peut alourdir la classe test et la rendre plus difficile à gérer. De plus, si la logique des méthodes protégées change, les tests doivent être ajustés de la même manière. Et si une méthode privée est utilisée par plusieurs méthodes protégées, alors elle sera testée de manière répétée, ce qui rend les tests moins efficaces. L'équilibre approprié dépend donc de chaque cas particulier.

Fred

> Une méthode protégée ne devrait justement **pas changer**, car c'est un **contrat** avec les classes qui en héritent, et les tester, cela a beaucoup de sens en fait.

ChatGPT

> Tout à fait, vous avez raison. Les méthodes protégées sont effectivement une **partie du contrat de la classe** avec ses sous-classes, tout comme les méthodes publiques sont un contrat avec tout code externe qui utilise la classe.
> Tester les méthodes protégées a donc du sens, car cela permet de garantir que la classe respecte son contrat même lorsque celle-ci évolue. En toute rigueur, c'est même **nécessaire** lorsque vous suivez une méthodologie de développement piloté par les tests (Test-Driven Development ou TDD), où chaque partie du code doit être couverte par des tests.
> C'est une bonne pratique de garder ce contrat aussi **stable** que possible pour ne pas casser les sous-classes qui dépendent de ces méthodes. Si vous devez changer le comportement de ces méthodes, il est important de le faire de manière à ne pas casser ce contrat. C'est là que les tests unitaires des méthodes protégées peuvent être **très utiles**, pour vérifier que vous n'avez pas introduit de régressions lors de la modification de ces méthodes.
> Il est donc justifié de tester vos méthodes protégées si cela correspond à votre logique d'organisation pour le code et à votre stratégie de test. Gardez cependant à l'esprit qu'il peut toujours être bénéfique de travailler au **découplage et à la simplification de vos classes**, en évitant autant que possible un grand nombre de méthodes protégées, conformément aux principes du **SOLID** et du développement orienté objet.

Fred

> Sur les méthodes privées, un développeur peut facilement se dire : "_je peux changer cette méthode, voir la supprimer, c'est pas grave, elle est privée_". Mais en faisant ça, il va **casser** mes tests et peut-être que cela le conduira à constater certains cas d'usage qu'il n'avait pas identifié. Peut-être qu'une annotation sur ma méthode permettrait de **prévenir** le développeur qu'il y a des tests associés, avant qu'il ne s'en rende compte plus tard. Une annotation "@see" par exemple

ChatGPT

> Votre suggestion d'utiliser une annotation "@see" est une excellente idée pour signaler qu'une méthode privée a des tests associés. Cela peut aider à renforcer la communication au sein de l'équipe de développement et à **faire comprendre** à quelqu'un qui modifie cette méthode qu'il doit aussi vérifier et potentiellement modifier les tests associés.
> Cela pourrait ressembler à ceci dans votre code:
> ```php
> /**
>  * @see \Tests\Unit\YourClassTest::testYourPrivateMethod
>  */
> private function yourPrivateMethod()
> {
>     // code
> }
> ```
> Cependant, notez que cette annotation doit être **entretenue** : si le test est renommé ou déplacé, l'annotation doit être mise à jour.
> Il est également important de noter qu'une **bonne couverture de tests des méthodes publiques** devrait également attraper les cas où une modification d'une méthode privée engendre des erreurs.
> Dans tous les cas, la décision de tester ou non les méthodes privées doit être prise en fonction du contexte spécifique du projet, de l'équipe, et du code lui-même. L'idée est de tester ce qui est nécessaire pour avoir **confiance** dans le fonctionnement correct de votre système, tout en **équilibrant** cet effort avec le temps de développement et la maintenabilité du code.

Fred

> Oui, je suis d'accord, cela doit rester à la marge, et favoriser les tests de méthodes publiques. Je m'interrogeais sur cette pratique pour **décomplexifier** les tests et éventuellement faciliter l'ajout de tests sur du **code legacy mal conçu**. Je préfère des tests sur méthodes privées que **pas de test du tout**

ChatGPT

> Tout à fait, je suis d'accord avec vous et vous avez une très bonne réflexion sur ce sujet. 
> Effectivement, lorsque vous travaillez sur du code legacy, vous n'avez **pas toujours** la possibilité de refactoriser tout ce que vous voudriez. Il est donc crucial d'avoir des tests pour garantir le bon fonctionnement de votre application, même si cela implique de tester des méthodes privées. 
> Et comme vous l'avez bien dit, dans certains cas le test d'une méthode privée peut être **moins complexe et plus rapide à mettre en place** que le test de la méthode publique qui l'utilise, notamment si cela évite de devoir mocker de nombreux objets.
> En résumé, tester des méthodes privées peut avoir du sens dans **certaines situations**, notamment dans le cadre de code legacy et quand cela permet de rendre les tests plus simples et plus fiables. C'est une pratique à utiliser avec **parcimonie** et **discernement**, en gardant en tête l'objectif principal des tests unitaires : **s'assurer de la fiabilité et de la qualité de votre code**.