Le nouveau générateur de tests basé sur les LLM de Meta
(read.engineerscodex.com)Le nouveau générateur de tests basé sur les LLM de Meta offre un aperçu de l’avenir du développement
- Meta a publié un article intitulé "Automated Unit Test Improvement using Large Language Models at Meta".
- Cet article montre comment l’IA peut accélérer le développement et réduire les bugs logiciels.
- En intégrant les LLM au workflow des développeurs, il propose des améliorations logicielles précises et complètes pour améliorer la couverture de code existante.
Points clés
- TestGen-LLM utilise l’approche « Assured LLM-based Software Engineering » (Assured LLMSE).
- Il adopte une approche d’ensemble qui génère des propositions d’amélioration du code à l’aide de plusieurs LLM, prompts et hyperparamètres, puis sélectionne la meilleure.
- TestGen-LLM a été spécialement conçu pour améliorer des tests existants écrits par des humains.
Statistiques
- Lors de l’évaluation des produits Reels et Stories d’Instagram, 75 % des cas de test générés par TestGen-LLM ont pu être compilés correctement, 57 % ont réussi de manière fiable, et 25 % ont augmenté la couverture.
- TestGen-LLM a permis d’améliorer 10 % de toutes les classes auxquelles il a été appliqué, et les développeurs ont accepté 73 % des propositions d’amélioration de tests pour les déployer en production.
- Lors d’un « test-a-thon » où les ingénieurs de Meta généraient des tests pour augmenter la couverture de test d’Instagram, la médiane des lignes de code ajoutées par les tests de TestGen-LLM était de 2,5.
Enseignements pratiques
- C’est un bon exemple de la manière dont les LLM peuvent améliorer efficacement la productivité des développeurs et la fiabilité logicielle.
- La véritable valeur des LLM réside dans leur capacité à trouver et à détecter des edge cases inattendus.
- Pour utiliser les LLM en production, il faut de l’orchestration, des pipelines et du traitement.
Fonctionnement de TestGen-LLM
- TestGen-LLM applique une série de filtres sémantiques aux solutions candidates générées par les LLM internes de Meta afin de ne conserver que les tests les plus utiles.
- Filtre 1 : possibilité de compilation, filtre 2 : exécution (réussite ou échec du test), filtre 3 : instabilité, filtre 4 : amélioration de la couverture.
- Ces filtres de traitement garantissent l’amélioration de la suite de tests.
Conclusion
- Cet article constitue un bon moyen de suivre les progrès des LLM dans le domaine de la fiabilité logicielle, que de nombreux développeurs utilisent déjà.
- Les LLM deviendront capables de trouver des bugs et de tester des systèmes logiciels de plus en plus complexes.
L’avis de GN⁺
- Cet article offre un éclairage intéressant sur l’impact potentiel de l’intelligence artificielle sur l’avenir du développement logiciel.
- Des outils comme TestGen-LLM peuvent grandement aider à automatiser le travail des développeurs et à améliorer l’efficacité.
- Les avancées de ces technologies vont dans le sens d’une réduction de la complexité du développement logiciel, d’une amélioration de la qualité et d’un gain de temps pour les développeurs.
1 commentaires
Commentaires sur Hacker News
Il est intéressant de voir cette tendance à utiliser les LLM d’abord pour écrire du code de test plutôt que pour l’implémentation
C’est peut-être parce que j’ai trop fait de TDD, mais je considère que les tests décrivent comment le système doit se comporter, et que cette partie doit être définie par un humain. Le code doit s’inscrire dans les garde-fous posés par les tests
Cela dit, un LLM peut aider à repérer les zones où la spécification manque de précision. Ce qui se passe ici, c’est peut-être une façon de lui faire proposer des tests unitaires sur les parties insuffisamment spécifiées
Même avant les LLM, je me suis parfois dit que si on écrivait tous les tests à l’avance, un singe avec une machine à écrire pourrait peut-être produire l’application
Ce sont des tests qui définissent non pas comment un humain pense que le code devrait fonctionner, mais comment la codebase fonctionne réellement aujourd’hui
Cela permet de réécrire, refactorer ou repenser l’architecture en minimisant les régressions. Le problème avec beaucoup de code legacy, c’est que personne ne comprend le comportement attendu, et parfois même les utilisateurs pensent que le système devrait fonctionner autrement que ce qu’il fait réellement
Donc, sauf si l’on vise explicitement un changement, le plus important est de ne pas modifier le comportement
L’idée de base est d’exprimer sous forme de « propriétés » des affirmations plus faibles sur le comportement du code que dans un système de preuve fermé et complet, puis de les vérifier dans des limites fondamentalement probabilistes
L’exemple classique est l’inversion d’une chaîne. Si l’on inverse une chaîne deux fois, on devrait généralement retrouver l’entrée. Avec une seule ligne de code, on peut vérifier autant de cas limites Unicode étranges que le temps et l’électricité le permettent
L’exemple paraît trivial, mais j’ai vu des experts CUDA travaillant sur l’autodifférentiation et les kernels — qui sont devenus PyTorch — utiliser cette approche de manière extrêmement efficace, au point d’obtenir une fiabilité du code multipliée par cinq pour environ moitié moins d’efforts et de coûts
Cela ne marche pas toujours, mais quand ça colle, c’est excellent, et les LLM semblent capables de s’approcher assez près d’un exemple Hypothesis plutôt que de partir de zéro
À l’inverse, écrire des tests consiste à décrire ce que l’application doit faire dans un langage extrêmement verbeux et contraint, puis à écrire des dizaines ou des centaines de lignes de configuration pour glisser quelques if/else vaguement embellis
Dans des langages comme C++ ou Java, les tests unitaires sont faits de pure monotonie, donc il n’est pas du tout surprenant qu’on ait l’instinct de confier ce travail à un LLM
J’ai souvent vu des ingénieurs passer plusieurs jours à écrire du code, puis se sentir obligés d’ajouter après coup quelques tests censés « prouver » que le système fonctionne. La couverture est faible et les tests sont généralement fragiles
Pour un ingénieur qui pense et travaille ainsi, un système de ce genre ressemblera à un cadeau tombé du ciel
J’ai même eu un manager qui interdisait d’écrire les tests d’abord parce que cela ralentissait le développement. Heureusement, j’étais en mission et j’ai pu ignorer ça en disant : « parlez-en à mon responsable ». Il pensait probablement comme les ingénieurs mentionnés plus haut
Vu sous un autre angle, la plupart des développeurs détestent la documentation. S’il existait une IA qui rédigeait une excellente documentation à partir du code, ils adoreraient. Et pour ces développeurs, une excellente documentation est une documentation qu’ils n’ont pas besoin d’écrire eux-mêmes
Le code de test est souvent considéré comme du code de moindre priorité, qu’on confie davantage à des ingénieurs juniors, ce qui semble aller exactement dans la mauvaise direction
Je n’ai pas envie de tout passer en revue, mais il y a une partie qui me semble particulièrement à côté de la plaque
Je précise que j’avais lu l’article original très rapidement juste après sa publication, et que je le reparcours seulement maintenant, donc mes souvenirs sont un peu flous
Le billet disait que la plupart des tests de TestGen-LLM de Meta ne couvraient que 2,5 lignes supplémentaires, mais qu’un test en couvrait 1326, et que la valeur de ce seul test était « exponentiellement plus grande », l’intérêt du LLM étant de penser activement hors du cadre pour détecter des cas limites inattendus
Mais l’expression même « exponentiellement plus grande valeur » devrait déjà déclencher l’alarme à bullshit. En lisant l’article, on voit que les auteurs décrivent cette couverture de 1326 lignes comme un test unique ayant touché le jackpot, et ils disent que la couverture additionnelle réaliste à attendre d’un test TestGen-LLM isolé a une médiane de 2,5 lignes
Les auteurs ne parlent ni de « cas limites inattendus » ni de « pensée hors du cadre ». Ils le présentent plutôt comme un cas exceptionnel : soit un test a touché une branche d’un switch atroce, soit c’est un artefact de la manière dont la couverture de code a été calculée
Il est également frappant qu’ils n’approfondissent pas davantage ce point dans la section « résultats qualitatifs ». Une explication inexacte n’aide personne. Internet compte déjà bien trop de gens qui font semblant d’avoir compris ce qu’ils font semblant d’avoir lu
J’ai modifié le texte pour préciser plus clairement que certaines interprétations relevaient de mon opinion personnelle
Ce billet est moins un résumé de l’article qu’un commentaire sur ce que ses résultats signifient. Après tout, Hacker News est un lieu de discussion
Cela dit, je pense toujours que la partie sur la « valeur exponentiellement plus grande » reste juste. Le fait qu’un LLM puisse tomber par hasard sur un « jackpot » en matière de couverture de test est précisément le cœur de sa valeur
Si l’on continue à essayer différentes combinaisons et qu’on obtient ne serait-ce qu’un seul jackpot comme dans l’article, cela a déjà énormément de valeur pour une équipe
Il peut s’agir d’un test qui n’était pas évident à écrire à la main ou qui était trop pénible pour qu’un humain prenne le temps de le faire
Pour quelqu’un qui a déjà passé bien trop de temps, dans des codebases de Big Tech (F/G), à savoir quoi tester mais à ne chercher que comment le tester, cette valeur est importante
Il est évident que cette équipe aussi est poussée par le nombre de lignes de code et le nombre de diffs. Au final, ce ne sera qu’un outil de génération de code qui ajoutera une montagne de code difficile à déboguer de plus
Les bons tests sont difficiles, et la couverture n’est pas automatiquement une bonne chose
Il est facile d’écrire trop de tests au point de figer le programme et de fabriquer en pratique un simple détecteur de changements. On en arrive à quelque chose comme : « Ah, vous avez changé un truc, tous les tests sont cassés. Pas grave, on n’a qu’à demander au LLM de tout régénérer ! Couverture à 100 % ! Incroyable ! Quel progrès ! »
Pour moi, c’est justement l’occasion de vérifier si ce changement était intentionnel. Sans cela, comment savoir si le programme fait bien ce qu’il est censé faire ?
Au-delà de ça, il faut lire ce que cinq autres personnes considèrent comme de bons tests. Nous écrivons tous mal les tests, chacun à sa manière
Ensuite, à chaque changement, les développeurs cliquaient naturellement sur le bouton de régénération et committaient le tout. Les diff étaient abondants, mais le signal restait douteux
Après avoir travaillé dans l’industrie des semi-conducteurs, notamment en lithographie computationnelle, où la conception pilotée par les tests est la norme, j’ai du mal à être d’accord
Cela ne veut pas dire qu’il faut toujours écrire les tests avant le code de production. Mais les tests font partie du code au même titre que n’importe quelle autre partie de la base de code, et ils doivent absolument être écrits en même temps que le code qu’ils testent
La partie la plus importante d’un test, c’est qu’il montre l’intention du développeur. Une suite de tests montre comment le code doit être utilisé, ce qu’il fait, ce qu’il ne fait pas, et dans quel but il a été écrit
Ainsi, lorsqu’un autre développeur utilise ou modifie ce code, il n’a pas besoin de parcourir la base de code comme Sherlock Holmes à la recherche d’indices
Si les tests ne racontent pas une histoire, alors ils sont mal écrits
Tant que les ordinateurs ne liront pas dans nos pensées et ne comprendront pas mieux nos intentions, un générateur basé sur l’IA/les LLM ne pourra pas faire ce travail à notre place
Bien sûr, si l’unique objectif d’une suite de tests est d’obtenir une coche verte avant le commit et d’afficher un joli chiffre de couverture, alors l’IA peut doubler votre productivité
Les générateurs automatiques de code nous aideront à écrire encore plus de mauvais code à la vitesse de la lumière. Si quelqu’un se plaint que le code est gonflé de boilerplate, difficile à comprendre, il suffira de lui dire de laisser l’IA s’en charger. Puisque ça a marché sur vous
C’est peut-être vraiment à cela que ressemblera l’avenir du développement, mais ce n’est pas l’avenir que j’espère
Ce qui est décrit ici semble viser les tests du « cœur » du code. Des tests qui font aussi office de documentation, de validation et, en partie, de garantie de stabilité
D’autres tests, comme le fuzzing, apportent une valeur totalement différente. À mon avis, les tests pilotés par l’IA peuvent occuper un espace du côté de la queue de distribution : la grande masse de tests à faible valeur, laissés de côté faute de temps et d’énergie humaine
C’est aussi ainsi que je vois l’état actuel des outils d’IA. Ce sont des outils d’assistance cognitive
Si cette direction de recherche ne produit pas des résultats assez concrets dans les prochaines années, j’en serais plutôt surpris
Je recite, en l’ayant légèrement retouché, ce que j’avais écrit quand l’article est sorti. Leur texte présente mal les statistiques
https://news.ycombinator.com/item?id=39406726
Le résumé ne correspond pas au contenu réel de l’article. Il se lit comme si l’on parlait de proportions au niveau des cas de test, du type : « 75 % compilent correctement, 57 % passent de manière stable, 25 % augmentent la couverture »
Or le rapport réel parle au niveau des classes de test, et chaque classe contient un ou plusieurs cas de test
Cela signifie : « dans 75 % des classes de test, il y avait au moins un nouveau cas de test qui compilait correctement », « dans 57 % des classes de test, il y avait au moins un cas de test qui compilait correctement et passait de manière stable », et « dans 25 % des classes de test, il y avait au moins un cas de test qui compilait, passait et augmentait la couverture de lignes par rapport aux autres classes de test sur la même cible de build »
Ce sont deux formulations complètement différentes. Chaque tentative d’extension d’une classe de test peut comporter plusieurs tentatives de génération de cas de test, et il y a même une note précisant que le taux de réussite par cas de test est généralement bien inférieur au taux de réussite par classe de test
Pourtant, dans la conclusion, ils reformulent encore les résultats de manière incorrecte, comme dans le résumé. Ils écrivent qu’en mode expérimental, TestGen-LLM obtenait un taux de réussite de 25 % par cas de test, et qu’en assouplissant l’exigence de couverture de lignes pour ne demander que la compilation et l’exécution réussie, ce taux montait à 57 %
J’ai de la peine pour les gens qui devront maintenir cet affreux code legacy produit par LLM à l’avenir
Ça risque d’être hideux
En fait, ça pourrait même être assez similaire, avec des commentaires et une documentation plus fournis, et peut-être moins de risques d’être activement faux
Les LLM ne s’amélioreront absolument jamais par rapport à aujourd’hui et n’ont pas du tout progressé ces deux dernières années. Ce ne sont que de brillantes chaînes de Markov
On ne peut les utiliser pour écrire du code que lorsque des gens qui ne savent pas programmer commitent aveuglément en production sans aucune revue
Pour quelqu’un qui sait coder, ça ne peut pas être utile et ça n’améliore pas la productivité
J’ignorerai ce bavardage LLM qui ne change absolument pas le monde, et vous devriez absolument faire de même
Si l’on ne sépare pas bien les choses, cela devient facilement un environnement hostile aux développeurs : un manager incompétent exige une couverture élevée, pendant que des juniors enthousiastes glissent en douce de grandes quantités de tests générés par IA
Au final, on se retrouve à devoir obtenir le tampon de code de test généré par LLM, difficile à maintenir, à chaque soumission de travail
Certaines parties de l’écriture des tests peuvent aller plus vite, mais rien ne garantit que la maintenance ira plus vite. Il en va de même pour la maintenance du code testé. Rien ne garantit non plus que de bons tests seront générés
Le processus pénible d’écriture des tests aide aussi souvent les développeurs à vérifier la conception dès le début. Si quelque chose est difficile à tester, c’est généralement que la conception n’est pas bonne ; par exemple, dans un contexte où le code doit être écrit avec d’autres personnes, le contrat du composant n’est souvent pas suffisamment abstrait
Un point facile à manquer, c’est que les tests sont du code sacrifiable. La plupart ne détecteront jamais rien de toute leur vie, et ce n’est pas grave. Ils apportent une réassurance automatisée et réduisent les fausses pistes en cas d’échec
Mais investir au maximum dans des garde-fous probabilistes n’apporte pas toujours une récompense. Plus la couverture se rapproche du plafond, plus l’utilité marginale diminue. Sauf pour des chemins d’exécution très fréquentés comme ceux d’une bibliothèque standard, afficher une couverture élevée est rarement récompensé
En plus, il faut presque toujours tout un écosystème de tests — pas seulement des tests unitaires, mais aussi des tests d’intégration, des tests système, etc. Est-ce qu’un LLM va s’asseoir en réunion de conception, comprendre l’architecture et écrire aussi ce type de tests ? Ou bien ses capacités vont-elles être exagérées au point d’évincer le travail réellement nécessaire ?
Quand on investit des efforts dans les tests, il faut savoir juger « ce qui est pertinent », non seulement au moment de l’écriture, mais aussi au moment de la conception et de la maintenance. Les humains sont plutôt bons à cela, mais pas les outils d’IA
Là où les LLM peuvent faire gagner du temps, c’est sur les frappes clavier d’un développeur expérimenté qui a déjà une bonne intuition de ce qu’il faut ou non tester. Mais ils peuvent aussi devenir une nuisance en introduisant subrepticement dans le code des choses peu pertinentes, et c’est déjà arrivé
Je ne veux pas d’une économie qui produit des frappes clavier. Je veux un ensemble de frappes réfléchies et pertinentes. Et j’espère que le second restera bien distinct du premier, de sorte que son utilité objective — ou son absence — apparaisse avec le temps
Je l’ai déjà essayé avec GPT-4
Je lui ai montré un module TypeScript et lui ai demandé de générer des tests unitaires ; il a produit des tests fonctionnels couvrant non seulement le chemin nominal, mais aussi quelques cas limites
ChatGPT dépasse les attentes sur bien des aspects. Les tests semblent faciles au regard des capacités de GPT
La semaine dernière, je lui ai fait écrire du code Python qui parcourt un AST pour créer un graphe React Flow et des composants. Je n’ai rien corrigé ; après quelques itérations de feedback sur le prompt, cela a très bien fonctionné. J’ai vu beaucoup de capacités intéressantes du même genre avec GPT
Comment l’IA sait-elle quels tests écrire ?
C’est une expérience intéressante, mais cela me laisse un peu sceptique. À mon avis, la meilleure façon pour l’IA d’aider en développement logiciel est de répondre quand un programmeur pose des questions sur son propre code ou sur celui d’un autre. Cela inclura parfois des suggestions de code, mais pas nécessairement à chaque fois
Elle devrait pouvoir répondre à des questions comme : « Y a-t-il un moyen de simplifier ce code ? », « Quels types d’entrées pourraient provoquer une erreur ? »
L’IA devrait nous aider à comprendre le code et à comprendre comment l’améliorer. Elle ne peut pas savoir ce que nous voulons si nous ne lui disons pas quoi faire ; il ne faut donc pas la laisser tout écrire toute seule
Les tests en sont un bon exemple. Qu’est-ce que nous voulons tester ?
Voilà enfin de la génération de code par IA qui paraît convaincante