Programmation basée sur contrat vs test unitaire

13

Je suis un programmeur quelque peu défensif et un grand fan des contrats de code Microsofts.

Maintenant, je ne peux pas toujours utiliser C # et dans la plupart des langues, le seul outil dont je dispose est les assertions. Je me retrouve donc généralement avec un code comme celui-ci:

class
{       
    function()
    {   
         checkInvariants();
         assert(/* requirement */);

         try
         {
             /* implementation */
         }
         catch(...)
         {
              assert(/* exceptional ensures */);                  
         }
         finally
         {
              assert(/* ensures */);
              checkInvariants();
         }
    }

    void checkInvariants()
    {
         assert(/* invariant */);
    }
}

Cependant, ce paradigme (ou ce que vous appelleriez) conduit à beaucoup d'encombrement de code.

J'ai commencé à me demander si cela en valait vraiment la peine et si un test unitaire approprié couvrirait déjà cela?

ronag
la source
6
Bien que les tests unitaires permettent de déplacer des assertions hors du code d'application (et donc d'éviter l'encombrement), considérez qu'ils ne peuvent pas vérifier tout ce qui peut se produire dans le système de production réel. Les contrats de code IMO présentent donc certains avantages, en particulier pour le code critique où l'exactitude est particulièrement importante.
Giorgio
Il s'agit donc essentiellement de temps de développement, de maintenabilité, de lisibilité par rapport à une meilleure couverture de code?
ronag
1
J'utilise principalement l'assertion dans le code pour vérifier l'exactitude des paramètres (sur null par exemple) et dans le test unitaire, vérifiez également ces assertions.
artjom

Réponses:

14

Je ne pense pas que vous devriez le considérer comme "vs".
Comme mentionné dans les commentaires de @Giorgio, les contrats de code doivent vérifier les invariants (dans l'environnement de production) et les tests unitaires doivent s'assurer que le code fonctionne comme prévu lorsque ces conditions sont remplies.

duros
la source
2
Je pense qu'il est également important de tester si le code fonctionne (par exemple, il lève une exception) lorsque les conditions ne sont pas remplies.
svick
6

Les contrats vous aident avec au moins une chose que les tests unitaires ne permettent pas. Lorsque vous développez une API publique, vous ne pouvez pas tester de manière unitaire comment les gens utilisent votre code. Vous pouvez cependant toujours définir des contrats pour vos méthodes.

Personnellement, je serais aussi rigoureux à propos des contrats que lorsqu'il s'agit de l'API publique d'un module. Dans de nombreux autres cas, cela ne vaut probablement pas la peine (et vous pouvez utiliser des tests unitaires à la place), mais ce n'est que mon avis.

Cela ne signifie pas que je conseille de ne pas penser aux contrats dans ces cas. Je pense toujours à eux. Je ne pense pas qu'il soit nécessaire de toujours les coder explicitement.

Honza Brabec
la source
1

Comme déjà mentionné, les contrats et les tests unitaires ont un objectif différent.

Les contrats concernent la programmation défensive pour s'assurer que les conditions préalables sont remplies, le code est appelé avec les bons paramètres, etc.

Tests unitaires pour s'assurer que le code fonctionne, dans différents scénarios. Ce sont comme des «spécifications avec des dents».

Les assertions sont bonnes, elles rendent le code robuste. Cependant, si vous craignez qu'il ajoute beaucoup de code, vous pouvez également ajouter des points d'arrêt conditionnels à certains endroits pendant le débogage et réduire le nombre d'assertions.

Sajad Deyargaroo
la source
0

Tout ce que vous avez dans vos appels checkVariants () pourrait être fait à partir des tests, combien d'efforts pourraient en fait dépendre de nombreuses choses (dépendances externes, niveaux de couplage, etc.) mais cela nettoierait le code d'un point de vue. Je ne sais pas si une base de code développée contre les assertions pourrait être testée sans refactorisation.

Je suis d'accord avec @duros, celles-ci ne doivent pas être considérées comme des approches exclusives ou concurrentes. En fait, dans un environnement TDD, vous pourriez même affirmer que les affirmations «d'exigence» devraient nécessiter des tests :).

Les assertions ne rendent cependant pas le code plus robuste à moins que vous ne fassiez quelque chose pour corriger l'échec de la vérification, elles arrêtent simplement les données d'être corrompues ou similaires, généralement en abandonnant le traitement au premier signe de problème.

Une solution pilotée par des tests / bien testée aura généralement déjà pensé et / ou découvert de nombreuses sources / raisons de mauvaises entrées et sorties tout en développant les composants en interaction et les a déjà traités plus près de la source du problème.

Si votre source est externe et que vous n'avez aucun contrôle sur elle, alors pour éviter d'encombrer votre code face à d'autres problèmes de code, vous pourriez envisager d'implémenter une sorte de composant de nettoyage / assertion de données entre la source et votre composant et y mettre vos vérifications .

De plus, je suis curieux de savoir quelles langues vous n'utilisez pas une sorte de xUnit ou une autre bibliothèque de test que quelqu'un a développée, je pensais qu'il y avait à peu près quelque chose pour tout ces jours-ci?

Chris Lee
la source
0

En plus des tests unitaires et des contrats de code, je pensais simplement mettre en évidence un autre aspect, qui est la valeur de la définition de vos interfaces de telle sorte que vous supprimez ou réduisez la possibilité d'appeler votre code de manière incorrecte en premier lieu.

Ce n'est pas toujours facile ni possible, mais cela vaut vraiment la peine de vous poser la question: "Puis-je rendre ce code plus infaillible?"

Anders Hejlsberg, créateur de C #, a déclaré que l'une des plus grandes erreurs en C # n'incluait pas les types de référence non annulables. C'est l'une des principales raisons pour lesquelles il existe un encombrement de code de garde si nécessaire.

Pourtant, le refactoring pour ne disposer que de la quantité nécessaire et suffisante de code de garde rend, selon mon expérience, un code plus utilisable et plus maintenable.

D'accord avec @duros sur le reste.

James World
la source
0

Faites les deux, mais créez des méthodes d'assistance statiques pour clarifier vos intentions. C'est ce que Google a fait pour Java, consultez code.google.com/p/guava-libraries/wiki/PreconditionsExplained

Alexander Torstling
la source