Celui qui voulait écrire du code testable

Même si vous n’avez pas forcément entendu parler de TDD (Test Driven Development), vous avez à coup sur déjà entendu parler de tests, unitaires et/ou fonctionnels.

Et peut-être que vous avez déjà cherché un petit peu comment écrire des tests pour votre code.
Et la, peut-être que vous vous êtes dit « ouais, super, ça fonctionne bien dans les codes des autres. Mais sur le mien, ça ne s’applique pas ».
Dans ce cas, le problème vient surement de votre code, qui n’est pas testable.

Voici donc 10 conseils pour rédiger du code testable, fortement inspirée de l’article du Google Testing Blog « Writing Testable Code« .

  1. Ne pas mélanger le constructeur avec le code logique

    Chacune de vos classes doit être indépendantalisable des autres.
    Si vous faites, par exemple
    class myClass {
      private $secondClass = new secondClass();
    }

    Votre code est difficilement testable. En effet, vous allez avoir besoin d’instancier la classe myClass afin de la tester, en lui passant un objet secondClass factice et que vous devrez modifier de la manière que vous désirez.

    Mieux vaut donc faire la chose de la manière suivante :
    class myClass {
      private $secondClass;
      public function __construct(secondClass $second) {
        $this->secondClass = $second;
      }
    }

  2. Demandez les choses. N’allez pas les chercher vous même

    Aka respecter la loi de Déméter.
    Supposons la classe suivante
    class myClass {
      private $second;
      public function __construct(Context $context) {
        $this->second = $context->second;
      }
    }

    Vous êtes mal parti 🙂

    Ce n’est pas à myClass de se préoccuper du contexte. La preuve : elle ne l’utilise pas, ne fait que en récupérer la valeur « second » et la stocker.
    Autant y passer directement cette valeur, dont la classe a réellement besoin.
    class myClass {
      private $second;
      public function __construct(Second $second) {
        $this->second = $second;
      }
    }

  3. Ne faites pas tout le travail dans le constructeur

    Lorsque vous testez un objet, vous allez avoir toute une floppée de tests instanciant chacun votre objet, et testant une fonctionnalité précise de cet objet pour ensuite vérifier que la valeur rendue et que l’action effectuée par celle-ci sont correctes.
    Si vous avez placé tout votre code dans le constructeur, vous devrez d’abord passer avec succès toutes les assertions de celui-ci, et ce à chaque test.
    Cela peut être bénin ou très complexe. Qui plus est, cela ne vous posera pas des problèmes que dans les tests de la classe en question mais dans tous les tests des classes faisant appel à celle-ci.

  4. Evitez les singletons et variables globales

    Chaque test doit être vu comme une instanciation de votre application.
    Utiliser des singletons ou des variables globales a pour effet la perte de cette notion.
    Du coup vos tests ne sont plus fait dans un environnement que vous définissez mais dans l’environnement déjà défini par les tests précédents. Vous allez à l’encontre de tests qui échouent à cause d’éléments définis dans un test précédent.
    Par ailleurs les frameworks de tests ne sont jamais très clair sur l’ordre d’exécution de ces tests. Par ordre alphabétique, de déclaration, …

  5. Evitez les méthodes statiques

    Parce que le procédural c’est mieux !
    Et surtout parce que la clé pour tester est de pouvoir sortir du flot d’éxecution « normal », afin de tester des parties de code et celles-ci uniquement.
    Avoir une méthode statique Math.abs n’est pas un problème car celle-ci est uniquement finale. Mais avoir une méthode statique qui fera appel à diverses méthodes privées à l’intérieur de la classe rendra le test de ces mêmes méthodes impossible.

  6. Favoriser les compositions aux héritages

    Beaucoup de développeurs utilisent l’héritage dans le seul but de réutiliser les méthodes disponibles dans une autre classe. Ce qui est une erreur.
    Hériter un contrôleur de votre classe d’identification va rendre le test impossible car vous devrez faire un mock de cette classe d’identification dans chaque test. Et naviguer dans la classe parente pour chacun de ces tests.
    Mais supposez que, en plus de cela, votre classe d’identification hérite de votre classe de transactions avec la base de données. Vive le cauchemar.

  7. Favoriser le polymorphisme aux conditions

    Si vous voyez un switch ou (pire) plein de if imbriqués, vous devriez penser polymorphisme.
    Le polymorphisme consistera à faire diverses classes et méthodes effectuant chacune une action spécifique et appellée par la classe ou méthode principale.
    Cela vous aidera fortement car une méthode plus petite/simple est beaucoup plus aisée à tester.

  8. Mélanger des objets de valeur et des objets de service

    Votre application devrait comporter deux types d’objets.

    • Des objets valeur. Par exemple, Ville, CodePostal, CarteBancaire, … Ces objets n’ont généralement que des méthodes set et get. Ils sont particulièrement simple à tester.
    • Des objets de service. Par exemple, MailServeur, AdresseValidateur, … Ces objets sont beaucoup plus complexes et contiennent toute la logique métier.

    Un objet valeur ne doit jamais utiliser un objet service. En revanche un objet service doit utiliser l’objet valeur.
    Les objets valeur étant simple à instancier, ils peuvent l’être avec la méthode new, et ce plusieurs fois par application si besoin.
    Un objet service sera plus compliqué à instancier et pourra nécessiter de le passage multiples paramètres de configuration. Il est donc mieux de passer par une « Factory », qui permettra de n’instancier l’objet qu’une seule fois dans l’application puis d’utiliser toujours le même par la suite.
    Ainsi par ailleurs, vous pourrez vous même l’instancier avec les paramètres de votre choix dans vos tests et le réutiliser par la suite.

  9. Ne mélangez pas tout

    Si lorsque vous résumez l’utilité d’une classe, vous devez utiliser le terme « et », que cette classe est difficile à lire et à comprendre pour les nouveaux membres de votre projet, qu’elle des paramètres qui ne sont utilisés que dans quelques méthodes alors vous pouvez considérer que vous avez un problème de mélange des fonctionnalités.
    Ces classes sont très difficiles à tester car elles cachent de multiples objets et que vous êtes par conséquent obligé de tester tous les objets en même temps.

  10. Pensez deux fois, n’agissez qu’une

    Si vous foncez dans le tas, développez sans réfléchir avant, vous courrez vers des répétitions de votre code. Et par conséquent de vos tests.
    Et par le même conséquent, augmentez les risques de régression et donc faire des tests perds de tout son intérêt.
    Mieux vaut donc réfléchir deux fois à quelque chose avant de le développer. Vous avez peut-être déjà, dans votre application, fait la moitié du travail qu’il suffit juste d’utiliser.

Voici donc une liste de dix conseils pour vous permettre de mieux tester votre code. Vous aurez peut-être d’autres conseils à fournir.
Ne vous en privez pas.

Deux lectures (en anglais) que je vous recommande si vous souhaitez aller plus loin :

  • xUnit Test Patterns : Refactoring Test Code
    Il s’agit de la bible de tout développeur désirant faire des tests et ne sachant pas très bien comment s’y prendre. Il fournit diverses solutions permettant de tester divers patterns d’application.
  • The Pragmatic Programmer
    Celui-ci ne concerne pas que les tests. Mais donne de multiples conseils afin d’améliorer la lisibilité de son code, sa productivité et ce, notamment en faisant des tests.

10 Commentaires

  1. De grosses erreurs de traduction (ou d’interprétation du texte original en anglais ?), qui aboutissent parfois à des contresens !

  2. Cet article serait beaucoup plus lisible si la sémantique HTML était respectée, les caractères en gras au début de chaque li étant à l’évidence des titres…

    Sinon, merci !

  3. Pingback: Ecrire du code testable - Damien Mathieu

  4. une petite précision sur la différence entre tests unitaires et fonctionnels aurai apporté un plus intéressant je pense.

    même si le but final est plus ou moins identique, faire la distinction peut permettre de mieux comprendre leurs différences.

  5. @Olivier c’est corrigé.
    @Loic ce n’est pas le but de cet article non plus.

  6. @damien : c’est mieux, mais tous les paragraphes commencent par un point maintenant ^^

  7. Ouf, je suis rassuré, cela ne concerne que la POO, et je sais pas programmer en POO 🙂

  8. @Code Promo en même temps écrire des tests sans faire de POO, l’intérêt est totalement nul.
    A moins que ce commentaire n’ait pour seul but de te faire un lien.

  9. Pingback: Bonnes pratique de codage en C#

  10. Pingback: Bonnes pratique de codage en C# « Hakanai

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *