dimanche 25 janvier 2009

LES TESTS SONT DU CODE

LA SOUPLESSE GAGNÉE

L'Extreme-Programming est une sacrée aide. Grâce au TDD, au remaniement et à la conception simple, nous arrivons à mettre au point des logiciels ayant une architecture extensible. Nous ajoutons des incréments de fonctionnalité au fil des itérations. Les tests de recette et les tests développeur sont un tuteur qui aident le logiciel à pousser et un filet de sécurité contre les régressions. Ils permettent au logiciel de conserver sa souplesse, celle du SOFTware .

Nous pratiquons le TDD et le test-first. La couverture du code par les tests est intégrale. De plus, ayant les contraintes de sûreté de fonctionnement du logiciel critique, nous ne lésinons pas sur les cas de tests.

D'ailleurs, en démo nous nous livrons à un petit exercice. Nous proposons au client d'introduire un défaut dans la base de code et nous lui montrons que la suite de tests détecte sa modification.

Depuis plusieurs projets, nous constatons que nous avons plus de deux fois plus de lignes de code de test que de lignes de code de production. Étant donné qu'il faut vérifier de nombreux scénarios, le code des tests est souvent assez répétitif.

Vous voyez où je veux en venir?

LA SOUPLESSE PERDUE

Cette masse de tests est là en partie pour conserver la souplesse du logiciel testé. Elle est un tuteur construit autour de l'application. Mais si nous ne faisons pas très attention, cette masse de tests devient inflexible.

Tous ces cas de tests si ressemblants sont autant de mauvaises occasions de dupliquer du code. Tous ces mocks et tous ces stubs aux comportements légèrement différents doivent tous évoluer si leur interface de base est amenée à changer. Pour modifier une ligne de code de production nous en arrivons à devoir changer plus de 10 lignes de code de test. Le code des tests tend naturellement à devenir une masse pénible à faire évoluer. Les tests deviennent inflexibles. Le tuteur est devenu un massif baobab. Le logiciel perd alors sa souplesse d'évolution parce que les tests sont devenus inflexibles. Avec de bonnes intentions nous avons rendu l'application inerte. Elle a perdue sa souplesse parce que l'échafaudage qui l'enrobe est devenu un gros sac de nœuds.

LA SOUPLESSE GAGNÉE (BIS)

Heureusement, ce constat n'est pas une fatalité. Nous avons de la chance car les tests sont aussi du code. Nous devons donc porter au code des tests le même soin qu'au code de production. Les standards de codage s'appliquent au code de production et au code des tests. Nous devons systématiquement remanier le code des tests pour en éliminer les duplications, en réduire le volume et le rendre clair et expressif.
Il existe une architecture des tests. Si une classe hérite d'une autre alors son test unitaire hérite du test unitaire de la super-classe. Ainsi, nous rejouons des tests hérités sur une instance fille. Ceci nous permet de vérifier le si puissant
Principe de Substitution de Liskov.
En remaniant les tests, nous devons faire bien attention à ne pas les faire régresser. Ceci est d'autant plus délicat que le code des tests est écrit sans tests! Quoique nous pouvons assimiler le code testé au test des tests (vous me suivez toujours?). Modifiez un test et vous aurez peut-être la chance de le voir échouer s'il n'est plus en cohérence avec le code testé. Ceci ne marche malheureusement pas si la modification du test l'a rendu plus permissif ...

En tout cas, ce qui est génial, c'est que tester c'est coder!

10 commentaires:

  1. Merci Emmanuel pour cette démonstration.

    En effet, lors de la première séance du Dojo CARA, qui a pour but l'apprentissage du TDD, Bruno a proposé de factoriser le code des tests ... Ce fut très difficile de faire passer le principe qui dit que "Les tests sont du code" et par conséquent que le principe DRY (Don't Repeat Yourself) devait être appliqué.

    Je pense qu'un premier remaniement du code, pour ajouter une fonctionnalité, montrera combien il est important d'appliquer les mêmes principes aux deux types de code ; si je peux m'autoriser la distinction !

    Luc

    RépondreSupprimer
  2. @Luc: effectivement tout le monde n'a pas lu Meszaros et son xUnit Test Patterns ;o) (pour ceux qui sont intéressés ça se trouve ici http://www.amazon.com/xUnit-Test-Patterns-Refactoring-Addison-Wesley/dp/0131495054)

    RépondreSupprimer
  3. Vous trouverez pas mal de détails sur le contenu de ce livre sur le site suivant xunitpatterns.

    RépondreSupprimer
  4. Ok, donc concrètement sur un cas standard de base (qui donc n'existe pas mais permet de réfléchir) Si on développe 100 de code je développe combien de code de tests ? on double le coût ?

    Avez vous une idée de la répartition des efforts à fournir sur ces 2 projets interdépendants que sont le code source et le codest (code des tests)

    RépondreSupprimer
  5. Alix:

    Sans parler d'un cas utopique, nous écrivons réellement plus de 2 fois plus de lignes de tests que de lignes de code.

    Ce qui est déstabilisant et contre-intuitif au début, est que cela n'augmente pas le coût de développement. Au contraire même, cela le réduit.

    Si on pense à court-terme, on peut en effet croire que les tests augmentent le coût de développement. Mais, nous constatons en comparant les projets que ceux qui appliquent le TDD rigoureusement coûtent moins à moyen et à long terme.

    En effet, le code est plus rapide à faire évoluer, il y a moins de corrections à faire dûes à de la non-qualité et les phases d'intégration "big-bang" disparaissent.

    Donc, tester coûte moins cher que de ne pas tester.

    Sur nos projets, nous argumentons même qu'il faut avoir une attitude extrême envers les tests. 100% de couverture fonctionnelle et structurelle est un objectif non-négociable pour réellement ressentir le retour sur investissement des tests.

    RépondreSupprimer
  6. Merci pour votre réponse.
    Une des principales difficultés que je rencontre dans ma mise en place des tests automatisés est la construction/maintenance des jeux de données utilisées par ces tests, et vous ?
    Concernant le type de projet sur lequel il y a un retour sur investissement, vous avez des projets de quelles taille ? Un projet de 8 mois de dev au total découpé en version de 2 mois de dev effectuée avec une pause de 2 mois entre chaque version peut impliquer que les équipes ne sont pas les mêmes pour chaque version (à part le chef de projet), avez vous ce genre de contexte ou bien etes vous dans un mode de developpement continue ?

    RépondreSupprimer
  7. Alix:

    Nous rencontrons également des difficultés à établir les jeux de données des tests. Néanmoins, cet effort me semble très rentable dès que ces tests sont rejoués automotiquement et très régulièrement.

    Nous appliquons ces démarches pour des projets de 4 développeurs sur 8 mois (cas rare) et des projets de 15 développeurs sur 2 ou 3 ans (cas plus courant).

    Nous sommes dans un contexte où les projets sont continuellement menés pendant de 2 à 3 ans. Ensuite, les produits sont mis en production et après quelques années nous avons de nouvelles fonctionnalités à ajouter.

    Dans ces cas de réveil de projet, les tests automatisés seront une aide des plus précieuses. En effet, l'équipe aura changé et seuls ces tests lui permettront de faire des modifications sans craindre les régressions.

    RépondreSupprimer
  8. La question suivante me parait donc utile :
    Le retour sur investissement des tests automatisés est il dépendant de la taille (jours et planning) du projet ? si oui, y a t'il une métrique ?
    On pourrait même élargir cette question pour l'application de l'agilité ...

    non ?

    RépondreSupprimer
  9. Alix:

    Je ne pense pas que le retour sur investissement des pratiques agiles dépend de la taille du projet. Il me semble que ces pratiques sont rentables pour toute gamme de projet.

    Par contre, je pense que TOUTE démarche de développement perd en efficacité au delà d'une taille critique.

    RépondreSupprimer
  10. Bonjour, et merci pour ce blog que je découvre.

    Le test des tests n'est pas (uniquement) le code correct, mais aussi le code buggé.

    Pour valider qu'un test marche toujours, il faudrait vérifier (1) qu'il réussit avec le code non modifié, et (2) qu'en introduisant volontairement le bug que le test est censé détecter, on fait échouer la nouvelle version du test.

    Il m'arrive d'introduire ainsi (et temporairement) un bug dans le code sous test, mais j'avoue être très loin de le faire systématiquement.

    Ce serait une piste intéressante pour l'automatisation, je crois qu'il existe déjà certains outils qui vont dans ce sens mais je serais malheureusement incapable de les nommer. J'espère que d'autres lecteurs du blog pourront en dire quelque chose... ?

    RépondreSupprimer