runward

RW™ · V0.31.0

Le détecteur de fumée que personne n'a jamais testé.

Un voyant vert ne prouve pas qu'un détecteur fonctionne. Il prouve qu'il est alimenté. La même chose est vraie d'une suite de tests, et il existe une méthode simple pour faire la différence : allumer un briquet dans chaque pièce.

Il y a un détecteur de fumée au plafond. Son voyant est vert. Est-ce qu'il fonctionne ?

Vous n'en savez rien. Vous savez qu'il est alimenté. Pour savoir s'il fonctionne, il n'existe qu'un moyen : approcher une flamme et écouter.

Une suite de tests au vert, c'est le même voyant. Elle vous dit qu'elle n'a pas échoué. Elle ne vous dit pas qu'elle aurait pu.

La différence entre deux questions

La plupart des équipes mesurent la couverture de code : quelle proportion du code les tests traversent. C'est utile, et ça répond à la question « y a-t-il un détecteur dans chaque pièce ? ».

Ce n'est pas la bonne question. On peut traverser 100 % d'un code sans jamais rien vérifier : il suffit d'écrire des tests qui exécutent tout et n'affirment rien. Le rapport de couverture sera parfait. Les détecteurs seront tous installés, et aucun ne sonnera.

La question qui compte est la seconde : si quelque chose casse, est-ce que quelqu'un le remarque ? Et pour y répondre, il faut allumer le briquet.

Casser son propre code, méthodiquement

La méthode tient en trois gestes. On prend une ligne du code source. On la casse volontairement, d'une manière précise et minuscule : une comparaison inversée, une condition assouplie, un « et » changé en « ou ». Puis on relance toute la suite de tests.

Deux issues possibles. Soit un test rougit, et il vient de prouver qu'il sert à quelque chose. Soit rien ne bouge, et vous venez d'apprendre que ce morceau de code n'est gardé par personne. On répare la ligne, on recommence ailleurs.

L'idée n'est pas neuve en informatique, et elle précède de loin la cousine qui l'a rendue célèbre : Netflix éteignant au hasard des instances de son propre système de production. Attention au malentendu, il est répandu : ce n'était pas pour vérifier que le service tenait. C'était pour obliger ses équipes à n'écrire que des services capables d'encaisser la disparition d'une pièce. Plutôt qu'attendre la panne, on la fabrique, et on la fabrique en permanence.

La différence entre les deux est plus intéressante qu'il n'y paraît. Netflix casse le système pour découvrir par où il cède : c'est une expérience, on cherche ce qu'on n'avait pas prévu. Ici on casse le code pour vérifier une propriété connue, et la réponse est binaire : ce test rougit, ou il ne rougit pas.

Ce qu'ils ont en commun est ailleurs, et c'est le plus important. Dans les deux cas, l'effet durable n'est pas le rapport produit à la fin. C'est la contrainte permanente : quand vous savez qu'une machine éteindra une instance au hasard, vous concevez autrement. Quand vous savez qu'on cassera cette ligne pour voir, vous testez autrement.

C'est aussi un cran plus inconfortable, parce que la réponse dit du mal de votre propre travail de vérification.

Ce que ça a donné chez nous

Nous l'avons fait sur sept modules du cœur de runward. Quarante-deux cassures, une par une.

Trente-six ont été arrêtées. Vingt-sept par un test qui rougit, neuf par le compilateur qui a refusé de construire le programme. Six sont passées sans que rien ne bouge.

Trois d'entre elles étaient de vrais trous, et deux méritent d'être nommées parce qu'elles sont ordinaires. La première : la vérification qui empêche une preuve de pointer vers un fichier situé hors du projet. Un test portait exactement ce nom, sur ce chemin exact, et il ne gardait rien : on pouvait affaiblir la protection de quatre façons différentes sans qu'il bronche. La seconde : rien ne vérifiait que l'outil fonctionne quand on le lance depuis un sous-dossier, c'est-à-dire la manière dont tout le monde s'en sert.

La partie que personne ne raconte

Toutes les cassures qui passent ne sont pas des trous. Certaines ne changent rien d'observable : le programme se comporte exactement pareil. On les appelle des équivalences, et il faut les écarter, sinon on écrit des tests décoratifs pour faire monter un score.

Nous avons donc trié nos six survivantes. Trois trous réels, trois inoffensives. Nous l'avons écrit, publié, et argumenté.

Nous nous sommes trompés sur deux des trois.

Nous avions raisonné. Le raisonnement était solide, propre, et faux. Sur l'une, nous analysions ligne par ligne un mécanisme qui garde sa mémoire d'une ligne à l'autre : un simple dièse dans une adresse web, à l'intérieur d'un fichier de configuration ordinaire, faisait disparaître une règle importante sur les garde-fous des traitements en arrière-plan. Nous avions écrit que ce cas « n'existe pas ». Sur l'autre, nous avions classé le défaut d'après son canal de sortie : « ce n'est qu'un message d'information ». Le message en question pouvait affirmer le contraire de ce que le programme venait de constater, ou désigner le mauvais fichier.

Ce ne sont pas des fautes d'inattention. C'est la démonstration d'une chose plus utile : pour trier les survivantes, le raisonnement ne suffit pas. Il faut expérimenter. Nous avons fini par le faire, en lançant des vérificateurs dont la consigne était de nous donner tort et qui avaient le droit d'exécuter le code pour le prouver. Ce sont eux qui ont fabriqué le fichier qui casse.

Pourquoi ça compte pour runward

runward existe pour une raison tenant en une phrase : une déclaration n'est pas une preuve. Écrire « cette décision est validée » ne vaut rien ; ce qui vaut, c'est un pointeur vers un fichier, une ligne, un test, que la machine peut aller vérifier.

Un test vert est exactement le même genre de déclaration. Il affirme qu'il vous protège. Tant que vous ne l'avez pas vu rougir devant un vrai défaut, c'est une affirmation, pas une preuve.

Un outil qui exige la preuve des autres et ne se l'applique pas à lui-même est un vendeur de balances qui ne se pèse jamais. C'est aussi simple que ça. Et il y a pire qu'un test manquant : un test qui porte le nom d'une garantie et ne la tient pas. Celui-là ne laisse pas un trou visible, il pose un couvercle dessus. C'est précisément ce que nous avons trouvé sur notre vérification de chemins.

Pourquoi ça compte pour vous

Deux raisons, une passive et une active.

La passive. Quand votre porte est au vert, ce vert vaut ce que valent les vérifications derrière. Vous avez maintenant un chiffre : sur quarante-deux défauts injectés dans le cœur, six sont passés, trois étaient réels, et ils sont bouchés. Ce n'est pas une garantie de perfection. C'est une mesure, là où il n'y avait qu'une intention.

L'active, et c'est la plus utile. Rien de tout cela n'est propre à runward. Votre code a des tests. Ces tests ont probablement des trous, et vous ne saurez lesquels qu'en cassant volontairement des choses. La méthode ne demande aucun outil particulier pour commencer : prenez la fonction dont vous dépendez le plus, inversez une condition, relancez vos tests. Si rien ne rougit, vous venez d'apprendre quelque chose sur votre filet.

C'est ce qu'a fait une équipe qui livre avec runward, sur la version précédente, de son propre chef. Elle ne nous a pas attendus.

Trois précautions valent d'être connues avant de s'y mettre. Vérifiez que votre cassure a réellement modifié le fichier : une modification qui ne s'applique pas « survit » toujours, et nous avons failli rapporter trois trous imaginaires pour cette raison. Ne transformez pas le résultat en pourcentage à faire monter, sinon vous écrirez des tests pour le score. Et ne triez pas les survivantes de tête : nous l'avons fait, nous nous sommes trompés deux fois sur trois.

Le voyant vert ne prouve rien. Le briquet, si.

← Toutes les nouveautés