2 points par GN⁺ 2024-02-19 | 1 commentaires | Partager sur WhatsApp

TestGen-LLM de Meta pour améliorer les tests unitaires automatisés

  • L'outil TestGen-LLM, développé par Meta, utilise des modèles de langage de grande taille (LLM) pour améliorer automatiquement les tests existants rédigés par des humains.
  • Les classes de test générées par TestGen-LLM passent avec succès une série de filtres qui garantissent une amélioration mesurable par rapport à la suite de tests d'origine, ce qui permet de résoudre le problème des hallucinations des LLM.
  • L'article décrit le déploiement de TestGen-LLM lors des test-a-thons pour les plateformes Instagram et Facebook de Meta.

Performances de TestGen-LLM

  • Lors de l'évaluation sur les produits Reels et Stories d'Instagram, 75 % des cas de test de TestGen-LLM ont été compilés correctement, 57 % ont réussi de manière fiable, et 25 % ont augmenté la couverture.
  • Lors des test-a-thons Instagram et Facebook de Meta, TestGen-LLM a amélioré 11,5 % de toutes les classes concernées, et les ingénieurs logiciels de Meta ont accepté 73 % des recommandations pour un déploiement en production.
  • Il s'agit du premier rapport à l'échelle industrielle portant sur du code généré par des LLM bénéficiant de ces garanties d'amélioration du code.

Opinion de GN⁺

  • TestGen-LLM est un outil qui pourrait révolutionner l'automatisation et l'amélioration de la qualité des tests logiciels, ayant réussi à améliorer des tests existants grâce à des modèles de langage de grande taille.
  • L'outil contribue de manière significative à la communauté de l'ingénierie logicielle en augmentant la couverture de tests dans un environnement industriel réel et en générant des cas de test fiables.
  • Les déploiements réussis de TestGen-LLM dans les test-a-thons de Meta montrent son potentiel d'intégration dans le développement de produits réels, ce qui pourrait améliorer l'efficacité et la stabilité du développement logiciel.

1 commentaires

 
GN⁺ 2024-02-19
Avis sur Hacker News
  • Dans une grande compagnie d’assurance où j’ai travaillé autrefois, la direction avait fixé comme objectif une couverture de tests de 80 % sur toute la base de code, si bien que les gens ont commencé à écrire des tests unitaires inutiles pour les getters/setters des DTO Java afin d’atteindre l’objectif.
    Évidemment, les développeurs ne pouvaient pas non plus modifier les règles de mesure de couverture de Sonar, et j’ai appris, jeune développeur, qu’en ne regardant que les KPI on peut encourager des comportements contraires à l’intention initiale.
    Quelques scénarios de tests E2E bien conçus auraient probablement été meilleurs pour la qualité du logiciel.

    • Dans une base de code similaire, j’ai soumis une PR qui simplifiait fortement une logique négligente écrite par des développeurs inexpérimentés, et qui réduisait la base de code de 20 % ; elle passait tous les tests et répondait à toutes les exigences utilisateur.
      Le problème, c’est que l’ancien code brouillon était très bien testé, avec 95 % de couverture. Le nouveau code avait 100 % de couverture, mais comme il était beaucoup plus court, la couverture globale baissait au lieu d’augmenter, et la PR ne passait pas.
      Le code restant n’était plus que du code d’UI Swing, difficile à tester et dont les tests avaient peu de sens ; plutôt que de passer une ou deux semaines à écrire des tests Swing, le lead dev a laissé l’ancien code quelque part dans le dépôt et a simplement fait pointer les tests vers ce code.
      Au final, des milliers de lignes de code mort, jamais appelées en production, sont restées dans le dépôt pour satisfaire Sonar.
    • Lors de mon premier stage aussi, la direction avait imposé un outil de qualité de code, qui comportait une règle « désactiver les nombres magiques ».
      Résultat : on s’est retrouvé avec des milliers de constantes dans les headers, du genre static const unsigned ONE = 1;, TWO = 2;, THREE = 3;.
    • À mon avis, la solution, c’est le mutation testing. Il ne s’agit pas simplement d’exécuter du code pour tromper la couverture, mais de forcer les tests à vérifier réellement l’implémentation.
      https://en.m.wikipedia.org/wiki/Mutation_testing
      Il existe des outils et frameworks pour presque tous les langages, par exemple stryker-mutator (C#, TypeScript), pitest (Java) et mutatest (Python).
    • Nous aussi, nous avions des scans Sonar obligatoires, et quand je suis arrivé, le lead technique se vantait d’avoir une note « A » en disant qu’il y avait « un niveau d’exigence élevé à maintenir ».
      En six ans d’expérience, je n’avais jamais vu une application aussi mal écrite ; ce n’était pas seulement une question de style, il y avait aussi beaucoup de parties réellement complètement cassées, mais personne ne savait ce qui n’allait pas.
      Je déteste vraiment Sonar. Il devrait servir uniquement à signaler des vulnérabilités, pas à dire qu’il faut renommer des variables ou « refactorer cette duplication de code ». Il y a déjà un backlog de tickets Jira ; qu’il évite aussi de nous dicter quoi faire et quand.
      Mais les managers adorent ce genre d’outils de jeu de pouvoir.
    • La phrase « lorsqu’une métrique devient un objectif, elle cesse d’être une bonne métrique » s’applique parfaitement.
      Le gros problème, c’est qu’on rend ces outils obligatoires, puis qu’on impose une énorme procédure bureaucratique pour échapper à leur bêtise. La semaine dernière encore, j’ai dû me battre parce qu’un outil obligatoire de qualité de code se plaignait que res.status(200).json() n’avait pas d’en-tête HSTS.
      Même en le configurant manuellement, même avec app.use(helmet()), il continuait à se plaindre, et il semblait finalement vouloir que tout le backend soit écrit dans un seul fichier. En réalité, HSTS se gère de façon plus élégante et automatique au niveau de l’ingress ou du load balancer.
      J’aurais pu passer une ou deux semaines à le marquer comme faux positif et à expliquer à un cadre supérieur ce qu’est HSTS pour obtenir son approbation, mais j’ai finalement ajouté res.sendJson(data, status = 200) au prototype de l’objet de réponse. C’est clairement une implémentation idiote, mais cela m’a fait comprendre que, dans les secteurs très bureaucratiques, les mauvais logiciels se construisent comme la somme de ce genre de mauvaises implémentations.
  • À la lecture du passage indiquant que « 75 % des cas de test de TestGen-LLM se compilaient correctement, 57 % passaient de manière stable, et 25 % augmentaient la couverture », le problème est que les tests générés par LLM semblent avoir de fortes chances « d’officialiser » des comportements bogués
    Ce serait particulièrement vrai pour une base de code dont la couverture de tests est déjà faible. Quand un humain écrit lui-même un nouveau test, il y a au moins quelqu’un pour juger si le système est stupide ou si le test est faux
    À tout le moins, ce genre de tests devrait être isolé dans un dossier de tests spécial et traité avec un niveau de suspicion approprié

    • Écrire des tests est, en pratique, une bonne occasion de trouver des bugs
      Cela dit, une base de code bien couverte permet d’effectuer de gros refactorings en toute sécurité, sans régression, et cela reste une propriété utile même si elle contient un bug et que le refactoring préserve ce bug tel quel
      Le risque d’un outil de génération de tests conçu pour encoder le comportement actuel, c’est qu’on peut tomber dans un faux sentiment de sécurité alors qu’il n’a en réalité encodé que le comportement actuel
      Peut-être que la solution serait de ne pas appeler cela des « tests », mais quelque chose comme des « instantanés de comportement ». Il faut que le nom indique qu’ils capturent le comportement actuel, pas le comportement correct
    • Je vois cela comme un cas particulier du problème plus général des changements indésirables. Quand on dispose d’un système automatisé capable de se modifier lui-même, comment savoir si un changement est réellement le bon changement voulu, ou s’il n’est qu’un symptôme issu d’un bug, d’un échec ou de la connaissance imparfaite de l’automatisation
      C’est pourquoi je pense qu’un certain degré de supervision humaine sera toujours nécessaire pour déterminer quel scénario s’est produit
      Ce genre de chose arrive dans toutes sortes de systèmes, et les gens ont tendance, comme ici, à penser qu’ajouter une couche d’automatisation supplémentaire résoudra le problème. Les tests ont été inventés à l’origine pour vérifier qu’un programme fonctionne correctement, mais si on automatise aussi cela, on retrouve le même problème dans un code plus gros, sous forme de tests plutôt que d’assertions
    • À l’inverse, dans une base de code à faible couverture de tests et où l’ancienneté moyenne des ingénieurs est d’environ un an, mettre en place le squelette de tests initial peut constituer un gros obstacle
      On peut ne pas savoir comment créer des factories pour les entrées auxiliaires nécessaires aux tests, tout en sachant comment le code lui-même est censé fonctionner
      Si un LLM met en place le squelette de tests et permet au développeur d’écrire facilement les vérifications de logique métier, cela peut apporter un gros gain
      En revanche, si les tests générés sont, comme la plupart des tests unitaires, trop couplés à l’implémentation, ils ralentiront le développement. Si corriger les tests individuellement devient trop difficile, on pourrait même voir des gens supprimer tous les tests lors de gros changements et les regénérer ensuite
    • Dans un système suffisamment grand, même les tests qui ne détectent que les changements de comportement ont de la valeur, quand bien même le comportement serait bogué
      Une partie du code peut dépendre de ce bug, et le corriger, que ce soit par accident ou intentionnellement, peut provoquer des problèmes plus graves
      Bien sûr, ce type de test ne peut pas remplacer les tests qui vérifient les véritables exigences
    • Je suis d’accord pour dire que, pour un nouveau projet ou un projet en développement actif, la génération automatique de tests est probablement une mauvaise idée
      Mais il existe d’innombrables systèmes legacy passés en mode maintenance avec une faible couverture, et dans ces cas-là, générer des tests qui valident le comportement actuel est très utile. Cela permet de vérifier que le reste n’a pas changé quand quelqu’un effectue une modification
  • Après avoir lu le PDF, il semble qu’il s’agisse « simplement » de générer des tests qui passent de façon répétée, c’est-à-dire qui ne sont pas flaky
    L’objectif principal est de créer une suite de tests de régression avec des tests qui figent le comportement du code existant, pas de remplacer les tests écrits par des développeurs qui connaissent les exigences fonctionnelles
    Dans une entreprise où je travaillais il y a près de 20 ans, nous avions aussi essayé AgitarOne, qui promettait de générer automatiquement des cas de test explorant le comportement du code Java. Il permettait aussi de créer presque automatiquement des tests qui passaient, utilisables comme suite de tests de régression
    Personnellement, je n’aimais pas ça. Cela produisait trop de choses, et la direction comprenait qu’une hausse de la couverture signifiait une hausse de la qualité. Je me demande dans quelle mesure l’approche LLM dont parle FB ici est meilleure qu’à l’époque
    http://www.agitar.com/solutions/products/agitarone.html

    • Une bonne partie des tests unitaires générés de cette manière deviennent davantage des détecteurs de changements que des tests de régression. Il y a une grande différence entre un test qui échoue quand le code change et un test qui échoue quand un bug est réintroduit
      Tant qu’un LLM ne pourra pas déterminer l’exactitude réelle sans s’appuyer sur l’hypothèse qu’un bon test passe, ou sur un oracle, il semble difficile d’aller plus loin. Il faudrait inclure d’une manière ou d’une autre les attentes de comportement dans le prompt
    • Cela peut aussi lier le système à des comportements accidentels
      La valeur d’un test est de garantir qu’on ne casse pas quelque chose qui compte pour quelqu’un, pas de figer pour toujours tous les comportements de cas limites presque inutilisés qui ne sont que des artefacts d’une implémentation particulière
  • Par expérience, écrire des tests est généralement un excellent moyen d’évaluer la qualité du code
    Si les tests sont complexes ou si atteindre une bonne couverture est difficile, il est très probable que le code testé doive être amélioré

    • La testabilité du code est effectivement un bon critère de qualité du code. Les éléments qui rendent le code difficile à tester sont généralement liés à du code de faible qualité
      Un code à faible couplage, forte cohésion et faible complexité devrait être facile à tester unitairement
  • Dans l’évaluation des produits Reels et Stories d’Instagram, 75 % des cas de test de TestGen-LLM se compilaient correctement, 57 % passaient de manière stable, et 25 % augmentaient la couverture
    Lors des opérations de test sur Instagram et Facebook chez Meta, l’outil aurait amélioré 11,5 % de l’ensemble des classes auxquelles il a été appliqué, et 73 % des recommandations auraient été approuvées par les ingénieurs logiciel de Meta pour un déploiement en production
    Je ne sais pas si ce sont de bons taux. Il faudrait en lire davantage pour savoir si ce qui n’était pas acceptable relevait des petites erreurs que l’on attrape en revue de code, ou de problèmes graves. Un ingénieur humain avec un taux d’échec de 25 % ne serait peut-être pas très utile, selon les types d’échecs
    Je me demande aussi si la mission globale consistant à automatiser la génération de tests unitaires pour le code Android va dans la bonne direction. Les adeptes du TDD doivent se retourner dans leur tombe, ou dans leur lit chez eux. Cela dit, j’imagine qu’ils auraient quand même ajouté une nuance à la fin

    • Chez Facebook, il y a beaucoup de code sans tests, et personne ne reçoit de points PSC pour corriger ça
  • Chez unlogged.io, pendant un temps, l’axe principal était la génération automatique de tests JUnit, mais cela n’a pas vraiment décollé, pour plusieurs raisons.
    Le code de test généré était trop volumineux, les développeurs ne voulaient pas le maintenir, il ne simulait pas de vrais scénarios, et la couverture de code était une métrique de vanité. Les développeurs trouvaient des contournements pour atteindre les objectifs avec des scénarios dénués de sens.
    Nous travaillons maintenant à fournir des tests de rejeu no-code qui simulent tous les scénarios opérationnels uniques et que les développeurs peuvent rejouer localement avec les dépendances externes mockées.
    Pour info, je suis le fondateur d’unlogged.io.

  • J’aimerais aller dans la direction inverse. Entrer les critères d’acceptation, générer des tests qui les vérifient, puis générer le code qui fait passer ces tests.
    Avec Copilot, on peut parfois faire quelque chose de similaire de manière limitée, mais je ne comprends pas pourquoi j’ai l’impression que personne ne se concentre sur cet ordre-là.

  • TestGen-LLM est vraiment un objet bizarre. Je peux imaginer l’utiliser comme première étape d’un refactoring ou d’une réécriture, mais le fait que l’article mette l’accent sur la couverture de code donne vraiment l’impression d’un jugement complètement à côté de la plaque.
    Cela peut être utile si une organisation est déjà dysfonctionnelle parce qu’elle exige une couverture élevée, mais TestGen-LLM ne rendra en rien le code du projet meilleur et ne fera qu’ajouter de la friction à la mise en œuvre de vraies améliorations.
    Générer des tests de cas limites qui peuvent passer ou non serait bien plus utile, mais TestGen-LLM s’appuie sur les erreurs de compilation et les tests en échec pour filtrer les déchets produits par le LLM.
    Le fait que l’article ne donne aucun exemple de test généré me fait soupçonner qu’ils sont probablement aussi amateurs que les autres codes générés par LLM que j’ai vus jusqu’ici.

    • J’ai récemment dû refactorer un projet sans aucun test, et le fait que le LLM génère automatiquement des ébauches de tests m’a été très utile.
      Cela m’a même aidé à comprendre ce que le code cherchait à faire.
  • Il est intéressant que des employés de Meta aient écrit un article de 12 pages pour promouvoir l’IA destinée aux développeurs, au point de sortir un diagramme de Sankey.
    Je me trompe peut-être, mais s’ils présentent les choses ainsi, il me semble qu’ils devraient aussi fournir des informations reproductibles.
    Ce n’est pas une théorie du complot, c’est juste que je n’ai pas accès au niveau de données que Meta utilise pour l’entraînement. Je me demande s’ils ont publié quelque chose.

    • Si c’est comme chez Google, ce sera tellement lié à l’infrastructure interne et au monorepo que ce sera difficile à publier.
    • S’il s’agit d’un article FSE 2024, l’artefact devrait probablement inclure une théorie ou une évaluation formelle.
  • Je me demande combien coûtera, à l’avenir, la maintenance d’un énorme corpus de tests générés automatiquement.
    Il ne faut pas seulement fournir un moyen de générer des cas de test, mais aussi une méthode automatisée pour les mettre à jour.