Eh bien ... oui en fait, si chaque chemin "à travers" le programme est testé. Mais cela signifie que chaque chemin possible à travers tout l'espace de tous les états possibles du programme peut avoir, y compris toutes les variables. Même pour un programme très simple compilé statiquement - par exemple, un ancien correcteur de nombres Fortran - ce n’est pas faisable, bien que cela puisse au moins être imaginable: si vous n’avez que deux variables entières, vous avez en gros la possibilité de relier des points entre eux. une grille bidimensionnelle; cela ressemble beaucoup à Travelling Salesman. Pour n telles variables, vous traitez avec un espace n- dimensionnel, de sorte que pour tout programme réel, la tâche est totalement indisponible.
Pire: pour les choses sérieuses, vous n’avez pas seulement un nombre fixe de variables primitives, mais vous créez des variables à la volée dans des appels de fonction, ou vous avez des variables de taille variable ... ou quelque chose du genre, autant que possible dans un langage complet de Turing. Cela donne à l'espace d'états une dimension infinie, brisant tous les espoirs d'une couverture totale, même avec un équipement de test absurdement puissant.
Cela dit ... en réalité, les choses ne sont pas si sombres. Il est possible de prouver que des programmes entiers sont corrects, mais vous devrez renoncer à quelques idées.
Premièrement: il est vivement conseillé de passer à une langue déclarative. Langues Impératif, pour une raison quelconque, ont toujours été de loin le plus populaire, mais la façon dont ils mélangent des algorithmes avec des interactions du monde réel, il est extrêmement difficile de dire même ce que vous entendez par « correct ».
Beaucoup plus facile dans les langages de programmation purement fonctionnels : ceux-ci établissent une distinction claire entre les propriétés réellement intéressantes des fonctions mathématiques et les interactions floues du monde réel sur lesquelles on ne peut vraiment rien dire. Pour les fonctions, il est très facile de spécifier le «comportement correct»: si, pour toutes les entrées possibles (à partir des types d'argument), le résultat souhaité correspondant sort, alors la fonction se comporte correctement.
Maintenant, vous dites que c'est toujours insoluble ... après tout, l'espace de tous les arguments possibles est en général aussi d'une dimension infinie. C'est vrai - bien que pour une seule fonction, même des tests de couverture naïfs vous mènent bien plus loin que vous ne pourriez l'espérer dans un programme impératif! Cependant, il existe un outil puissant incroyable qui change le jeu: la quantification universelle / polymorphisme paramétrique . En gros, cela vous permet d'écrire des fonctions sur des types de données très généraux, avec la garantie que si cela fonctionne pour un exemple simple de données, cela fonctionnera pour toute entrée possible.
Au moins théoriquement. Il n'est pas facile de trouver les bons types qui sont vraiment si généraux que vous pouvez tout à fait le prouver - vous avez généralement besoin d'un langage typé en fonction de la dépendance , ce qui est plutôt difficile à utiliser. Mais écrire dans un style fonctionnel avec un polymorphisme paramétrique augmente déjà énormément votre «niveau de sécurité» - vous ne trouverez pas nécessairement tous les bogues, mais vous devrez les cacher assez bien pour que le compilateur ne les repère pas!