Les assertions ou les tests unitaires sont-ils plus importants?

51

Les assertions et les tests unitaires servent à la fois de documentation pour une base de code et de moyen de détecter les bogues. Les principales différences sont que les assertions fonctionnent comme des contrôles de cohérence et voient les entrées réelles, alors que les tests unitaires fonctionnent sur des entrées simulées spécifiques et sont des tests par rapport à une "bonne réponse" unique et bien définie. Quels sont les avantages relatifs de l'utilisation d'assertions par rapport à des tests unitaires comme principal moyen de vérifier l'exactitude? Selon vous, sur lequel devrait-on insister plus lourdement?

Dsimcha
la source
4
J'aime beaucoup cette idée ... à tel point que je fais de ce sujet un devoir pour mon cours de test logiciel et d'assurance de la qualité. :-)
Macneil
Une autre question est la suivante: devriez-vous tester vos affirmations? ;)
mojuba

Réponses:

43

Les assertions sont utiles pour vous informer de l'état interne du programme . Par exemple, vos structures de données ont un état valide, par exemple, une Timestructure de données ne tiendra pas la valeur de 25:61:61. Les conditions vérifiées par les assertions sont les suivantes:

  • Conditions préalables, qui assurent que l'appelant respecte son contrat,

  • Postconditions, qui assure que l'appelé respecte son contrat, et

  • Les invariants, qui assurent que la structure de données contient toujours une propriété après le retour de la fonction. Un invariant est une condition qui est une condition préalable et une condition ultérieure.

Les tests unitaires sont utiles pour vous informer sur le comportement externe du module . Vous Stackpouvez avoir un état cohérent après l' push()appel de la méthode, mais si la taille de la pile n'augmente pas de trois après l'appel de trois fois, il s'agit d'une erreur. (Par exemple, le cas trivial où l' push()implémentation incorrecte vérifie uniquement les assertions et se termine.)

Strictement parlant, la principale différence entre les assertions et les tests unitaires réside dans le fait que les tests unitaires contiennent des données de test (valeurs permettant au programme de s'exécuter), contrairement aux assertions. Autrement dit, vous pouvez exécuter vos tests unitaires automatiquement, alors que vous ne pouvez pas en dire autant pour les assertions. Pour les besoins de cette discussion, j'ai supposé que vous parliez de l'exécution du programme dans le contexte des tests de fonction d'ordre supérieur (qui exécutent l'ensemble du programme et ne pilotent pas les modules comme les tests unitaires). Si vous ne parlez pas de tests de fonction automatisés comme moyen de "voir les entrées réelles", alors clairement l'intérêt de l'automatisation réside dans le succès des tests unitaires. Si vous en parlez dans le cadre de tests de fonctions (automatisés), reportez-vous à la section ci-dessous.

Il peut y avoir un certain chevauchement dans ce qui est testé. Par exemple, Stackla postcondition de a peut en fait affirmer que la taille de la pile augmente de un. Mais il y a des limites à ce qui peut être fait dans cette affirmation: faut-il aussi vérifier que l'élément supérieur est ce qui vient d'être ajouté?

Pour les deux, l'objectif est d'améliorer la qualité. Pour les tests unitaires, l'objectif est de trouver des bogues. Pour les assertions, l'objectif est de faciliter le débogage en observant les états de programme non valides dès qu'ils se produisent.

Notez que ni l'une ni l'autre technique ne vérifie l'exactitude. En fait, si vous effectuez des tests unitaires dans le but de vérifier que le programme est correct, vous obtiendrez probablement un test sans intérêt qui, à votre connaissance, fonctionnera. C'est un effet psychologique: vous ferez tout ce qui est en votre pouvoir pour atteindre votre objectif. Si votre objectif est de trouver des bugs, vos activités le refléteront.

Les deux sont importants et ont leurs propres objectifs.

[Note finale sur les assertions: pour obtenir le maximum de valeur, vous devez les utiliser à tous les points critiques de votre programme, et non à quelques fonctions clés. Sinon, la source du problème d'origine aurait peut-être été masquée et difficile à détecter sans des heures de débogage.]

Macneil
la source
7

Lorsque vous parlez d'assertions, gardez à l'esprit qu'elles peuvent être désactivées sur simple pression d'un commutateur.

Exemple d'une très mauvaise assertion:

char *c = malloc(1024);
assert(c != NULL);

Pourquoi est-ce mauvais? Parce qu'aucune vérification d'erreur n'est effectuée si cette assertion est ignorée en raison de la définition de NDEBUG.

Un test unitaire serait (probablement) juste une erreur de segmentation sur le code ci-dessus. Bien sûr, il a fait son travail en vous disant que quelque chose ne va pas, ou l'a-t-il fait? Quelle est la probabilité malloc()d'échec sous le test?

Les assertions sont à des fins de débogage lorsqu'un programmeur doit impliquer qu'aucun événement «normal» ne déclencherait l'assertion. malloc()échouer est, en effet, un événement normal, donc ne devrait jamais être affirmé.

Il existe de nombreux autres cas dans lesquels des assertions sont utilisées au lieu de traiter correctement des problèmes pouvant aller mal. C'est pourquoi les assertions ont mauvaise réputation et que des langues telles que Go ne les incluent pas.

Les tests unitaires sont conçus pour vous avertir lorsque quelque chose que vous avez modifié a cassé autre chose. Ils sont conçus pour vous éviter (dans la plupart des cas) de parcourir toutes les fonctionnalités du programme (les testeurs sont toutefois importants pour les versions) à chaque fois que vous effectuez une construction.

Il n'y a pas vraiment de corrélation distincte entre les deux, si ce n'est qu'ils vous ont tous deux dit que quelque chose s'était mal passé. Pensez à une assertion comme un point d'arrêt dans quelque chose sur lequel vous travaillez, sans avoir à utiliser un débogueur. Pensez à un test unitaire comme quelque chose qui vous dit si vous avez cassé quelque chose sur lequel vous ne travaillez pas .

Tim Post
la source
3
C'est pourquoi les assertions sont pour toujours supposées être des déclarations vraies et non des tests d'erreur.
Dominique McDonnell
@DominicMcDonnell Eh bien, les déclarations "devrait être vraie". J'affirme parfois qu'il faut contourner les bizarreries du compilateur, telles que certaines versions de gcc intégrant un buggy abs (). L'important, c'est de se rappeler que les versions de production devraient de toute façon être désactivées .
Tim Post
Et là , je pense que le code de production est l'endroit qui a besoin de l'affirme le plus , parce qu'il est dans la production où vous obtiendrez des entrées que vous ne pensiez pas possible. La production est l'endroit où chassent tous les bugs les plus difficiles.
Frank Shearar
@ Frank Shearar, très vrai. Vous devriez toujours échouer durement au premier état d'erreur détecté en production. Bien sûr, les utilisateurs vont se plaindre, mais c’est la seule façon de s’assurer que les bugs seront corrigés. Et c'est bien mieux d'avoir un bla à 0 plutôt qu'une exception de mémoire pour déréférencer null quelques appels de fonction plus tard.
Dominique McDonnell
pourquoi n'est-il pas préférable de traiter des problèmes typiques mais souvent rares (aux yeux de l'utilisateur) plutôt que d'affirmer qu'ils ne devraient jamais se produire? Je ne parviens pas à réaliser cette sagesse. Bien sûr, affirmez qu'un générateur de nombres premiers renvoie un nombre premier, ce qui nécessite un peu de travail supplémentaire, comme prévu dans les versions de débogage. Affirmer quelque chose qu'une langue peut tester de manière native est tout simplement stupide. Ne pas envelopper ces tests dans un autre commutateur qui peut être désactivé quelques mois après une sortie, encore plus stupide.
Tim Post
5

Ce sont deux outils utilisés pour améliorer la qualité globale du système que vous construisez. Cela dépend beaucoup de la langue que vous utilisez, du type d'application que vous construisez et du meilleur endroit où vous passerez votre temps. Sans compter que vous avez quelques écoles de pensée à ce sujet.

Pour commencer, si vous utilisez une langue sans assertmot-clé, vous ne pouvez pas utiliser d'assertions (du moins pas comme nous parlons ici). Pendant longtemps, Java n’avait pas de assertmot-clé et de nombreuses langues n’en avaient toujours pas. Les tests unitaires deviennent alors un peu plus importants. Dans certaines langues, les assertions ne sont exécutées que lorsqu'un indicateur est défini (ici encore avec Java). Lorsque les protections ne sont pas toujours là, ce n'est pas une fonctionnalité très utile.

Il existe une école de pensée qui dit que si vous "affirmez" quelque chose, vous pourriez aussi bien écrire un bloc d'exception if/ throwsignificatif. Ce processus de pensée provient de nombreuses assertions placées au début d'une méthode pour s'assurer que toutes les valeurs sont dans des limites. Tester vos conditions préalables est une partie très importante de la postcondition attendue.

Le test unitaire est un code supplémentaire qui doit être écrit et maintenu. Pour beaucoup, c'est un inconvénient. Cependant, avec la série actuelle de frameworks de tests unitaires, vous pouvez générer un plus grand nombre de conditions de test avec relativement peu de code. Les tests paramétrés et les "théories" effectueront le même test avec un grand nombre d'échantillons de données pouvant révéler des bogues difficiles à trouver.

Personnellement, je trouve que les tests unitaires me permettent de gagner plus de temps que de parler de saupoudrage, mais cela est dû aux plates-formes que je développe la plupart du temps (Java / C #). D'autres langues ont un support d'assertion plus robuste, et même "Conception par contrat" ​​(voir ci-dessous) pour offrir encore plus de garanties. Si je travaillais avec l'une de ces langues, je pourrais utiliser davantage le DBC que les tests unitaires.

http://en.wikipedia.org/wiki/Design_by_contract#Languages_with_native_support

Berin Loritsch
la source