Y a-t-il une raison pour laquelle les tests ne sont pas écrits en ligne avec le code qu'ils testent?

91

J'ai récemment lu quelques mots sur Literate Programming , et cela m'a fait réfléchir ... Des tests bien écrits, notamment les spécifications de style BDD, permettent de mieux expliquer ce que le code fait que la prose et a le gros avantage de vérifier leur propre précision.

Je n'ai jamais vu de tests écrits en ligne avec le code qu'ils testent. Est-ce simplement parce que les langues ne permettent pas facilement de séparer le code d'application et le code de test lorsqu'ils sont écrits dans le même fichier source (personne ne l'a simplifié), ou y a-t-il une raison plus logique qui sépare le code de test du code d'application?

Chris Devereux
la source
33
Certains langages de programmation tels que python avec doctest vous permettent de le faire.
Simon Bergot
2
Vous pouvez penser que les spécifications de style BDD sont meilleures que la prose pour expliquer le code, mais cela ne signifie pas que la combinaison des deux n'est pas meilleure.
JeffO
5
La moitié des arguments présentés ici s'applique également à la documentation en ligne.
CodesInChaos
3
Les docteurs @Simon sont trop simplistes pour des tests sérieux, principalement parce qu'ils ne sont pas conçus pour cela. Ils étaient destinés à avoir des exemples de code dans la documentation, qui peuvent être vérifiés automatiquement, et sont excellents. Maintenant, certaines personnes les utilisent également pour les tests unitaires, mais récemment (comme dans les années précédentes), cela a pris beaucoup de flak, car cela tend à se terminer par des dégâts fragiles, une "documentation" excessivement verbeuse et autres.
7
Design by Contract permet de créer des spécifications en ligne qui facilitent les tests.
Fuhrmanator

Réponses:

89

Le seul avantage auquel je puisse penser pour les tests en ligne serait de réduire le nombre de fichiers à écrire. Avec les IDE modernes, ce n'est pas vraiment un gros problème.

Les tests en ligne présentent toutefois un certain nombre d'inconvénients évidents:

  • Cela viole la séparation des préoccupations . Cela peut être discutable, mais pour moi la fonctionnalité de test est une responsabilité différente que de la mettre en œuvre.
  • Vous devez soit introduire de nouvelles fonctionnalités linguistiques pour distinguer les tests / la mise en œuvre, soit vous risqueriez de brouiller la ligne de démarcation entre les deux.
  • Les fichiers source plus volumineux sont plus difficiles à utiliser: plus difficiles à lire, plus difficiles à comprendre, vous êtes plus susceptible de devoir gérer des conflits de contrôle de source.
  • Je pense que cela rendrait plus difficile de mettre votre chapeau "testeur", pour ainsi dire. Si vous regardez les détails de l'implémentation, vous serez plus tenté de ne pas implémenter certains tests.
vaughandroid
la source
9
C'est intéressant. Je suppose que l’avantage, c’est que lorsque vous portez votre chapeau de "codeur", vous voulez penser aux tests, mais c’est un point positif, c’est que l’inverse n’est pas vrai.
Chris Devereux
2
Dans cette optique, il est possible (et peut-être souhaitable) de faire appel à une personne pour créer les tests et à une autre pour implémenter le code. Mettre les tests en ligne rend cela plus difficile.
Jim Nutt
6
serait downvote si je pouvais. Comment est-ce une réponse de quelque façon que ce soit? Les développeurs n'écrivent pas de tests? Les gens sautent des tests s’ils examinent les détails de la mise en œuvre? "Juste trop dur" Des conflits sur de gros fichiers ?? Et de quelle manière un test pourrait-il être confondu avec un détail d'implémentation ???
Bharal
5
@bharal En outre, le masochisme est une vertu du fou. Je veux que tout soit facile sauf le problème que j'essaie de résoudre.
deworde
3
Le test unitaire peut être considéré comme de la documentation. Cela suggère que les tests unitaires devraient être inclus dans le code pour la même raison que les commentaires - pour améliorer la lisibilité. Cependant, le problème, c'est qu'il y a souvent beaucoup de tests unitaires et beaucoup de temps système d'implémentation de test qui ne spécifient pas les résultats attendus. Même les commentaires dans le code doivent rester concis, avec des explications plus larges écartées: vers un bloc de commentaires en dehors de la fonction, dans un fichier séparé ou peut-être dans un document de conception. Les tests unitaires sont rarement, voire jamais, assez courts pour que le code testé ressemble à des commentaires.
Steve314
36

Je peux penser à certains:

  • Lisibilité. Le fait d'interpréter du code "réel" et des tests rendra plus difficile la lecture du code réel.

  • Code ballonnement. Mélanger du code "réel" et du code de test dans les mêmes fichiers / classes / tout ce qui est susceptible de donner lieu à des fichiers compilés plus volumineux, etc. Ceci est particulièrement important pour les langues avec liaison tardive.

  • Vous ne voulez peut-être pas que vos clients / clients voient votre code de test. (Je n'aime cette raison ... mais si vous travaillez sur un projet source fermé, le code de test est peu susceptible d'aider le client de toute façon.)

Il existe maintenant des solutions de contournement possibles pour chacun de ces problèmes. Mais IMO, il est plus simple de ne pas y aller en premier lieu.


Il est intéressant de noter qu'au début, les programmeurs Java faisaient ce genre de chose; Par exemple, inclure une main(...)méthode dans une classe pour faciliter les tests. Cette idée a presque complètement disparu. Il est de pratique courante dans l’industrie de mettre en œuvre des tests séparément en utilisant un cadre de test quelconque.

Il convient également de noter que la programmation littéraire (telle que conçue par Knuth) n’a jamais fait son chemin dans l’industrie du génie logiciel.

Stephen C
la source
4
+1 Problèmes de lisibilité - le code de test peut être proportionnellement supérieur au code de mise en œuvre, en particulier dans les conceptions OO.
Fuhrmanator
2
+1 pour signaler l'utilisation de frameworks de test. Je ne peux pas imaginer utiliser un bon framework de test en même temps que le code de production.
joshin4colours
1
RE: Vous ne voulez peut-être pas que vos clients voient votre code de test. (Je n'aime pas cette raison ... mais si vous travaillez sur un projet à source fermée, le code de test n'aidera de toute façon pas le client.) - Il peut être souhaitable d'exécuter les tests sur la machine du client. L'exécution des tests peut aider à identifier rapidement le problème et à identifier les différences entre les clients ..
sixtyfootersdude
1
@sixtyfootersdude - c'est une situation assez inhabituelle. Et en supposant que vous développiez des sources fermées, vous ne voudriez pas inclure vos tests dans votre distribution binaire standard au cas où. (Vous créez un ensemble séparé contenant les tests que vous souhaitez exécuter par le client.)
Stephen C
1
1) Avez-vous manqué la première partie de ma réponse où j'ai donné trois raisons réelles? Il y avait une "pensée critique" impliquée là .... 2) Avez-vous manqué la deuxième partie où j'ai dit que les programmeurs Java faisaient cela auparavant, mais ce n'est pas le cas maintenant? Et l’implication évidente que les programmeurs ont cessé de faire cela… pour une bonne raison?
Stephen C
14

En fait, vous pouvez penser à Design By Contract comme ceci. Le problème est que la plupart des langages de programmation ne vous permettent pas d'écrire du code comme celui-ci :( Il est très facile de tester manuellement les conditions préalables, mais les conditions de publication constituent un véritable défi sans changer la façon dont vous écrivez le code (une OMI extrêmement négative).

Michael Feathers a fait une présentation à ce sujet et c’est l’une des nombreuses façons dont il dit que l’on peut améliorer la qualité du code.

Daniel Kaplan
la source
13

Pour bon nombre des raisons pour lesquelles vous essayez d’éviter un couplage étroit entre les classes de votre code, il est également judicieux d’éviter tout couplage inutile entre les tests et le code.

Création: Les tests et le code peuvent être écrits à des moments différents, par différentes personnes.

Contrôle: si les tests sont utilisés pour spécifier des exigences, vous voudriez certainement qu’ils soient soumis à des règles différentes sur qui peut les changer et quand le code lui-même.

Réutilisation: si vous mettez les tests en ligne, vous ne pouvez pas les utiliser avec un autre morceau de code.

Imaginez que vous ayez un morceau de code qui fasse le travail correctement, mais laisse beaucoup à désirer en termes de performances, de maintenabilité, etc. Vous décidez de remplacer ce code par du code nouveau et amélioré. L'utilisation du même ensemble de tests peut vous aider à vérifier que le nouveau code produit les mêmes résultats que l'ancien code.

Possibilité de sélection: le fait de séparer les tests du code facilite le choix des tests que vous souhaitez exécuter.

Par exemple, vous pouvez avoir une petite suite de tests qui ne concerne que le code sur lequel vous travaillez actuellement, et une suite plus grande qui teste l'ensemble du projet.

Caleb
la source
Je suis perplexe quant à vos raisons: TDD indique déjà que la création de test a lieu avant (ou en même temps) en tant que code de production et doit être effectuée par le même codeur! Ils suggèrent également que les tests ressemblent beaucoup aux exigences. Bien sûr, ces objections ne s'appliquent pas si vous ne vous abonnez pas au dogme TDD (ce qui serait acceptable, mais vous devez être clair!). En outre, qu'est-ce qu'un test "réutilisable"? Les tests, par définition, ne sont-ils pas spécifiques au code qu'ils testent?
Andres F.
1
@AndresF. Non, les tests ne sont pas spécifiques au code qu'ils testent. ils sont spécifiques au comportement pour lequel ils sont testés. Supposons donc que vous ayez un module Widget complet avec un ensemble de tests permettant de vérifier que Widget se comporte correctement. Votre collègue propose BetterWidget, qui prétend faire la même chose que Widget mais trois fois plus vite. Si les tests pour Widget sont incorporés dans le code source du widget de la même manière que Literate Programming intègre la documentation dans le code source, vous ne pouvez pas très bien appliquer ces tests à BetterWidget pour vérifier qu'il se comporte de la même manière que Widget.
Caleb
@AndresF. inutile de préciser que vous ne suivez pas TDD. ce n'est pas un défaut cosmique. En ce qui concerne le point de réutilisation. Lorsque vous testez un système, vous vous souciez des entrées et des sorties, pas des éléments internes. Lorsque vous devez ensuite créer un nouveau système qui se comporte exactement de la même manière mais dont la mise en œuvre est différente, il est bon de disposer de tests que vous pouvez exécuter à la fois sur l'ancien et le nouveau système. cela m'est arrivé plus d'une fois, il faut parfois travailler sur le nouveau système pendant que l'ancien est toujours en production ou même le faire fonctionner côte à côte. Regardez comment Facebook testait 'réagir fibre' avec les tests de réaction pour parvenir à la parité.
user1852503
10

Voici quelques raisons supplémentaires auxquelles je peux penser:

  • avoir des tests dans une bibliothèque séparée facilite la liaison de cette bibliothèque uniquement avec votre framework de test, et non avec votre code de production (cela pourrait être évité par certains pré-processeurs, mais pourquoi le construire de telle sorte que la solution la plus simple est d'écrire les tests en un endroit séparé)

  • Les tests d'une fonction, d'une classe, d'une bibliothèque sont généralement écrits du point de vue "utilisateurs" (un utilisateur de cette fonction / classe / bibliothèque). Un tel "code d'utilisation" est généralement écrit dans un fichier ou une bibliothèque distinct (e), et un test peut être plus clair ou "plus réaliste" s'il imite cette situation.

Doc Brown
la source
5

Si les tests étaient en ligne, il serait nécessaire de supprimer le code nécessaire au test lorsque vous expédiez le produit à votre client. Ainsi, un emplacement supplémentaire dans lequel vous stockez vos tests sépare simplement le code dont vous avez besoin et le code dont votre client a besoin.

mhr
la source
9
Pas impossible. Cela nécessiterait une phase de prétraitement supplémentaire, comme le fait LP. Cela pourrait être fait facilement en C, ou dans un langage de compilation à js, par exemple.
Chris Devereux
+1 pour me l'avoir signalé. J'ai modifié ma réponse pour représenter cela.
Mhr
Il existe également une hypothèse selon laquelle la taille du code est importante dans tous les cas. Ce n’est pas parce que cela importe dans certains cas que cela importe dans tous les cas. Il existe de nombreux environnements dans lesquels les programmeurs ne sont pas motivés à optimiser la taille du code source. Si c'était le cas, ils ne créeraient pas autant de classes.
zumalifeguard
5

Cette idée revient simplement à une méthode "Self_Test" dans le contexte d'une conception à base d'objet ou orientée objet. Si vous utilisez un langage compilé basé sur des objets comme Ada, tout le code d’autotest sera marqué par le compilateur comme étant inutilisé (jamais appelé) lors de la compilation de la production, et il sera donc optimisé à l’extérieur - aucun d’entre eux n’apparaîtra dans la liste. résultat exécutable.

Utiliser une méthode "Self_Test" est une très bonne idée, et si les programmeurs étaient vraiment soucieux de la qualité, ils le feraient tous. Un problème important, cependant, est que la méthode "Self_Test" doit faire preuve d'une discipline intense, en ce sens qu'elle ne peut accéder à aucun des détails d'implémentation et qu'elle ne doit s'appuyer que sur toutes les autres méthodes publiées dans la spécification de l'objet. Évidemment, si l'autotest échoue, la mise en œuvre devra changer. L'autotest doit être un test rigoureux de toutes les propriétés publiées des méthodes de l'objet, sans jamais s'appuyer de quelque manière que ce soit sur les détails d'une implémentation spécifique.

Les langages basés sur les objets et orientés objet fournissent souvent exactement ce type de discipline en ce qui concerne les méthodes externes à l'objet testé (ils appliquent la spécification de l'objet, empêchant tout accès à ses détails d'implémentation et générant une erreur de compilation si une telle tentative est détectée ). Cependant, les méthodes internes de l'objet disposent d'un accès complet à tous les détails de la mise en œuvre. La méthode d’autotest se trouve donc dans une situation unique: il faut qu’elle soit une méthode interne en raison de sa nature (l’autotest est évidemment une méthode de l’objet testé), mais elle doit recevoir toute la discipline du compilateur d’une méthode externe ( il doit être indépendant des détails d'implémentation de l'objet). Peu ou pas de langages de programmation offrent la possibilité de discipliner un objet ' s méthode interne comme s'il s'agissait d'une méthode externe. Il s’agit donc d’un problème de conception de langage de programmation important.

En l'absence d'un support approprié du langage de programmation, la meilleure façon de le faire est de créer un objet compagnon. En d'autres termes, pour chaque objet que vous codez (appelons-le "Big_Object"), vous créez également un deuxième objet compagnon dont le nom consiste en un suffixe standard concaténé avec le nom de l'objet "réel" (dans ce cas, "Big_Object_Self_Test "), et dont la spécification consiste en une seule méthode (" Big_Object_Self_Test.Self_Test (This_Big_Object: Big_Object) return return Boolean; "). L'objet compagnon dépendra alors de la spécification de l'objet principal et le compilateur appliquera pleinement toute la discipline de cette spécification à l'implémentation de l'objet compagnon.

commenter8
la source
4

Cela fait suite à un grand nombre de commentaires suggérant que les tests en ligne ne sont pas effectués car il est difficile, voire impossible, de supprimer le code de test des versions validées. C'est faux. Presque tous les compilateurs et assembleurs le supportent déjà, avec des langages compilés, tels que C, C ++, C #, cela se fait avec ce qu'on appelle des directives de compilateur.

Dans le cas de c # (je crois aussi que c ++, la syntaxe peut être légèrement différente en fonction du compilateur que vous utilisez), voici comment vous pouvez le faire.

#define DEBUG //  = true if c++ code
#define TEST /* can also be defined in the make file for c++ or project file for c# and applies to all associated .cs/.cpp files */

//somewhere in your code
#if DEBUG
// debug only code
#elif TEST
// test only code
#endif

Comme il utilise des directives de compilation, le code n'existe pas dans les fichiers exécutables générés si les indicateurs ne sont pas définis. C’est également ainsi que vous créez des programmes "écrire une fois, compiler deux fois" pour plusieurs plates-formes / matériels.

John
la source
2

Nous utilisons des tests en ligne avec notre code Perl. Il existe un module, Test :: Inline , qui génère des fichiers de test à partir du code en ligne.

Je ne suis pas particulièrement doué pour organiser mes tests et je les ai trouvés plus faciles et plus susceptibles d'être maintenus en ligne.

Répondant à quelques préoccupations exprimées:

  • Les tests en ligne sont écrits dans des sections POD, ils ne font donc pas partie du code réel. Ils sont ignorés par l'interprète, il n'y a donc pas de problème de code.
  • Nous utilisons le pliage Vim pour masquer les sections de test. La seule chose que vous voyez est une seule ligne au-dessus de chaque méthode testée +-- 33 lines: #test----. Lorsque vous souhaitez travailler avec le test, développez-le.
  • Le module Test :: Inline "compile" les tests en fichiers normaux compatibles TAP, afin qu'ils puissent coexister avec les tests traditionnels.

Pour référence:

mla
la source
1

Erlang 2 prend en charge les tests en ligne. Toute expression booléenne du code qui n’est pas utilisée (par exemple, assignée à une variable ou passée) est automatiquement traitée comme un test et évaluée par le compilateur; si l'expression est fausse, le code ne se compile pas.

Mark Rendle
la source
1

Une autre raison de la séparation des tests est que vous utilisez souvent des bibliothèques supplémentaires ou même différentes pour les tests que pour l'implémentation réelle. Si vous combinez des tests et une implémentation, le compilateur ne pourra pas intercepter l’utilisation accidentelle de bibliothèques de tests dans l’implémentation.

En outre, les tests ont généralement beaucoup plus de lignes de code que les composants d'implémentation testés. Vous aurez donc du mal à trouver l'implémentation entre tous les tests. :-)

Hans-Peter Störr
la source
0

Ce n'est pas vrai Il est bien préférable de placer vos tests unitaires aux côtés du code de production lorsque ce dernier est utilisé, en particulier lorsque la routine de production est pure.

Si vous développez sous .NET, par exemple, vous pouvez insérer votre code de test dans l'assemblage de production, puis utiliser Scalpel pour le supprimer avant l'expédition.

zumalifeguard
la source