Journal Faites des tests !

Posté par  . Licence CC By‑SA.
14
22
juil.
2018

Actuellement sur le développement d'un programme en langage C++ et avec la quête de produire un logiciel fiable et performant je me suis mis à la recherche d'outils me permettant d'atteindre cet objectif.

Après quelques recherches, j'en ai découvert deux, le test unitaire et fonctionnel. Pour le test unitaire j'ai choisi la librairie Boost et un script Bash pour le test fonctionnel.

Durant la rédaction de ces tests, j'ai été surpris plusieurs fois à corriger des bugs dans mon programme. Et cela sans lancer les tests juste en les rédigeant !

Donc conclusion les tests unitaires et fonctionnels ça prend du temps à développer mais ça permet de corriger un paquet de bugs.

  • # Bug titre

    Posté par  . Évalué à 2.

    Je viens de me rendre compte que j'ai oublié un "s" à test dans le titre. Pourriez-vous corrigez ce bug ?

  • # + intégration continue

    Posté par  . Évalué à 5.

    Tout à fait d'accord pour les tests.

    Ensuite il faut s'assurer que les test sont bien exécutés avant chaque merge d'une Pull Request dans master. Il y a moyen de faire faire le boulot par un petit script mais le mieux c'est de mettre en place un outils d'intégration continue.

  • # tester c'est douter, mais testez !

    Posté par  (site web personnel) . Évalué à 6.

    • pyramide des tests : beaucoup de tests unitaires, des tests d'intégration, des tests fonctionnels, un peu de bout-en-bout
    • sur du C++, test de fuites de mémoire et autres soucis mémoire avec valgrind
    • compiler pour plusieurs architectures et avec différentes options, c'est pénible mais ça lève des bugs
    • avoir une compilation reproductible parce que c'est à la mode
    • [^] # Re: tester c'est douter, mais testez !

      Posté par  . Évalué à 2.

      Et en plus ou à la place de valgrind, compiler et lancer les tests unitaires avec address sanitizer et undefined behaviour sanitizer (et évidemment corriger les bugs éventuels).

      • [^] # Re: tester c'est douter, mais testez !

        Posté par  . Évalué à 3.

        J'ajouterais à ton ajout qu'on peut également détecter les (des) problèmes d'UB avec des analyseurs statiques de code, en plus d'utiliser les sanitizers dynamiques de clang. Ca peut montrer beaucoup de choses (ainsi que de rappeler un paquet de règles du C/C++ qui peuvent être oubliées, en lisant les résultats)

        (Divulgation complète : l'entreprise pour laquelle je travaille vend un analyseur statique, mais ce n'est pas vraiment la partie sur laquelle je bosse)

        Tous les nombres premiers sont impairs, sauf un. Tous les nombres premiers sont impairs, sauf deux.

        • [^] # Re: tester c'est douter, mais testez !

          Posté par  . Évalué à 0.

          Je connais beaucoup moins ça… Du coup ça m'intéresse. Ça marche bien ? Comment ça s'appelle ? (Combien ça coute ? :p)

          • [^] # Re: tester c'est douter, mais testez !

            Posté par  . Évalué à 6.

            Ça marche bien ?

            J'ai envie de dire oui. Ca dépend de ce que l'on attend, évidemment, mais par rapport à ce qu'ils sont censé tester, oui, ils font ce que l'on attend d'eux. Il ne faut pas s'attendre à ce qu'ils disent "ton algorithme est faux parce que tu fait C = a*b alors que la spec demande C = a+b", parce que ce n'est pas leur rôle. Par contre leur rôle c'est de trouver/prouver* des défauts/bugs dans l'utilisation du langage. Par exemple, en C, un analyseur doit (devrait) trouver l'utilisation d'un pointeur ou d'une variable non initialisée, un accès hors limite de tableau, une division par 0, …

            En général, vu que c'est "gratuit" à implémenter, il y a souvent en plus la possibilité de vérifier des bonnes pratiques de codages (par exemple les règles MISRA), mais ça ça peut déjà souvent être fait (en partie au moins) par un linteur (enfin, techniquement, un linteur c'est déjà un outil d'analyse statique)

            Comment ça s'appelle ?

            Ceux qui sont vendu par ma boite c'est Polyspace Code Prover et Bug Finder.

            Combien ça coute ?

            Disons qu'on ne vise pas le marché des particuliers ;-) . C'est plutôt à destination des gens qui ont a certifier un produit, souvent dans un processus encadré par des normes (DO-178C, IEC61508, ISO26262, …) mais pas uniquement. (une autre façon de dire ça serait de dire que parmi les différents trucs qu'on vend, il me semble que ça fait partie des quelque exceptions pour lesquels on n'a pas directement un prix affiché sur le site, mais un lien "contacter un commercial")

            Après le prix, dans l'absolu, c'est pas le plus important, la question est "est-ce que ça rapporte plus que ça ne coûte ?" (oh mon dieu, je pense que je traîne trop avec des commerciaux)

            * trouver et prouver sont deux termes qui ne veulent pas dire la même chose et qui recouvrent deux types d'outils d'analyse statiques différents. Les outils qui prouvent (par calcul formel, "sound static analyzer" vs "unsound" pour les termes anglais) sont "mieux" (pas de faux positifs, pas de faux négatifs, uniquement des preuves d'absence ou de présence de bug. Ah, et aussi parfois des "je ne sais pas", mais c'est "limité" en nombre). Mais ils sont plus limités par la taille du programme analysé et le type de bugs à identifier. (le temps et la mémoire utilisée pour l'analyse augmente rapidement avec la taille du programme analysé)

            Tous les nombres premiers sont impairs, sauf un. Tous les nombres premiers sont impairs, sauf deux.

            • [^] # Re: tester c'est douter, mais testez !

              Posté par  . Évalué à 1.

              Merci pour les infos.
              Dans mon boulot, je me bats avec du vieux code largement améliorable. J'ai déjà pioché pas mal dans les outils libres pour trouver des bugs ou éviter qu'on en rajoute, mais je ne vais pas cracher sur un nouvel outil d'aide s'il apporte des choses.

    • [^] # Re: tester c'est douter, mais testez !

      Posté par  (site web personnel) . Évalué à 2.

      La compilation reproductible (reproducible build) n'a pas, pour autant que je sache, d'intérêt d'un point de vue de validation, c'est plutôt dans un objectif de sécurité.

      • [^] # Re: tester c'est douter, mais testez !

        Posté par  (site web personnel) . Évalué à 6.

        Indirectement tester avec ou sans locales, des locales différentes, un nouvel utilisateur (potentiellement un compte neuf propre), etc., etc. Bref les conditions de tests des 'reproductible builds', ça peut lever des bugs, même si ça n'est pas l'objectif premier. J'ai déjà vu du code marcher en locale C et planter en locale fr_FR, ou un code marcher en version optimisée et pas en debug. Comme toujours un des buts est de passer dans d'autres portions du code (notion de couverture).

        (Un autre but est de tester les valeurs limites : min/max, null ou 0, chaîne vide ou chaîne longue, unicode 4 octets, NaN/infinis, etc.)

      • [^] # Re: tester c'est douter, mais testez !

        Posté par  . Évalué à 3.

        Ça te permet aussi d'être sûr que le binaire qui déconne est bien compilé sur le commit que tu pense.

        « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

  • # TDD powaaaaaa !

    Posté par  . Évalué à 2.

    Comme indiquer par un collègue (ronaldo si tu lis ça) , le Test Driven Development c'est sensass  !

    • [^] # Re: TDD powaaaaaa !

      Posté par  . Évalué à 2.

      J'ai découvert aussi cette technique de développez les test unitaire avant d'écrire le code source. J'y penserais pour mes développement futur.

  • # Le test fait partie intégrante du développement

    Posté par  (site web personnel) . Évalué à 10.

    Pour moi, l'écriture de tests fait partie intégrante du développement et code est tests sont inséparables. D'ailleurs, quand un chef insiste pour séparer tests et code en deux colonnes dans les chiffrages/planifications, je mets 0 dans l'une des deux (genre 0 dev et tout dans test pour choquer les esprits), histoire d'éviter d'entendre ensuite qu'on va pas faire de tests car c'est trop cher.

    Remarque, faudrait que j'essaie un truc du style la colonne dev multipliée par deux (ou plus), et la colonne tests avec un nombre négatif pour retrancher la perte de temps à refaire le dev éternellement jusqu'à ce que ça marche en bout de chaîne.

    De mon expérience, du code non testé c'est une chance sur deux pour que ça foire (même un truc très simple), donc de base je considère que du code non testé c'est du code qui ne marche pas (je parle pour mon code). Le nombre de fois où je me suis dis « ah c'est facile à corriger, juste une ligne, et tester ça me demande d'écrire un test… » et puis bah en fait, j'aurais dû l'écrire ce test, avant de livrer ma correction en bois…

    J'ai testé avec et sans tests sur un même logiciel. Suite à de gros travaux d'architecture réalisés en urgence, j'avais perdu tous les tests (ils étaient mal écris et ça représentait un travail monstrueux pour les remettre d'équerre avec le changement d'architecture). Quand en plus j'ai dû faire du refactoring sur le logiciel, je provoquait des régressions à tout va qu'on ne voyait qu'en bout de chaîne, donc ça coûtait cher à reprendre (sans compter l'énervement du client et des valideurs, plus la perte de confiance à chaque nouvelle livraison). Un jour j'ai pris le temps (qu'on ne m'a pas donné, mais je l'ai pris quand même) de ré-écrire une bonne base de tests pour contrôler un peu ce que je livrais. Ça a stoppé net toutes les régressions, et m'a permis de reprendre un cycle de développement avec tests, ce qui a fait gagner beaucoup de temps par la suite. Bref, sans tests, le refactoring est impossible. Depuis cette expérience, j'ai la preuve formelle que pas de test = caca.

    À noter qu'écrire du code testable (c'est-à-dire qu'on puisse tester facilement à 100%) mène à une conception différente (et plus propre) que quand on travaille à la rache, et ça sans augmenter le coût total du développement.

    Enfin, les tests me permettent de dire « j'ai fini mon travail », car ils constituent une preuve de résultat.

  • # Faire des tests, c'est bien, mais...

    Posté par  (site web personnel) . Évalué à 2. Dernière modification le 22 juillet 2018 à 16:18.

    Je vais sans doute me faire lyncher, d'autant plus que l'on n'est pas vendredi, mais comme chacun y va de son expérience personnelle pour ériger en règle absolue qu'on ne saurait produire du logiciel de qualité sans le soumettre à une batterie de tests, il n'y a pas de raison que je ne fasse pas part de la mienne comme exception à cette règle.

    Alors, que ce soit bien clair : FAITES DES TESTS ! Vos développements logiciels ne s'en porteront que mieux ; les miens n'y font pas exception.

    Comme tout un chacun, j'aurais l'esprit plus tranquille si je livrais mes logiciels après leur avoir fait passer avec succès une batterie de tests. Mais le fait est que le codage de ces tests prend plus de temps qu'il ne m'en ferait gagner. C'est donc une activité que je ne peux me permettre en tant que développeur professionnel, car, le temps étant de l'argent, elle est économiquement non justifiable. Pour être honnête, il me faut aussi avouer que je préfère de loin coder de nouvelles fonctionnalités que des tests (et je ne suis probablement pas le seul)…

    J'ai bien été confronté à quelques bugs qui auraient été détectés nettement plus précocement, et donc corrigés plus facilement et plus rapidement, si j'avais pris la peine de soumettre le logiciel concerné à des tests, mais cela m'est arrivé tellement rarement que cela ne peut justifier l'écriture systématique de tests pour tous mes logiciels. Oui, même l'exception à la règle a son exception :-).

    Mon cas est sans doute exceptionnel, voire unique, mais c'est probablement dû à la manière unique, ou du moins exceptionnelle, que j'ai de développer.
    Je suis développeur C++, mais je n'utilise aucune bibliothèque C++ (en particulier, je n'ai jamais utilisé la STL), ni même C standard. J'utilise mes propres bibliothèques. Et à chaque fois que je développe une nouvelle fonctionnalité, j'essaie de la généraliser au maximum, ou d'en extraire un maximum de sous-fonctions, pour les inclure à ces bibliothèques. Ce qui fait que je n'utilise que du code factorisé au maximum. Or, plus un code est factorisé, plus il est utilisé, et ses bugs sont donc d'autant plus rapidement détectés et corrigés. Ce qui rend probablement les tests superflus dans mon cas.

    Encore une fois, même si moi j'arrive à m'en passer, FAITES DES TESTS !!!

    Avant de vous lancer dans des commentaires enflammés, en voici quelques-uns sur le sujet déjà publiés en ces lieux : https://linuxfr.org/users/fredx/journaux/ce-qu-on-demande-a-un-developpeur-aujourd-hui#comment-1471811

    Pour nous émanciper des géants du numérique : Zelbinium !

    • [^] # Re: Faire des tests, c'est bien, mais...

      Posté par  . Évalué à 6.

      Juste par curiosité, est-ce qu'une partie de ton code est publique/libre ? Ce serait pour faire des tests dessus…

      Tous les nombres premiers sont impairs, sauf un. Tous les nombres premiers sont impairs, sauf deux.

    • [^] # Re: Faire des tests, c'est bien, mais...

      Posté par  . Évalué à 10.

      Mon cas est sans doute exceptionnel, voire unique, mais c'est probablement dû à la manière unique, ou du moins exceptionnelle, que j'ai de développer. […] e suis développeur C++, mais je n'utilise aucune bibliothèque C++ (en particulier, je n'ai jamais utilisé la STL), ni même C standard. J'utilise mes propres bibliothèques. […] Ce qui rend probablement les tests superflus dans mon cas.

      Nan nan ton cas n'est pas exceptionnel. La plupart des joe le bricolo qui bossent tout seul (et|ou) ont appris tout seul sont comme toi. Vous vous inventez un monde parallèle ou tout est clair pour vous et où votre productivité est bien évidement incomparable aux autres.

      Un enfer quand tu récupères un gars comme ça dans une équipe ! :)

      • [^] # Re: Faire des tests, c'est bien, mais...

        Posté par  . Évalué à 5.

        Factoriser là où je bosse, ça a l'air exceptionnel en effet. Et vas y que je copie/colle à chaque nouveau cas ultra proche, que je met du boiler plate parce que "on a toujours fait comme ça", etc. …

        J'aimerais que ce soit encore moins exceptionnel.

        "Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)

    • [^] # Re: Faire des tests, c'est bien, mais...

      Posté par  (site web personnel) . Évalué à 7.

      > Ce qui rend probablement les tests superflus dans mon cas.

      Ha ! J'ai bien ri ! Et on n'est que lundi matin. Merci.

    • [^] # Re: Faire des tests, c'est bien, mais...

      Posté par  . Évalué à 4.

      Or, plus un code est factorisé, plus il est utilisé, et ses bugs sont donc d'autant plus rapidement détectés et corrigés. Ce qui rend probablement les tests superflus dans mon cas.

      Mais en fait tes bugs, tu les découvres comment ?
      À la compilation et à l’exécution ?

      Je suis un peu dans ton cas, je ne code que seul et sur des projets que je suis le seul à utiliser ou presque. J'ai codé longtemps sans tests et ça m'allait, avec le cercle typique de Code/Compile/Essai. Mais même si comme tu le dis, on mets tout dans des fonctions sans effet de bord, des libs, (c'est la base tout ça en fait), des tests ça va grandement aider à savoir ce que tu attend d'une fonction, les paramètres d'entrés, tous ça. Tu peux faire une modif à 20 functions de profondeur dans ton code sans te rendre compte que tu retourne 5 à une entré qui va t'envoyer à une série de fonction et ça va peter quand dans certain cas tordu. Les tests peuvent empêcher cela. ça peut aider à ne pas avoir le cycle Code/Compile/Essai qui ne couvre rien en fait.

      • [^] # Re: Faire des tests, c'est bien, mais...

        Posté par  (site web personnel) . Évalué à 1.

        Ben déjà, je compile systématiquement (avant livraison) avec Visual C++, g++ et clang sous GNU/Linux, macOS et Windows, pour IA-32, AMD64 et AArch(32|64) (ARM), et je ne laisse passer aucun warning. Ça doit déjà éliminer pas mal de bugs. Ensuite, je n'appelle jamais les fonctions C de la bibliothèque d'allocation dynamique de la mémoire (malloc, calloc, free…), et c'est rarissime que je fasse appel (au sens propre et au sens figuré) aux opérateurs new et delete. Et quand je les utilise, là je fais extrêmement attention, notamment pour éviter les fuites mémoires.

        Lorsque j'ai besoin de mémoire dynamique (dont la gestion est reconnu être à l'origine de nombreux bugs), j'utilise des objets dont le développement a débuté il y a plus de 15 ans, et que j'utilise systématiquement. Après tout ce temps, la probabilité qu'il subsiste un bug est très faible. Les seuls bugs liés à la gestion de la mémoire lorsque j'utilise mes bibliothèques sont au nombre de deux : soit je n'ai pas initialisé un objet, soit je passe un objet par valeur au lieu de le passer par référence. Et le système de gestion d'erreur de mes bibliothèques me permet de rapidement déterminer quel est l'objet concerné, et de corriger ce qui ne va pas.

        Je ne manipule jamais directement la mémoire ; pas de tableau de pointeur, ou de pointeur sur un tableau. Je n'utilise jamais l'opérateur [] pour accéder à un élément de tableau ; j'utilise à la place des opérateurs de mes objets.

        Le corollaire de ceci, c'est que, pour les boucles, je ne me rappelle même plus la dernière fois que j'ai écris un for (en fait, si, mais ce n'était pas du C/C++). Si je veux parcourir un conteneur, nommons-le C par exemple, c'est toujours de la manière suivante :

        sRow Row = C.First();
        
        while ( Row != qNIL ) {
         // Traitement sur C( Row ).
        
         Row = C.Next( Row );
        }

        Du coup, jamais de bug lié à un dépassement de limites. À noter que les indexes des conteneurs sont typés, ce qui fait que, si jamais j'utilise l'index d'un conteneur pour accéder à un élément d'un autre conteneur, j'ai tout de suite une erreur lors de la compilation.

        Lorsque je livre une nouvelle version d'un logiciel suite à l'implémentation d'une nouvelle fonctionnalité, je le fais juste après quelques tests manuels pour voir si la fonctionnalité est implémenté correctement. Il se peut que parfois le client découvre un cas de figure pour lequel le logiciel ne se comporte pas comme il faut, mais c'est généralement très vite corrigé. Et là, écrire des tests ne servirait à rien, puisque si je n'ai pas pensé à tester ce cas de figure manuellement, je n'aurais pas non plus penser à écrire le test correspondant.

        Quant aux régressions, dont l'un des but des tests est de faciliter la détection, le fait est que c'est rarissime que j'y sois confrontés. La seule explication que je vois c'est que je suis extrêmement rigoureux, notamment en ce qui concerne la factorisation, que j'applique dés que la possibilité s'en présente.

        Honnêtement, à chaque fois que je livre un logiciel, j'ai un peu mauvaise conscience, vu que, contrairement à ce qui semble se pratiquer à la lecture des différents commentaires, je ne le soumets qu'à quelques tests manuels. Mais tout mes clients ont toujours été satisfait de mes prestations, ce qui permet de me consacrer exclusivement au développement de nouvelles fonctionnalités et à leur amélioration, tâche autrement plus intéressante que de coder de tests. Le jour où cette absence de tests devient problématique, et bien je m'y mettrais aussi, ou peut-être changerais-je de métier…

        Pour nous émanciper des géants du numérique : Zelbinium !

        • [^] # Re: Faire des tests, c'est bien, mais...

          Posté par  . Évalué à 4.

          L'argument de dire que quand on utilise de vieux algo / structure de données donne moins de bug ne prouve rien ! D'autant plus que les contraintes de l'époque ne sont plus les mêmes aujourd'hui …

          Les problèmes d'allocation mémoire, ce n'est qu'une infime partie des bugs qui ne sont pas détectés par les outils d'analyse de code généralement …

          Il y a tout un tas de problèmes que tu ne peux pas détecter même en diversifiant les contextes de combinaison d'appel de tes fonctions, avec en vrac :

          • les effets de bord de ré-utilisation des ressources déjà utilisées
          • les effets de bord dû aux accès concurrentiels
          • les effets de bord dû au fait que tu ne contrôle pas l’environnement d’exécution ou les entrés de tes fonctions
          • etc.

          Bref, tout ça pour dire qu'à mon humble avis, me concernant, c'est très difficile et peu rassurant de ne pas avoir à minima du test unitaire à l'intérieur des API qu'on écrit soit même.

          • [^] # Re: Faire des tests, c'est bien, mais...

            Posté par  (site web personnel) . Évalué à 1.

            L'argument de dire que quand on utilise de vieux algo / structure de données donne moins de bug ne prouve rien ! D'autant plus que les contraintes de l'époque ne sont plus les mêmes aujourd'hui …

            Mais je n'entend rien prouver du tout. J'expose un fait, et j'avance une explication. Si tu en as une meilleure, je suis preneur.

            Les problèmes d'allocation mémoire, ce n'est qu'une infime partie des bugs qui ne sont pas détectés par les outils d'analyse de code généralement …

            Possible, mais les bugs que j'ai exposé dans mon précédent commentaire constituent la quasi-majorité de ceux que je rencontre, et le fait est qu'ils sont relatifs de manière générale à la gestion de la mémoire dynamique.

            Il y a tout un tas de problèmes que tu ne peux pas détecter même en diversifiant les contextes de combinaison d'appel de tes fonctions, avec en vrac :

            • les effets de bord de ré-utilisation des ressources déjà utilisées
            • les effets de bord dû aux accès concurrentiels
            • les effets de bord dû au fait que tu ne contrôle pas l’environnement d’exécution ou les entrés de tes fonctions
            • etc.

            Problématiques que j'ai prises en compte lors du développement de mes bibliothèques. Et les problèmes qui m'auraient échappés, vu depuis combien de temps j'utilise ces bibliothèques, il y a belle lurette que les bugs afférents se sont révélés et que je l'ai ai corrigés (là encore, c'est juste une hypothèse, mais je n'en vois pas d'autre ; et je le rappelle : l'élément-clef de ma méthode de développement, c'est la factorisation).

            Bref, tout ça pour dire qu'à mon humble avis, me concernant, c'est très difficile et peu rassurant de ne pas avoir à minima du test unitaire à l'intérieur des API qu'on écrit soit même.

            Je ne vois pas où est la difficulté, mais je suis parfaitement d'accord ; savoir que du code a passé une batterie de tests, unitaires ou autres, est très rassurant pour celui qui l'a développé, et je ne fais pas exception. Peut-être suis-je le développeur le plus chanceux de l'univers, mais pourquoi diable irai-je écrire des tests qui, en l'état actuel du code que je développe, ne me servirait qu'à me faire perdre du temps ?

            Pour nous émanciper des géants du numérique : Zelbinium !

            • [^] # Re: Faire des tests, c'est bien, mais...

              Posté par  . Évalué à 5.

              Pour résumer ton propos, c'est que tu n'as pas besoin de tests parce que ton code, de part sa maturité, s'est révélé sans bugs, malgré tes nouvelles évolutions.

              Moi comme beaucoup, je ne peux (veux ?) pas : Les tests unitaires, c'est justement un truc qui permets de m'assurer que malgré les modifs internes, rien n'est à priori cassé … Sans vouloir faire le vieux con, cela fait maintenant un paquet d'année que je code (toujours dans dans le même langage, toujours avec le même compilo), et sans forcement m'en rendre compte, il m'arrive encore de changer le comportement d'une API en voulant y faire des évolutions mineures.

              Aussi consciencieux que j'essaie de l'être, c'est très difficile de me dire que tout ce que j'ai codé est résistant à tous les cas tordus sans tests ; et là je ne parle pas que du test unitaire, mais du code de debug, voire les générateurs d'input tout pourris etc.

              • [^] # Re: Faire des tests, c'est bien, mais...

                Posté par  (site web personnel) . Évalué à 3.

                Alors, mon hypothèse, et ce n'est rien de plus qu'une hypothèse, c'est que, plus un code est ancien et factorisé, plus rapidement les bugs introduits pas des modifications interagissant avec ce code sont détectés. J'ignore dans quelles proportions chacun de ces critères influe sur le résultat final, en admettant mon hypothèse fondée, mais mon code étant très ancien (informatiquement parlant) et très factorisé, il cumule de toute manière les deux avantages. Je ne sais pas en quel langage tu développes, mais, en terme de factorisation, il y a moyen d'atteindre des sommets avec les templates du C++.

                Du code réputé mature, pour moi, ce n'est non seulement du code qui fait exactement ce pourquoi il est conçu, mais également qui te fournit une information pertinente si on lui demande de réaliser des choses qui n'ont pas de sens. Au début du développement de mes bibliothèques, il m'arrivait d'avoir des bugs que j'imputais en premier lieu à mes bibliothèques pour me rendre compte qu'au final le bug se situait dans le code appelant ces bibliothèques. J'ai donc modifié mes bibliothèques de telle manière que, si cette situation se reproduit, elles produisent un message qui me permette facilement de détecter l'origine du bug.

                Il m'arrive également d'introduire des, disons, comportements indésirables lorsque je modifie une de mes bibliothèques, mais, sans doute parce que le code est très factorisé (encore une fois, ce n'est qu'une hypothèse), le code fautif est rapidement mis à contribution et donc son comportement erratique détecté.

                Il faut savoir que, chaque jour, de manière automatisé, des utilitaires que j'ai développés, et qui sont donc basés sur mes bibliothèques, sont utilisés de manière intensive. Ces utilitaires, je les recompile régulièrement. Aujourd'hui, il est rare qu'un bug induit par des modification de mes bibliothèques ne soient détectés qu'après recompilation de ces utilitaires, mais peut-être qu'auparavant, c'était eux qui faisaient office de tests, rendant inutile l'écriture de tests spécifiques…

                Pour nous émanciper des géants du numérique : Zelbinium !

                • [^] # Re: Faire des tests, c'est bien, mais...

                  Posté par  . Évalué à 2.

                  Juste deux questions.
                  Comment tu factorise ton code ? J'essaye de factoriser mon code mais parfois je ne sait pas trop quelle partie séparer d'une fonction dans une autre fonction. Combien de ligne il faut au maximum pour une fonction ?
                  Comment tu gère la remontée d'erreur ? Parce que un de mes programmes que je développe en C++ à peu ou pas de gestion d'erreur. J'aimerai m'améliorer sur ce point là.

                  • [^] # Re: Faire des tests, c'est bien, mais...

                    Posté par  (site web personnel) . Évalué à 2. Dernière modification le 24 juillet 2018 à 20:26.

                    Comment tu factorise ton code ? J'essaye de factoriser mon code mais parfois je ne sait pas trop quelle partie séparer d'une fonction dans une autre fonction. Combien de ligne il faut au maximum pour une fonction ?

                    Alors là, pour le coup, je vais perdre le peu de crédibilité qui pouvait me rester.

                    En fait, je n'ai pas de règle. C'est purement intuitif.

                    Parfois, je regarde juste mon code, et il ne me plait pas, sans que je puisse dire pour quelle raison. Je ne tarde alors pas de découvrir que je peux remplacer des groupes d'instructions par des appels à des fonctions de mes bibliothèques.

                    D'autres fois, j'écris du code et tout d'un coup je me dis "(censuré), mais j'ai déjà écrit ça. Bon sang, mais c'est bien sûr, c'est pour (une autre fonctionnalité présentant des similitudes avec celle que je suis en train d'écrire)". J'isole le code commun, je l'embarque dans une fonction, et je le déporte généralement dans une de mes bibliothèques. Souvent, cette fonction existe déjà, et il ne me reste plus qu'à l'utiliser.

                    Du coup, il m'arrive de réécrire du code parfaitement fonctionnel juste parce que, uniquement sur une intuition, je le trouve perfectible. Et je n'ai de cesse de le retoucher jusqu'à ce qu'il ai atteint une certaine forme de perfection. Oui, je sais, ça s'apparente plus à un travail d'artisan qu'à celui d'ingénieur, mais c'est viscérale, je ne peux m'en empêcher.

                    Tout ça ne t'est sans doute pas très utile, mais je ne vois pas trop ce que je pourrais dire de plus…

                    Comment tu gère la remontée d'erreur ? Parce que un de mes programmes que je développe en C++ à peu ou pas de gestion d'erreur. J'aimerai m'améliorer sur ce point là.

                    Les exceptions. C'est un mécanisme qui m'a semblé très tôt idéal pour gérer les erreurs. À tel point que j'utilisais ce mécanisme lorsque je développais encore en C, avant que je me mette au C++, alors que les exceptions n'existaient pas encore (la gestion des erreurs en C reposait ou repose encore essentiellement sur le traitement des valeurs de retour des fonctions). L'astuce, c'est que j'avais implémenté un mécanisme similaire aux exceptions du C++ à l'aide de la bibliothèque setjmp.

                    Généralement, mes fonctions ont un paramètre optionnel qui, si absent, lance une exception lorsqu'une erreur survient. Si l'exception n'est pas interceptée, mes bibliothèques sont conçues pour afficher le nom du fichier et le numéro de la ligne à l'origine de l'exception. Alternativement, soit j'intercepte l'exception et réalise l'action adéquate, soit je passe une valeur à la fonction qui lui indique de ne pas générer d'exception si une erreur survient, mais de retourner une valeur dédiée que je teste au retour de la fonction pour, encore une fois, réaliser l'action adéquate, généralement l'affichage d'un message informant l'utilisateur d'un problème.

                    Il y a un certains nombre de fonctionnalités dont je n'ai plus besoin de m'occuper de le gestion d'erreur. Par exemple, si j'ai un logiciel susceptible d'écrire un fichier qui existe déjà, j'ai tout un mécanisme qui crée automatiquement une copie du fichier avant de l'écraser, et qui rétablit automatiquement ce fichier si une erreur survient. Donc, si le programme échoue, j'ai le contenu du fichier comme si je n'avais jamais lancé le logiciel, même si le logiciel avait déjà commencé à écrire des données, soit, si le logiciel s'exécute sans problèmes, j'ai la nouvelle version du fichier, plus une copie de l'ancien fichier avec une extension du genre .bak. Et tout cela est géré automatiquement par mes bibliothèques.

                    Et j'ai ainsi toute une série de mécanismes qui se mettent en œuvre automatiquement avec une gestion adéquate des erreurs sans que j'ai à m'en préoccuper (gestion des arguments de la ligne de commande, gestion de la base de registre de mes logiciels, gestion des fichiers de configuration et des locales…).

                    Pour nous émanciper des géants du numérique : Zelbinium !

                • [^] # Re: Faire des tests, c'est bien, mais...

                  Posté par  (site web personnel) . Évalué à 3.

                  C'est possible de se passer de tests unitaires dans certains cas particulier comme celui décrit par Claude Simon, mais c'est un cas bien idéal, à savoir un code qui n'est manipulé que par une seule personne.

                  Dans 90% des cas, un code est amené à être modifié par un autre développeur que celui qui l'a écrit.

                  Une batterie de tests unitaires devient alors un outil indispensable pour un mainteneur afin de garantir que la rétrocompatibilité n'est pas cassée lors d'une modification, d'un refacto ou même simplement une correction de bug.

                  Dans des applications développées de manière itérative, il est souvent conseillé lors de la correction d'un bug, d'écrire un test qui échoue lorsque le bug est présent puis de réparer le bug et de valider la correction à l'aide de ce test unitaire.

                  Les tests unitaires permettent aussi à peu de frais de vérifier qu'un algorithme ou qu'une fonction marche dans des cas limite (entrée de taille nulle, entiers avec des valeurs de type MAX_INT, MIN_INT, etc).

                  Je pense que toi, Claude Simon, et d'après ce que tu dis, tu es parvenu à factoriser ton code de telle sorte que si un bug existait dans une de tes librairies, tu le détecterai automatiquement. C'est une bonne chose d'écrire avec un tel principe, c'est un des piliers d'un code de très bonne qualité de code (DRY = Don't Repeat Yourself). Dans ce cas, tu dois pouvoir te permettre de tester tes softs de manière plus intégrées (pas les libs en elle même mais le comportement des applications en situations réelles).

                  L'intérêt des tests unitaires / d'intégration c'est surtout de pouvoir automatiser les sessions de test et d'avoir un point de vue global sur l'état d'un projet.

                  • [^] # Re: Faire des tests, c'est bien, mais...

                    Posté par  . Évalué à 4. Dernière modification le 26 juillet 2018 à 09:26.

                    Je pense que toi, Claude Simon, et d'après ce que tu dis, tu es parvenu à factoriser ton code de telle sorte que si un bug existait dans une de tes librairies, tu le détecterai automatiquement

                    C'est ça que je trouve le plus étrange. Si le code est très factorisé, un changement va avoir des répercussions dans différentes parties du code, mais comme il n'y a pas de test, eh bien en fait on ne sait pas si on a introduit une régression sur un de ces parties touchées. Le bug va effectivement rapidement apparaître chez un des utilisateurs (quoique), mais moins rapidement qu'avec des tests…

  • # Et les tests aussi il faut les tester !

    Posté par  (site web personnel) . Évalué à 6.

    C'est bien d'écrire des tests, mais il faut aussi qu'ils soient
    - pertinents (tester juste pour faire monter le taux de couverture n'est pas un objectif en soi)
    - unitaires (j'ai trop souvent vu des tests unitaires plus complexes que le code testé lui même …)
    - exempts de bugs eux mêmes

    Pour ce dernier point, j'attire l'attention sur le concept de mutation : https://en.wikipedia.org/wiki/Mutation_testing
    L'idée générale est que si on fait varier le code du test mais que le résultat du test reste inchangé, c'est que le test n'est pas pertinent ou buggé

    J'ai des outils java/maven qui font ça plutôt bien et naturellement, j'aimerais en avoir en c/c++ avec cmake, il faut que je me penche sur cette question

  • # My $0.02

    Posté par  (site web personnel) . Évalué à 2.

    Personnellement j'utilise Boost pour les tests unitaire et fonctionnels. Dans la plupart des cas j'arrive quasiment à tout faire.

    Je sépare toujours mon application (le main en gros) du code avec une bibliothèque, ça me permet de pouvoir tester facilement le code sans recompiler chaque fichier testé.

    Ensuite pour les tests fonctionnels, j'utilise Boost.Process pour vérifier que les commandes invoquées répondent bien à ce que j'attends (erreur ou sortie standard correcte). Bon, ça nécessite pas mal de code, il faudrait peut-être que je trouve un moyen simple de faire des tests plus conviviaux.

    Mercurial fait un fichier déclaratif ou chaque ligne indentée avec $ signifie une commande à exécuter, j'aime bien ce principe.

    git is great because linus did it, mercurial is better because he didn't

    • [^] # Re: My $0.02

      Posté par  . Évalué à 1.

      Je ne savais pas qu'on pouvait faire du test fonctionnel avec Boost et ce Boost.Process m'a l'ai intéressant ! Pour l'instant mes test fonctionnelle sont en script sh mais j'hésite à passer dans un autre langage peut être Perl… ou autre. Je ne sait pas laquelle est le plus adaptés.

      • [^] # Re: My $0.02

        Posté par  (site web personnel) . Évalué à 2.

        Pouvoir est un grand mot, j'ai juste utilisé Boost.Process pour lancer une commande et vérifier les sorties. Ça ressemble à :

        const auto [code, out, err] = exec({ "rule-info", "0" });
        
        BOOST_TEST(!code);
        BOOST_TEST(out.size() == 7U);
        BOOST_TEST(err.size() == 0U);
        BOOST_TEST(out[0]  == "rule:        0");
        BOOST_TEST(out[1]  == "servers:     s1 s2 ");
        BOOST_TEST(out[2]  == "channels:    c1 c2 ");
        BOOST_TEST(out[3]  == "plugins:     p1 p2 ");
        BOOST_TEST(out[4]  == "events:      onCommand onMessage ");
        BOOST_TEST(out[5]  == "action:      drop");

        exec() est une fonction qui exécute boost::process::system, lit stdin, stderr et me renvoie un tuple avec le code de retour. C'est pas ce qu'il y a de plus convivial à écrire mais ça reste plutôt simple.

        git is great because linus did it, mercurial is better because he didn't

  • # Expect

    Posté par  (site web personnel) . Évalué à 6.

    Si ton programme est en ligne de commande, tu peux utiliser expect plutôt qu'un script Bash pour les tests d'intégration (ou tests fonctionnels).

    Cela permet d'interagir très simplement avec ton programme, en envoyant des données sur l'entrée standard et en vérifiant la sortie standard.
    Je l'utilise dans le paquet Debian de TapTempo : https://git.tuxfamily.org/taptempo/taptempo_deb_packaging.git/tree/debian/tests/tempo et ça fonctionne plutôt bien.

    • [^] # Re: Expect

      Posté par  . Évalué à 3.

      Très intéressant !

    • [^] # Re: Expect

      Posté par  . Évalué à 1.

      J'ai tester Expect et il est chiant à utiliser. Je sais pas pourquoi mais il a une limite au niveau du nombre de caractère. Là ou sur un script SH il n'y a aucun problème. Il faut faire particulièrement attention au espace et retour à la ligne car par exemple pour des condition il considère cela comme des erreur si tu n'a pas respecter la syntaxe.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.