Y a-t-il trop d'odeur de code d'assertions?

19

Je suis vraiment tombé amoureux des tests unitaires et du TDD - je suis infecté par le test.

Cependant, les tests unitaires sont normalement utilisés pour les méthodes publiques. Parfois, même si je dois également tester certaines hypothèses-assertions dans des méthodes privées, car certaines d'entre elles sont "dangereuses" et la refactorisation ne peut pas aider davantage. (Je sais, les frameworks de test permettent de tester des méthodes privées).

Il est donc devenu une de mes habitudes que la première et la dernière ligne d'une méthode privée soient toutes deux des affirmations.

Cependant, j'ai remarqué que j'ai tendance à utiliser des assertions dans les méthodes publiques (ainsi que privées) juste "pour être sûr". Serait-ce «tester la duplication» puisque les hypothèses de la méthode publique sont testées de l'extérieur par le cadre de tests unitaires?

Quelqu'un pourrait-il penser à trop d'assertions comme une odeur de code?

Florents Tselai
la source
1
ces affirmations sont-elles activées ou désactivées lors de la diffusion?
jk.
Je travaille principalement avec Java où les assertions doivent être activées manuellement.
Florents Tselai
Je viens de trouver ce "Les assertions augmentent la productivité en traquant les bogues" programmers.stackexchange.com/a/16317/32342
Florents Tselai
comment définissez-vous «trop»?
haylem
@haylem Il y a "trop" d'assertions si un autre programmeur lit le code et dit "je suis fatigué de ces assertions".
Florents Tselai

Réponses:

26

Ces assertions sont vraiment utiles pour tester vos hypothèses, mais elles servent également un autre objectif très important: la documentation. Tout lecteur d'une méthode publique peut lire les assertions pour déterminer rapidement les conditions de pré et post, sans avoir à regarder la suite de tests. Pour cette raison, je vous recommande de conserver ces assertions pour des raisons de documentation, plutôt que pour des raisons de test. Techniquement, vous dupliquez les assertions, mais elles servent à deux fins différentes et sont très utiles dans les deux.

Il est préférable de les conserver sous forme d'affirmations plutôt que de simplement utiliser des commentaires, car ils vérifient activement les hypothèses chaque fois qu'elles sont exécutées.

Oleksi
la source
2
Très bon point!
Florents Tselai
1
Les assertions sont de la documentation, mais cela ne rend pas leur duplication légitime. Au contraire, cela soulève même des soupçons sur la qualité des tests s'il semble que les mêmes assertions soient nécessaires plus d'une fois.
Yam Marcovic
1
@YamMarcovic Je pense que cela est acceptable car les deux morceaux de code "dupliqués" ont deux objectifs différents et distincts. Je pense qu'il est utile de les avoir aux deux endroits, malgré la duplication. Avoir les assertions dans la méthode est inestimable pour la documentation, et elles sont évidemment nécessaires pour les tests.
Oleksi
2
Les assertions en code pour moi sont complémentaires aux assertions de tests unitaires. Vous n'aurez pas une couverture de test à 100% et les assertions dans le code vous aideront à affiner plus rapidement les tests unitaires défaillants.
sebastiangeiger
15

On dirait que vous essayez de faire la conception par contrat à la main.

Faire DbC est une bonne idée, mais vous devriez au moins envisager de passer à un langage qui le prend en charge nativement (comme Eiffel ) ou au moins utiliser un cadre de contrat pour votre plate-forme (par exemple, Microsoft Code Contracts for .NETest plutôt sympa, sans doute le framework de contrat le plus sophistiqué, encore plus puissant qu'Eiffel). De cette façon, vous pouvez mieux exploiter la puissance des contrats. Par exemple, en utilisant un cadre existant, vous pouvez faire apparaître les contrats dans votre documentation ou un IDE (par exemple, les contrats de code pour .NET sont affichés dans IntelliSense et, si vous avez VS Ultimate, peuvent même être vérifiés statiquement par un prouveur de théorème automatique au moment de la compilation pendant que vous tapez; de même de nombreux frameworks de contrats Java ont des doclets JavaDoc qui extrairont automatiquement les contrats dans votre documentation API JavaDoc).

Et même s'il s'avère que dans votre situation, il n'y a pas d'alternative à le faire manuellement, vous savez maintenant au moins comment il s'appelle et pouvez le lire.

Donc, en bref: si vous faites effectivement du DbC, même si vous ne le savez pas, alors ces affirmations sont parfaitement bien.

Jörg W Mittag
la source
4

Chaque fois que vous n'avez pas un contrôle total sur vos paramètres d'entrée, c'est une très bonne idée de tester à l'avance les erreurs simples. Échec sur null par exemple.

Ce n'est pas une duplication de vos tests, car ils devraient tester que le code échoue de manière appropriée en raison de mauvais paramètres d'entrée, puis documenter cela .

Je ne pense pas que vous devriez affirmer sur les paramètres de retour (sauf si vous avez explicitement un invariant que vous voulez que le lecteur comprenne). C'est aussi le travail des non-testés.

Personnellement, je n'aime pas la assertdéclaration en Java, car ils peuvent être désactivés et c'est une fausse sécurité.


la source
1
+1 Pour "Je n'aime pas la déclaration assert en Java, car ils peuvent être désactivés et il s'agit alors d'une fausse sécurité."
Florents Tselai
3

Je pense que l'utilisation d'assertions dans les méthodes publiques est encore plus importante, car là vous ne contrôlez pas les entrées et il est plus probable que l'hypothèse soit brisée.

Le test des conditions d'entrée doit être effectué dans toutes les méthodes publiques et protégées. Si les entrées sont transmises directement à une méthode privée, tester ses entrées peut être redondant.

Le test des conditions de sortie (par exemple l'état de l'objet ou cette valeur de retour! = Null) doit être effectué dans les méthodes internes (par exemple les méthodes privées). Si les sorties sont transmises directement d'une méthode privée à la sortie d'une méthode publique sans calculs supplémentaires ni changements d'état interne, le test des conditions de sortie de la méthode publique peut être redondant.

Je suis d'accord avec Oleksi, cependant, que la redondance peut augmenter la lisibilité et elle peut également augmenter la maintenabilité (si l'affectation directe ou le retour n'est plus le cas à l'avenir).

Danny Varod
la source
4
Si une condition d'erreur ou une condition d'argument non valide peut être provoquée par l'utilisateur (appelant) de l'interface publique, il convient de lui donner le traitement de traitement des erreurs complet approprié. Cela signifie formater un message d'erreur significatif pour l'utilisateur final, accompagné d'un code d'erreur, de la journalisation et des tentatives de récupération des données ou de restauration dans un état sain. La substitution d'une gestion des erreurs appropriée par des assertions rendra le code moins robuste et plus difficile à dépanner.
rwong
Les assertions pourraient (et dans de nombreux cas devraient) inclure des exceptions de journalisation et de levée (ou des codes d'erreur en C--).
Danny Varod
3

Il est difficile d'être indépendant de la langue à propos de ce problème, car les détails de la façon dont les assertions et la gestion «correcte» des erreurs / exceptions sont implémentées ont une incidence sur la réponse. Voici mon 0,02 $, basé sur ma connaissance de Java et C ++.

Les affirmations dans les méthodes privées sont une bonne chose, en supposant que vous n'allez pas trop loin et ne les mettez pas partout . Si vous les appliquez à des méthodes très simples ou vérifiez à plusieurs reprises des choses comme des champs immuables, vous encombrez le code inutilement.

Il vaut mieux éviter les affirmations dans les méthodes publiques. Vous devriez toujours faire des choses comme vérifier que le contrat de méthode n'est pas violé, mais si c'est le cas, vous devriez lancer des exceptions de type approprié avec des messages significatifs, revenir à l'état lorsque cela est possible, etc. (ce que @rwong appelle "le plein traitement correct des erreurs ").

De manière générale, vous ne devez utiliser que des assertions pour faciliter votre développement / débogage. Vous ne pouvez pas supposer que quiconque utilise votre API aura même des assertions activées lorsqu'il utilisera votre code. Bien qu'ils aient une certaine utilité pour aider à documenter le code, il existe souvent de meilleures façons de documenter les mêmes choses (c.-à-d. Documentation de méthode, exceptions).

vaughandroid
la source
2

Ajout à la liste des réponses (principalement) exceptionnelles, une autre raison pour beaucoup d'affirmations et de duplication, c'est que vous ne savez pas comment, quand ni par qui la classe sera modifiée au cours de sa durée de vie. Si vous écrivez du code à jeter qui sera mort dans un an, ce n'est pas un problème. Si vous écrivez du code qui sera utilisé dans plus de 20 ans, il sera très différent - et une supposition que vous avez faite peut ne plus être valide. Le gars qui fait ce changement vous remerciera pour les assertions.

De plus, tous les programmeurs ne sont pas parfaits - encore une fois, les assertions signifient que l'un de ces "dérapages" ne se propagera pas trop loin.

Ne sous-estimez pas l'effet que ces affirmations (et d'autres vérifications sur les hypothèses) auront sur la réduction des coûts de maintenance.

mattnz
la source
0

Avoir des assertions en double n'est pas mauvais en soi, mais avoir les assert "juste pour être sûr" n'est pas le meilleur. Cela se résume vraiment à ce que chaque test essaie d'accomplir. N'affirmez que ce qui est absolument nécessaire. Si un test utilise Moq pour vérifier qu'une méthode est invoquée, peu importe ce qui se passe après l'invocation de cette méthode, le test ne vise qu'à s'assurer que la méthode est invoquée.

Si chaque test unitaire a le même ensemble d'assertions, sauf un ou deux, alors lorsque vous refactorisez un peu tous les tests peuvent échouer pour la même raison exacte, au lieu de vous montrer le véritable impact du refactor. Vous pourriez vous retrouver dans une situation où vous exécutez tous les tests, ils échouent tous parce que chaque test a les mêmes assertions, vous corrigez cet échec, réexécutez les tests, ils échouent pour une autre raison, vous corrigez cet échec. Répétez jusqu'à ce que vous ayez terminé.

Pensez également à la maintenance de votre projet de test. Que se passe-t-il lorsque vous ajoutez un nouveau paramètre ou que la sortie est légèrement modifiée, devrez-vous revenir en arrière à travers un tas de tests et modifier une assertion ou ajouter une assertion? Et, selon votre code, vous pourriez avoir à configurer beaucoup plus juste pour vous assurer que toutes les assertions passent.

Je peux comprendre l'attrait de vouloir inclure des assertions en double dans le test unitaire, mais c'est vraiment exagéré. J'ai suivi le même chemin avec mon premier projet de test. Je m'en suis éloigné car l'entretien seul me rendait dingue.

bwalk2895
la source
0

Cependant, les tests unitaires sont utilisés pour les méthodes publiques.

Si votre infrastructure de test autorise les accesseurs, alors les tests unitaires des méthodes privées sont également une bonne idée (IMO).

Quelqu'un pourrait-il penser à trop d'assertions comme une odeur de code?

Je fais. Les affirmations sont correctes, mais votre premier objectif devrait être de faire en sorte que le scénario ne soit même pas possible ; pas seulement que cela n'arrive pas.

Telastyn
la source
-2

C'est une mauvaise pratique.

Vous ne devez pas tester les méthodes publiques / privées. Vous devez tester la classe dans son ensemble. Assurez-vous simplement d'avoir une bonne couverture.

Si vous optez pour la méthode (primitive) de tester chaque méthode séparément en règle générale, vous aurez du mal à refactoriser votre classe à l'avenir. Et c'est l'une des meilleures choses que TDD vous permet de faire.

Par ailleurs, il est double emploi. De plus, cela suggère que l'auteur du code ne savait pas vraiment ce qu'il faisait. Et le plus drôle, c'est que vous le faites .

Certaines personnes traitent leurs tests avec moins de soin que leur code de production. Ils ne devraient pas. Si quoi que ce soit, mon expérience suggère même de traiter les tests avec plus de soin. Ils en valent la peine.

Yam Marcovic
la source
-1 Le test unitaire d'une classe dans son ensemble peut être dangereux car vous finissez par tester plus d'une chose par test. Cela rend les tests vraiment fragiles. Vous devez tester un comportement à la fois, ce qui correspond souvent à tester une seule méthode par test.
Oleksi
En ce qui concerne également "cela suggère que l'auteur du code ne savait pas vraiment ce qu'il faisait [...] vous faites". Les futurs développeurs pourraient ne pas être aussi clairs sur ce que fait une méthode particulière. Dans ce cas, avoir des conditions pré et post sous forme d'assertions peut être inestimable pour comprendre un morceau de code.
Oleksi
2
@Oleksi Un bon test porte sur des scénarios d'utilisation valides, et non sur l'affirmation de tous les moindres détails non pertinents. Les assertions redondantes sont en effet une odeur de code en raison de l'intention obscure et ne sont qu'un autre mauvais exemple de programmation défensive.
Yam Marcovic