En tant que programmeur, j'ai adhéré de tout cœur à la philosophie TDD et je m'efforce de réaliser des tests unitaires complets pour tout code non trivial que j'écris. Parfois, ce chemin peut être douloureux (les changements de comportement entraînant de multiples changements de tests unitaires en cascade ; de grandes quantités d'échafaudages nécessaires), mais dans l'ensemble, je refuse de programmer sans tests que je peux exécuter après chaque changement, et mon code est beaucoup moins bogué en conséquence.
Récemment, j'ai joué avec Haskell et sa bibliothèque de tests, QuickCheck. D'une manière très différente de TDD, QuickCheck met l'accent sur le test des invariants du code, c'est-à-dire certaines propriétés qui sont valables pour toutes les entrées (ou des sous-ensembles importants). Un exemple rapide : un algorithme de tri stable devrait donner la même réponse si nous l'exécutons deux fois, devrait avoir une sortie croissante, devrait être une permutation de l'entrée, etc. Ensuite, QuickCheck génère une variété de données aléatoires afin de tester ces invariants.
Il me semble, au moins pour les fonctions pures (c'est-à-dire les fonctions sans effets secondaires - et si vous faites correctement le mocking, vous pouvez convertir des fonctions sales en fonctions pures), que les tests invariants pourraient supplanter les tests unitaires en tant que sur-ensemble strict de ces capacités. Chaque test unitaire consiste en une entrée et une sortie (dans les langages de programmation impératifs, la "sortie" n'est pas seulement le retour de la fonction mais aussi tout état modifié, mais cela peut être encapsulé). On pourrait concevoir de créer un générateur d'entrées aléatoires suffisamment bon pour couvrir toutes les entrées de tests unitaires que vous auriez créées manuellement (et même plus, car il générerait des cas auxquels vous n'auriez pas pensé) ; si vous trouvez un bogue dans votre programme dû à une condition limite, vous améliorez votre générateur d'entrées aléatoires pour qu'il génère également ce cas.
Le défi consiste donc à savoir s'il est possible ou non de formuler des invariants utiles pour chaque problème. Je dirais que oui : il est beaucoup plus simple, une fois que vous avez une réponse, de voir si elle est correcte que de calculer la réponse en premier lieu. Penser aux invariants permet également de clarifier la spécification d'un algorithme complexe bien mieux que les cas de test ad hoc, qui encouragent une sorte de réflexion au cas par cas du problème. Vous pouvez utiliser une version antérieure de votre programme comme implémentation modèle, ou une version d'un programme dans un autre langage. Etc. Finalement, vous pourriez couvrir tous vos anciens scénarios de test sans avoir à coder explicitement une entrée ou une sortie.
Est-ce que je suis devenu fou, ou est-ce que je suis sur quelque chose ?