Ce qui n’est pas testé ne fonctionne pas

Lors de mes recherches sur la proposition d’apprendre le code à l’école du Conseil National du Numérique, j’ai retrouvé plusieurs fois l’idée selon laquelle la programmation pouvait être une activité formatrice au sens où elle promouvait un processus de création par essai – erreur où l’erreur n’était pas considérée comme une sanction définitive mais comme une étape naturelle participant à la construction de la solution. Cette idée est également très régulièrement citée comme un des facteurs de la réussite de la Silicon Valley, où un échec n’est pas considéré comme rédhibitoire, mais comme une expérience valorisable comme une autre.

Encore faut-il que les programmeurs s’intéressent au fait que leur programme soit erroné, et d’après mon expérience, c’est souvent là que le bât blesse.

C’est ballot, mais la moindre modification du code doit être testée !

Je vais reprendre un exemple récent arrivé à un de mes développeurs – si il se reconnait, rien de personnel, c’est pour l’exemple – sur une fonction plpgsql ; ça, c’est la première version qui marche (simplifiée à l’extrême bien sûr) :

CREATE OR REPLACE FUNCTION MAFONCTION(V_ID INT, V_MONPARAMETRE BOOLEAN) RETURNS INT AS $$
DECLARE
 V_RESULT INT:=1;
BEGIN
 UPDATE MATABLE SET MAVALEUR=V_MONPARAMETRE WHERE ID=V_ID;
 RETURN V_RESULT;
END;
$$ LANGUAGE plpgsql;

En relisant les spécifications, le développeur s’aperçoit que j’avais demandé que le deuxième paramètre soit optionnel et ait comme valeur par défaut false, et connaissant mal la syntaxe plpgsql, écrit le code suivant – négligeant de le tester, se disant qu’il n’y avait pas de raison que cela ne fonctionne pas :

CREATE OR REPLACE FUNCTION MAFONCTION(V_ID INT, V_MONPARAMETRE BOOLEAN) RETURNS INT AS $$
DECLARE
 V_RESULT INT:=1;
 V_MONPARAMETRE BOOLEAN:=false;
BEGIN
 UPDATE MATABLE SET MAVALEUR=V_MONPARAMETRE WHERE ID=V_ID;
 RETURN V_RESULT;
END;
$$ LANGUAGE plpgsql;

Pas de bol, il se trouve que les paramètres d’une fonction plpgsql peuvent être nommés, mais que ce n’est qu’une sorte de macro ; posgtresql va remplacer toutes les occurrences du nom choisi dans la fonction par le numéro du paramètre précédé d’un $ ; dans notre cas, postgresql ne le fera pas, puisqu’on a défini une variable dans la clause DECLARE qui écrase le paramètre en entrée, et postgresql ne fera ni erreur ni avertissement. Du coup, la valeur de V_MONPARAMETRE sera toujours false, quoi qu’on passe à la fonction. Le développeur est venu une fois fini le reste du travail demandé me dire que tout était terminé ; mes tests d’intégration n’ont bien sûr pas fonctionné, pas plus que mes tests unitaires. Le développeur était pourtant certain d’avoir vu sa fonction marcher, sans doute avant sa dernière modification qu’il considérait comme tellement triviale qu’il n’y avait pas besoin de la tester.

Un exemple historique… et autrement plus coûteux

Le premier vol d’Ariane 5 s’est achevé au bout de quelques dizaines de secondes, détruisant pour 370 millions de dollars de satellites. La source du problème ? Un module de calcul de poussée de la fusée, repris tel quel d’Ariane 4, dans lequel la poussée était stockée sur une variable d’une taille trop petite pour la nouvelle fusée, munie de moteurs plus puissants ; habituellement, en programmation, quand une variable numérique reçoit une valeur trop grande pour sa capacité de stockage, et en l’absence de vérification, la valeur passe à 0 (cas non signé) ou à la plus petite valeur négative (cas signé), donc en tout état de cause une valeur anormalement basse.

Du coup, la fusée s’est crue dans une position nécessitant une intervention immédiate et radicale du pilote automatique pour rectifier la trajectoire, intervention qui a exercé des contraintes telles sur la fusée qu’elle a commencé à se disloquer, aboutissant à l’enclenchement du mécanisme d’auto-destruction, et à un joli mais très coûteux feu d’artifice. Tout ça pour une variable stockée sur 8 bits au lieu de 9 nécessaires, dans un module repris à l’identique, mais… non testé dans les nouvelles conditions.

De ces deux exemples, on peut déduire une première règle : ne jamais oublier que quelle que soit la petitesse de la modification effectuée sur le code, ce qui n’est pas testé ne fonctionne pas.

Et deux tests valent mieux qu’un…

Une autre de mes activités, récemment en croissance, est de débusquer des trous de sécurité, que ce soit sur des sites nous appartenant ou que nous hébergeons ; rien de très évolué, plutôt du très classique : injection SQL permettant de s’identifier avec n’importe quel compte ou de consulter toute une base de données payante sans l’acheter, cross-site scripting lors de la soumission de données pour intégrer du code actif envoyant discrètement des données du site attaqué vers un tiers ou pour faire du hameçonnage, ce genre de choses. Le point commun de ces défauts de sécurité, également commun avec les classiques attaques buffer overflow à la base de la plupart des failles des systèmes d’exploitation ? Le système définit des entrées – par exemple, le premier paramètre doit être un entier, et le deuxième une chaîne de caractères de moins de 256 octets de long – mais ne vérifie pas que les valeurs passées respectent cette définition.

Tendance bien naturelle : quand on programme la fonction et les appels à la fonction puis que dans la foulée, on effectue les tests, il est assez logique de mettre en oeuvre des tests respectant les contraintes qu’on a soit même définies, elles sont assez fraîches dans notre esprit. De nombreuses façons d’éviter ces erreurs existent : faire écrire les tests par une autre équipe que l’équipe de développement est une parade classique, certains langages de programmation incluent ce type de contraintes dans la signature des fonctions comme Ada par exemple, etc. Mais aucune n’est plus efficace que d’être conscient de la problématique et de la mettre au cœur de ses pratiques !

Ces exemples nous permettent d’énoncer une deuxième règle : ne jamais faire confiance aux données envoyées par le client.

Si vous gardez ces deux règles présentes à l’esprit tout au long de vos développements, quels que soient les outils que vous utilisez, aussi protecteurs que vous pensiez qu’ils soient contre les fautes de programmation, vous aurez fait un grand pas vers la production de programmes conformes et sûrs.

Une réflexion sur « Ce qui n’est pas testé ne fonctionne pas »

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *