Pourquoi tester une langue n'est-il pas une fonctionnalité prise en charge au niveau syntaxique?

37

Vous pouvez trouver une liste interminable de blogs, d'articles et de sites Web faisant la promotion des avantages du test unitaire de votre code source. Il est presque garanti que les développeurs qui ont programmé les compilateurs pour Java, C ++, C # et d'autres langages typés ont utilisé des tests unitaires pour vérifier leur travail.

Alors pourquoi alors, malgré sa popularité, les tests sont-ils absents de la syntaxe de ces langages?

Microsoft a introduit LINQ to C # , alors pourquoi ne pourraient-ils pas ajouter des tests?

Je ne cherche pas à prédire quels seraient ces changements linguistiques, mais à préciser pourquoi ils sont absents.

Par exemple: nous savons que vous pouvez écrire une forboucle sans la syntaxe de l' forinstruction. Vous pouvez utiliser whileou if/ gotodéclarations. Quelqu'un a décidé qu'une fordéclaration était plus efficace et l'a introduite dans une langue.

Pourquoi les tests n'ont-ils pas suivi la même évolution des langages de programmation?

Réactionnel
la source
24
Est-il vraiment préférable de complexifier une spécification de langage en ajoutant une syntaxe, plutôt que de concevoir un langage avec des règles simples pouvant être étendues de manière générique?
KChaloux
6
Qu'entendez-vous par "au niveau de la syntaxe"? Avez-vous des détails ou une idée de la manière dont cela serait mis en œuvre?
SJuan76
21
Pouvez-vous donner un exemple de pseudo-code de test en tant que fonctionnalité prise en charge par la syntaxe? Je suis curieux de voir à quoi cela pourrait ressembler.
FrustratedWithFormsDesigner
12
@FrustratedWithFormsDesigner Voir le langage D pour un exemple.
Doval
4
Rust est une autre langue avec des fonctionnalités de test unitaire intégrées.
Sebastian Redl

Réponses:

36

Comme pour beaucoup d'autres choses, le test unitaire est mieux pris en charge au niveau de la bibliothèque, pas au niveau de la langue. En particulier, C # a de nombreuses bibliothèques de tests unitaires disponibles, ainsi que des éléments natifs du .NET Framework tels que Microsoft.VisualStudio.TestTools.UnitTesting.

Chaque bibliothèque de tests unitaires a une philosophie et une syntaxe de test quelque peu différentes. Toutes choses étant égales par ailleurs, plus de choix valent mieux que moins. Si les tests unitaires étaient intégrés à la langue, vous seriez soit enfermé dans les choix du concepteur de langue, soit vous utiliseriez ... une bibliothèque et évitiez totalement les fonctionnalités de test de langue.

Exemples

  • Nunit - Un cadre de test unitaire idiomatique et à but général, tirant pleinement parti des fonctionnalités du langage C #.

  • Moq - Cadre moqueur qui tire pleinement parti des expressions lambda et des arbres d'expression, sans métaphore d'enregistrement / de lecture.

Il y a beaucoup d'autres choix. Des bibliothèques telles que Microsoft Fakes peuvent créer des "shims ..." qui ne vous obligent pas à écrire vos classes à l'aide d'interfaces ou de méthodes virtuelles.

Linq n'est pas une langue (malgré son nom)

Linq est une fonctionnalité de la bibliothèque. Nous avons gratuitement beaucoup de nouvelles fonctionnalités dans le langage C # lui-même, comme les expressions lambda et les méthodes d'extension, mais l'implémentation réelle de Linq se trouve dans le .NET Framework.

Un certain sucre syntaxique a été ajouté à C # pour rendre les instructions linq plus propres, mais ce sucre n'est pas requis pour utiliser linq.

Robert Harvey
la source
1
Je suis d'accord avec le fait que LINQ n'est pas une fonctionnalité de langage, mais pour que LINQ se réalise, il devait exister certaines fonctionnalités de langage sous-jacentes, en particulier des génériques et des types dynamiques.
Wyatt Barnett
@WyattBarnett: Oui, il en va de même pour les tests unitaires faciles - comme la réflexion et les attributs.
Doc Brown
8
LINQ avait besoin de lambdas et d'arbres d'expression. Les génériques étaient déjà en C # (et .Net en général, ils sont présents dans le CLR), et la dynamique n'a rien à voir avec LINQ; vous pouvez faire LINQ sans dynamique.
Arthur van Leeuwen
DBIx :: Class de Perl est un exemple de fonctionnalité de type LINQ mise en œuvre plus de 10 ans après que toutes les fonctionnalités nécessaires à son implémentation ont été intégrées au langage.
slebetman
2
@ Carson63000 Je pense que vous entendez les types anonymes en combinaison avec le varmot clé :)
M.Mimpen
21

Il y a beaucoup de raisons. Eric Lippert a répété à maintes reprises que la raison feature Xpour laquelle il n'était pas en C # était parce que ce n'était tout simplement pas dans son budget. Les concepteurs de langage ne disposent ni d'un temps infini ni de l'argent pour mettre en œuvre des éléments et chaque nouvelle fonctionnalité entraîne des coûts de maintenance. Garder le langage le plus petit possible n'est pas seulement plus facile pour les concepteurs de langage, il est également plus facile pour quiconque écrit des implémentations et des outils alternatifs (par exemple, des IDE). portabilité gratuite. Si le test unitaire est implémenté en tant que bibliothèque, il vous suffit de l'écrire une seule fois et cela fonctionnera dans n'importe quelle implémentation conforme du langage.

Il convient de noter que D dispose d’un support au niveau de la syntaxe pour les tests unitaires . Je ne sais pas pourquoi ils ont décidé de le mentionner, mais il convient de noter que D est censé être un "langage de programmation système de haut niveau". Les concepteurs ont voulu que ce soit viable pour le type de code non sécurisé et de bas niveau utilisé traditionnellement par C ++, et une erreur dans le code non sécurisé est incroyablement coûteuse - un comportement indéfini. Je suppose donc que cela leur semblait logique de déployer des efforts supplémentaires pour tout ce qui peut vous aider à vérifier que certains codes non sécurisés fonctionnent. Par exemple, vous pouvez imposer que seuls certains modules sécurisés peuvent effectuer des opérations non sécurisées telles que des accès à des tableaux non contrôlés ou une arithmétique de pointeur.

Le développement rapide était également une priorité pour eux, à tel point qu'ils en ont fait un objectif de conception que le code D compile assez rapidement pour le rendre utilisable en tant que langage de script. Cuire des tests unitaires directement dans la langue afin que vous puissiez exécuter vos tests en transmettant simplement un indicateur supplémentaire au compilateur vous aide dans cette tâche.

Cependant, je pense qu'une bonne bibliothèque de tests unitaires fait beaucoup plus que simplement trouver des méthodes et les exécuter. Prenez par exemple QuickCheck de Haskell , qui vous permet de tester des choses telles que "pour tout x et y f (x, y) == f (y, x)". QuickCheck est mieux décrit comme un générateur de test unitaire et vous permet de tester des choses à un niveau supérieur à "pour cette entrée, je m'attends à cette sortie". QuickCheck et Linq ne sont pas si différents, ce sont deux langues spécifiques à un domaine. Par conséquent, plutôt que de vous limiter au support de tests unitaires dans une langue, pourquoi ne pas ajouter les fonctionnalités nécessaires pour rendre les DSL pratiques? Vous obtiendrez ainsi non seulement des tests unitaires, mais une meilleure langue.

Doval
la source
3
Pour rester dans le monde .Net, il existe FsCheck, qui est un portage de QuickCheck en F #, avec quelques aides à utiliser en C #.
Arthur van Leeuwen
Rester dans le monde .NET? Cette question n'a rien à voir avec .NET.
Miles Rout
@MilesRout C # fait partie de .NET. La question et la réponse parlent souvent de C #, et la question parle même de LINQ.
Zachary Dow
La question n'est pas étiquetée .NET ou C #.
Miles Rout
@MilesRout Cela peut être vrai, mais vous ne pouvez pas raisonnablement dire que cela n'a rien à voir avec cela simplement parce qu'il n'a pas de balise. :)
Zachary Dow
13

Parce que les tests, et en particulier les développements pilotés par les tests, constituent un phénomène profondément contre-intuitif.

Presque tous les programmeurs commencent leur carrière en pensant qu'ils maîtrisent beaucoup mieux la complexité qu'ils ne le sont réellement. Le fait que même le plus grand programmeur ne puisse pas écrire de programmes volumineux et complexes sans erreurs graves à moins d'utiliser de nombreux tests de régression est sérieusement décevant et même honteux pour de nombreux praticiens. D'où la réticence face aux tests réguliers, même parmi les professionnels qui devraient déjà savoir mieux.

Je pense que le fait que les tests religieux deviennent de plus en plus courants et attendus est en grande partie dû au fait que, avec l'explosion de la capacité de stockage et de la puissance de calcul, de plus grands systèmes sont en cours de construction - et les très grands systèmes sont particulièrement sujets à l'effondrement de la complexité qui ne peut pas être géré sans le filet de sécurité des tests de régression. En conséquence, même les développeurs particulièrement obstinés et désemparés admettent maintenant à contrecœur qu'ils ont besoin de tests et qu'ils en auront toujours besoin (si cela ressemble à la confession lors d'une réunion des AA, c'est tout à fait intentionnel - il est psychologiquement difficile d'admettre qu'il existe un filet de sécurité personnes).

La plupart des langues populaires d’aujourd’hui datent d’avant ce changement d’attitude, de sorte qu’elles ont peu d’assistance intégrée pour les tests: elles ont assert, mais pas de contrat. Je suis à peu près sûr que si la tendance se maintient, les futures langues auront davantage de soutien au niveau de la langue plutôt qu'au niveau de la bibliothèque.

Kilian Foth
la source
3
Vous exagérez un peu votre cas. Bien que le test unitaire soit incontestablement d’une importance primordiale , il est tout aussi important, sinon plus, de concevoir des programmes de la manière qui convient le mieux. Les langages tels que Haskell ont des systèmes de types qui améliorent la fiabilité et la prouvabilité des programmes écrits, et les meilleures pratiques en matière de codage et de conception évitent bon nombre des pièges d'un code non testé, ce qui rend les tests unitaires moins critiques qu'ils ne le seraient autrement.
Robert Harvey
1
@RobertHarve ... et les tests unitaires sont un très bon moyen de pousser indirectement les développeurs vers cela. Travailler sur l'API tout en créant des tests, plutôt que de l'utiliser avec un autre code, aide à les placer dans le bon état d'esprit pour limiter ou supprimer les abstractions qui fuient ou les appels de méthodes impairs. Je ne pense pas que KillianFoth exagère quelque chose.
Izkata
@Robert: La seule chose que je crois qu'il exagère, c'est que "les tests religieux deviennent de plus en plus courants" Si lentement est défini en termes de délais géographiques, il n'est probablement pas très loin ..... :)
mattnz
+1 Dans mon travail actuel, je suis frappé par le changement radical d'attitude du développement avec les nouvelles bibliothèques et technologies disponibles. Pour la première fois de ma carrière, ces gars-là me conseillent d'utiliser un processus de développement plus sophistiqué, au lieu de me donner l' impression que je suis au sommet d'une colline en train de crier au vent. Je conviens que ce n'est qu'une question de temps avant que ces attitudes trouvent leur expression dans la syntaxe de la langue maternelle.
Rob
1
Désolé, encore une pensée concernant le commentaire de Robert Harvey. À l'heure actuelle, les systèmes de types sont une vérification intéressante, mais ils ne sont vraiment pas en mesure de définir des contraintes sur les types - ils s'adressent à l'interface, mais ne contraignent pas le comportement correct. (c.-à-d. 1 + 1 == 3 compileront, mais peuvent ou non être vraies, en fonction de l'implémentation de +) Je me demande si la prochaine tendance verra des systèmes de types plus expressifs combinant ce que nous considérons comme des tests unitaires maintenant.
Rob
6

Beaucoup de langues ont un support pour les tests. Les affirmations de C sont des tests que le programme peut échouer. C’est là que la plupart des langues s’arrêtent, mais Eiffel et plus récemment Ada 2012 ont des préinvariants (choses que les arguments d’une fonction doivent passer) et postinvariants (choses que la sortie d’une fonction doit passer), Ada offrant la possibilité de référencer les arguments initiaux dans le fichier. postinvariant. Ada 2012 propose également des invariants de types. Ainsi, chaque fois qu'une méthode est appelée sur une classe Ada, l'invariant de types est vérifié avant le renvoi.

Ce n'est pas le type de test complet qu'un bon framework de test peut vous offrir, mais c'est un type de test important que les langues peuvent le mieux prendre en charge.

prosfilaes
la source
2
Ayant fait un peu de travail à Eiffel et étant un utilisateur assidu d'affirmations, mon expérience a été que tout ce que vous pouvez faire, de nature contractuelle, aide à détecter beaucoup de bugs.
Blrfl
2

Certains partisans de langages fonctionnels fortement typés affirment que ces fonctionnalités réduisent ou suppriment le recours aux tests unitaires.

Deux, à mon humble avis, bon exemple de cela, c’est de F # pour le plaisir et le profit ici et ici

Personnellement, je continue de croire en la valeur des tests unitaires, mais certains points sont valables. Par exemple, si un état illégal n'est pas représentable dans le code, il est non seulement inutile d'écrire un test unitaire pour ce cas, c'est impossible.

Pete
la source
2

Je dirais que vous avez manqué l’ajout de certaines fonctionnalités nécessaires car elles n’étaient pas mises en avant pour les tests unitaires .

Par exemple, les tests unitaires en C # reposent principalement sur l'utilisation d'attributs. La fonctionnalité Attributs personnalisés fournit un mécanisme d'extension riche qui permet aux infrastructures telles que NUnit d'itérer et de rivaliser avec des éléments tels que les tests basés sur la théorie et les tests paramétrés .

Cela m'amène à mon deuxième point majeur - nous ne savons pas assez ce qui fait une bonne testabilité pour l'incorporer dans une langue. Le rythme des innovations en matière de test étant beaucoup plus rapide que celui des autres concepts linguistiques, nous devons disposer de mécanismes flexibles dans nos langues pour laisser la liberté d'innover.

Je suis un utilisateur mais je ne suis pas un fanatique de TDD - cela est très utile dans certains cas, en particulier pour améliorer votre pensée conceptuelle. Il n'est pas forcément utile pour les systèmes hérités plus importants: des tests de niveau supérieur avec de bonnes données et une automatisation produiront probablement plus d'avantages avec une culture de contrôle du code forte .

Autre lecture pertinente:

Andy Dent
la source