Analyse de mutation, ou comment tester des tests

Il n'y a jamais trop de tests - tout le monde le sait. Les mèmes sur les tests unitaires et d'intégration ne sont plus très amusants. Mais nous ne savons toujours pas s'il est possible de se fier aux résultats des tests, et quel pourcentage de couverture nous permettra d'éviter les bugs en production. Si des changements fatals dans les tests de saut de code, sans affecter leur résultat, la solution se suggère alors - vous devez tester les tests!



L'approche pour automatiser cette tâche a été le rapport de Mark Langovoy de Frontend Conf . La vidéo et l'article sont courts et les idées fonctionnent très bien - vous devez en prendre note.


À propos du conférencier: Mark Langovoi ( marklangovoi ) travaille à Yandex dans le projet Yandex.Tolok . Il s'agit d'une plateforme de crowdsourcing permettant de baliser rapidement de grandes quantités de données. Les clients téléchargent des données qui, par exemple, doivent être préparées pour être utilisées dans les algorithmes d'apprentissage automatique, et fixer un prix, et de l'autre côté, les exécuteurs peuvent effectuer des tâches et gagner de l'argent.

Pendant son temps libre, Mark développe les Krasnodar Devodar Developer Days, l'une des 19 communautés informatiques dont nous avons invité les militants à Frontend Conf à Moscou.

Test


Il existe différents types de tests automatisés.


Lors des tests unitaires populaires , nous écrivons des tests pour les petites pièces (modules) d'une application. Ils sont faciles à écrire, mais parfois lors de l'intégration avec d'autres modules, ils peuvent ne pas se comporter exactement comme prévu.

Pour éviter cela, nous pouvons écrire des tests d'intégration qui testeront ensemble le fonctionnement de nos modules.


Ils sont un peu plus compliqués, nous allons donc aujourd'hui nous concentrer sur les tests unitaires.

Tests unitaires


Tout projet qui souhaite au moins une stabilité minimale écrit des tests unitaires.

Prenons un exemple.

class Signal { on(callback) { ... } off(callback) { const callbackIndex = this.listeners.indexOf(callback); if (callbackIndex === -1) { return; } this.listeners = [ ...this.listeners.slice(0, callbackIndex - 1), ...this.listeners.slice(callbackIndex) ]; } trigger() { ... } } 

Il existe une classe Signal - il s'agit d'un émetteur d'événements, qui a une méthode on pour s'abonner et une méthode off pour supprimer un abonnement - nous vérifions si le rappel est contenu dans le tableau des abonnés, puis supprimez-le. Et, bien sûr, il existe une méthode de déclenchement qui appellera les rappels signés.

Nous avons un test simple pour cet exemple qui appelle les méthodes on et off, puis le déclencheur, afin de vérifier que le rappel n'a pas été appelé après la désinscription.

 test('off method should remove listener', () => { const signal = new Signal(); let wasCalled = false; const callback = () => { wasCalled = true; }; signal.on(callback); signal.off(callback); signal.trigger(); expect(wasCalled).toBeFalsy(); }); 

Critères d'évaluation de la qualité


Quels sont les critères d'évaluation de la qualité d'un tel test?

La couverture de code est le critère le plus populaire et le plus connu qui indique le pourcentage de lignes de code exécutées lors du test.


Vous pouvez avoir une couverture de 70%, 80% ou 90% du code, mais cela signifie-t-il que lorsque vous récupérerez la prochaine version pour la production, tout ira bien ou quelque chose pourrait mal se passer?

Revenons à notre exemple.

Vendredi soir, vous êtes fatigué, vous terminez la prochaine fonctionnalité. Et puis vous tombez sur ce code que votre collègue a écrit. Quelque chose en toi vous semblait compliqué et effrayant.

  ...this.listeners.slice(0, callbackIndex - 1), ...this.listeners.slice(callbackIndex) 

Vous avez décidé que vous pouvez probablement simplement effacer le tableau:

 class Signal { ... off(callback) { const callbackIndex = this.listeners.indexOf(callback); if (callbackIndex === -1) { return; } this.listeners = []; } ... } 

Je me suis engagé, monté le projet et envoyé à la production. Tests réussis - pourquoi pas? Et il est allé se reposer dans un bar.



Mais soudain, tard dans la nuit, un appel a sonné dans le récepteur, que tout tombait, les gens ne pouvaient pas utiliser le produit, et en général, les affaires gaspillaient de l'argent! Vous brûlez, vous êtes menacé de licenciement.



Comment y faire face? Que faire des tests? Comment attraper de telles erreurs stupides primitives? Qui testera les tests?

Bien sûr, vous pouvez embaucher une armée d'ingénieurs QA - laissez-les s'asseoir et applaudissez simplement notre application.



Ou embauchez l'automatisation de l'assurance qualité. On peut leur reprocher d'écrire des tests - pourquoi écrire par vous-même s'il y a des personnes spéciales pour cela?

Mais en fait, cela coûte cher, alors nous parlerons aujourd'hui d'analyse mutationnelle ou de test mutationnel.

Test de mutation


C'est un moyen d'automatiser le processus de test de nos tests. Son but est d'identifier les tests inefficaces et incomplets, c'est-à-dire qu'il s'agit en fait de tests .

L'idée est de changer des morceaux de code, d'exécuter des tests sur eux, et si les tests ne tombent pas, alors ils sont incomplets.

Des modifications sont apportées à l'aide de certaines opérations - mutateurs . Ils remplacent, par exemple, plus par moins, multiplient par division et d'autres opérations similaires. Les mutateurs peuvent changer des morceaux de code, remplacer des conditions dans while, zéro tableau au lieu d'ajouter un élément au tableau.


À la suite de l'application de mutations au code source, il mute et devient un mutant .

Les mutants sont divisés en deux catégories:

  1. Tué - ceux dans lesquels nous avons pu identifier les écarts, c'est-à-dire sur lesquels au moins un test est tombé.
  2. Les survivants sont ceux qui se sont enfuis de nous et ont amené le bogue en production.

Pour évaluer la qualité, il existe une métrique MSI (Mutation Score Indicator) - le pourcentage de mutants tués et survivants. Plus la différence entre les tests de couverture de code et MSI est grande, plus le pourcentage de couverture de code reflète la pertinence de nos tests.

C'était un peu de théorie, et considérons maintenant comment il peut être utilisé en JavaScript.

Solution Javascript


Il n'y a qu'un seul outil de test de mutation en développement actif dans JavaScript - Stryker . Ce nom a été donné à l'instrument en l'honneur du personnage de X-man William Stryker - le créateur de "Weapon X" et un combattant avec tous les mutants.



Stryker n'est pas un coureur de test comme Karma ou Jest; ce n'est pas non plus un cadre de test comme Mocha ou Jasmine. Il s'agit d'un cadre de test de mutation qui complète votre infrastructure actuelle.

Système de plugin


Stryker est très flexible, entièrement construit sur un système de plug-in, dont la plupart sont écrits par des développeurs Stryker.


Il existe des plugins pour exécuter des tests sur Jest, Karma et Mocha. Il existe une intégration avec les frameworks Mocha (stryker-mocha-framework) Jasmine (stryker-jasmine) et des ensembles de mutateurs prêts à l'emploi pour JavaScript, TypeScript et même pour Vue:

  • stryker-javascript-mutator;
  • stryker-typescript;
  • stryker-vue-mutator.

Les mutateurs pour React sont inclus dans le stryker-javascript-mutator. En plus de cela, vous pouvez toujours écrire vos propres mutateurs.

Si vous devez convertir le code avant de l'exécuter, vous pouvez utiliser des plugins pour Webpack, Babel ou TypeScript.


Tout est mis en place relativement simplement.

La configuration


La configuration n'est pas difficile: il vous suffit de spécifier dans la configuration JSON quel runner de test (et / ou framework de test et / ou transpiler) vous utilisez, ainsi que d'installer les plugins appropriés depuis npm.

L'utilitaire de console simple stryker-cli peut faire tout cela pour vous dans un mode question / réponse. Elle vous demandera ce que vous utilisez et se configurera.

Comment ça marche


Le cycle de vie est simple et comprend les étapes suivantes:

  • Lecture et analyse de la config. Stryker télécharge la configuration et l'analyse de divers plugins, paramètres, exclusion de fichiers, etc.
  • Téléchargez les plugins en fonction de la configuration.
  • Exécution de tests sur le code source afin de vérifier si les tests sont pertinents en ce moment (du coup ils sont déjà cassés).
  • Si tout va bien, un ensemble de mutants est généré à partir des fichiers que nous avons permis de muter.
  • Exécution de tests sur des mutants.



Ci-dessus est un exemple de démarrage de Stryker:

  • Stryker démarre;
  • lit une config;
  • charge les dépendances nécessaires;
  • trouve les fichiers qui muteront;
  • exécute des tests sur le code source;
  • crée 152 mutants;
  • exécute des tests dans 8 threads (dans ce cas, en fonction du nombre de cœurs de processeur).

Ce n'est pas un processus rapide, il est donc préférable de le faire sur certains serveurs CI / CD.

Après avoir passé tous les tests, Stryker donne un bref rapport sur les fichiers avec le nombre de mutants créés, tués et survivants, ainsi que le pourcentage du rapport des mutants tués aux survivants (MSI) et les mutateurs qui ont été utilisés.

Ce sont des problèmes potentiels qui n'étaient pas prévus dans nos tests.

Pour résumer


Le test de mutation est utile et intéressant . Il peut trouver des problèmes dans les premiers stades du test et sans la participation de personnes. Cela réduira le temps de validation des demandes d'extraction, par exemple, car les développeurs qualifiés n'auront pas à consacrer de temps à la validation des demandes d'extraction, qui présente déjà des problèmes potentiels. Ou enregistrez la production si vous décidez de préparer une nouvelle version vendredi soir.

Stryker est un outil de test de mutation multithread flexible. Il se développe activement, mais jusqu'à présent humide, n'a pas encore atteint la version majeure. Par exemple, lors de la préparation de ce rapport, ses développeurs ont finalement permis dans le plugin Babel de spécifier le fichier de configuration et corrigé l'intégration Jest. Il s'agit d'un projet OpenSource qui peut être aidé à se développer.

FAQ
- Comment tester les tests de mutation? Certes, il y a aussi une erreur. Dans le premier exemple de test unitaire, la couverture était de 90%. Il semblerait que tout va bien, mais les caisses ont quand même glissé lorsque tout est tombé et a pris feu. Par conséquent, pourquoi devrait-on avoir le sentiment que tout va bien après avoir couvert ces tests mutationnels?

- Je ne dis pas que le test de mutation est une solution miracle et qu'il guérira tout. Naturellement, il peut y avoir des cas fous limites ou l'absence d'une sorte de mutateur. Tout d'abord, les erreurs typiques sont facilement détectées. Par exemple, vous mettez une vérification sur l'âge, la définissez sur <18 (c'était nécessaire <=), et dans le test, vous oubliez de faire une vérification du cas de la frontière. Vous avez fait une autre comparaison avec le mutateur, et en conséquence, le test est tombé (ou n'est pas tombé), et vous comprenez que tout est bon ou tout est mauvais. De telles choses sont rapidement capturées. C'est un moyen d'ajouter simplement les tests correctement, de trouver les points manquants.

- Vous avez souvent une situation de "stupéfait et parti"? Je pense que ce n'est pas vrai.

- Non, mais je pense que dans de nombreux projets, de telles choses existent. Naturellement, ce n'est pas vrai. Beaucoup de gens pensent que la couverture du Code permet de tout vérifier, vous pouvez partir en toute sécurité et ne vous inquiétez pas - mais ce n'est pas le cas.

- Je dirai tout de suite quel est le problème. Nous avons beaucoup de toutes sortes de réducteurs et d'autres choses que nous testons mutuellement, et il y en a beaucoup. Tout cela grandit et il s'avère que pour chaque demande de tirage, un test de mutation est lancé, ce qui prend beaucoup de temps. Est-il possible de fonctionner uniquement sur ce qui a changé?

"Je pense que vous pouvez le configurer vous-même." Par exemple, du côté du développeur, quand il pousse, valide, vous pouvez créer un plugin à étapes lint qui n'exécutera que les fichiers qui ont changé. Sur CI / CD, cela est également possible. Dans notre cas, le projet est très grand et ancien, et nous pratiquons des tests ponctuels. On ne vérifie pas tout, car cela prendra une semaine, il y aura des centaines de milliers de mutations. Je recommanderais de faire des vérifications ponctuelles ou d'organiser moi-même un processus de démarrage sélectif. Je n'ai pas vu d'outil prêt à l'emploi pour une telle intégration.

- L'exhaustivité de toutes les mutations possibles pour un morceau de code particulier est-elle assurée? Sinon, comment exactement les mutations sont-elles sélectionnées?

- Personnellement, je n'ai pas vérifié, mais je n'ai pas rencontré de problème non plus. Stryker doit générer toutes les mutations possibles sur le même morceau de code.

- Je veux poser des questions sur les instantanés. Mon test unitaire teste à la fois la logique et, y compris la disposition du composant React Snapshot. Naturellement, si je modifie une conception logique, ma disposition changera là. C'est un comportement attendu, n'est-ce pas?

- Oui, c'est leur sens, que vous mettez à jour manuellement les instantanés.

- Donc, vous ignorez en quelque sorte les instantanés dans ce rapport?

- Très probablement, les instantanés doivent être mis à jour à l'avance, puis exécuter des tests de mutation, sinon il y aura beaucoup de déchets de Stryker.

- Question sur les serveurs CI. Pour les tests unitaires simples, il existe des rapporteurs, sous GitLab, pour tout ce que vous voulez, qui affichent le pourcentage de tests réussis que vous pouvez configurer, qu'il échoue ou non. Et Stryker? Il affiche simplement la tablette dans la console, mais que puis-je faire ensuite?

- Ils ont HTML-reporter, vous pouvez créer vos propres journalistes - tout est personnalisable de manière flexible. Il existe peut-être des outils spécifiques, mais comme nous effectuons toujours des tests de mutation ponctuelle, je n'ai pas trouvé d'intégrations spécifiques avec TeamCity et des outils CI / CD similaires.

- Dans quelle mesure les tests de mutation augmentent-ils le support des tests que vous avez en général? Autrement dit, les tests sont pénibles et les tests doivent être réécrits lorsque le code est réécrit, etc. Parfois, il est plus facile de réécrire le code que les tests. Et ici, j'ai aussi des tests mutationnels. Combien coûte-t-il pour une entreprise?

- Premièrement, je corrigerai probablement que la réécriture de code pour des raisons de test est incorrecte. Le code doit être facile à tester. Quant à ce qui doit être accompli, il est encore important pour l'entreprise que les tests soient aussi complets et efficaces que possible. S'ils ne sont pas terminés, cela signifie qu'un bogue peut se produire qui entraînera des pertes. Naturellement, vous ne pouvez tester que les pièces les plus importantes pour l'entreprise.

"Pourtant, combien cela coûte plus cher lorsque des tests de mutation apparaissent, que s'ils n'étaient pas là."

- C'est autant de mauvais tests maintenant. Si les tests sont mal écrits maintenant, vous devrez en ajouter beaucoup. Les tests de mutation trouveront les cas qui ne sont pas couverts par les tests.

- Sur la diapositive avec les résultats du contrôle Stryker, il y a beaucoup de vorings, ils sont critiques ou pas critiques. Comment gérer les faux positifs?

- La question subtile est ce qui est considéré comme faux. J'ai demandé aux gars de notre équipe quelles choses intéressantes leur étaient arrivées. Il y avait un exemple sur le texte d'erreur. Stryker a signalé que les tests n'ont pas répondu au changement de texte d'erreur. Il semble que ce soit un montant, mais mineur.

- Vous voyez donc de telles erreurs et sautez celles non critiques en mode manuel?

"Nous avons une vérification ponctuelle, alors oui."

- J'ai une question pratique. Lorsque vous avez implémenté cela, quel pourcentage des tests avez-vous échoué?

- Nous ne l'avons pas implémenté sur l'ensemble du projet, mais il y a eu des problèmes mineurs sur le nouveau projet. Par conséquent, je ne peux pas dire les chiffres exacts, mais en général, l'approche a définitivement amélioré la situation.

Découvrez d'autres performances frontales tout aussi utiles sur notre chaîne YouTube , tous les rapports thématiques de toutes nos conférences y arrivent progressivement. Ou abonnez-vous à la newsletter , et nous vous tiendrons informés de tout nouveau matériel et des nouvelles des futures conférences.

Source: https://habr.com/ru/post/fr421141/


All Articles