web-dev-qa-db-fra.com

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

Les assertions et les tests unitaires servent de documentation pour une base de code et de moyen de découvrir les bogues. Les principales différences sont que les assertions fonctionnent comme des vérifications d'intégrité et voient les entrées réelles, tandis que les tests unitaires s'exécutent sur des entrées simulées spécifiques et sont des tests par rapport à une "bonne réponse" bien définie. Quels sont les avantages relatifs de l'utilisation d'assertions par rapport aux tests unitaires comme principal moyen de vérifier l'exactitude? À votre avis, sur quoi devriez-vous insister davantage?

52
dsimcha

Asserts sont utiles pour vous parler de l'état interne du programme. Par exemple, que vos structures de données ont un état valide, par exemple, qu'une structure de données Time ne contiendra pas la valeur de 25:61:61. Les conditions vérifiées par assert sont:

  • Les conditions préalables, qui garantissent que l'appelant conserve son contrat,

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

  • Invariants, qui garantissent que la structure de données détient toujours une propriété après le retour de la fonction. Un invariant est une condition qui est une condition préalable et une postcondition.

Les tests unitaires sont utiles pour vous parler de le comportement externe du module. Votre Stack peut avoir un état cohérent après l'appel de la méthode Push(), mais si la taille de la pile n'augmente pas de trois après avoir été appelée trois fois, alors c'est une erreur . (Par exemple, le cas trivial où l'implémentation incorrecte de Push() vérifie uniquement les assertions et les sorties.)

À strictement parler, la principale différence entre les assertions et les tests unitaires est que les tests unitaires ont des données de test (valeurs pour exécuter le programme), contrairement aux assertions. Autrement dit, vous pouvez exécuter vos tests unitaires automatiquement, tandis que vous ne pouvez pas en dire autant pour les assertions. Pour les besoins de cette discussion, j'ai supposé que vous parlez d'exécuter le programme dans le contexte de tests de fonction d'ordre supérieur (qui exécutent l'ensemble du programme et ne pilotent pas de modules comme les tests unitaires). Si vous ne parlez pas des tests de fonction automatisés comme du moyen de "voir les entrées réelles", alors la valeur réside clairement dans l'automatisation, et donc les tests unitaires gagneraient. Si vous en parlez dans le cadre de tests de fonctions (automatisés), voyez ci-dessous.

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

Pour les deux, l'objectif est d'augmenter la qualité. Pour les tests unitaires, le but est de trouver des bugs. Pour les assertions, l'objectif est de faciliter le débogage en observant les états de programme invalides dès qu'ils se produisent.

Notez que aucune des deux techniques 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, selon vous, fonctionnera. C'est un effet psychologique: vous ferez tout pour atteindre votre objectif. Si votre objectif est de trouver des bugs, vos activités en seront le reflet.

Les deux sont importants et ont leurs propres objectifs.

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

43
Macneil

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

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 quelque chose comme NDEBUG.

Un test unitaire serait (probablement) simplement une erreur de segmentation sur le code ci-dessus. Bien sûr, il a fait son travail en vous disant que quelque chose s'est mal passé, ou est-ce que c'est arrivé? Quelle est la probabilité que malloc() échoue lors du test?

Les assertions sont à des fins de débogage lorsqu'un programmeur doit impliquer qu'aucun événement "normal" ne provoquerait le déclenchement de l'assertion. malloc() l'échec est, en effet un événement normal, ne doit donc jamais être affirmé.

Il existe de nombreux autres cas où des assertions sont utilisées au lieu de traiter de manière adéquate des choses qui pourraient mal tourner. C'est pourquoi les assertions ont mauvaise réputation et pourquoi les langues comme Go ne les incluent pas.

Les tests unitaires sont conçus pour vous dire quand quelque chose que vous avez changé a cassé quelque chose d'autre. Ils sont conçus pour vous éviter (dans la plupart des cas) de parcourir toutes les fonctionnalités du programme (cependant, les testeurs sont importants pour les versions) chaque fois que vous effectuez une construction.

Il n'y a vraiment aucune corrélation distincte entre les deux, à part que les deux vous disent que quelque chose s'est mal passé. Considérez une assertion comme un point d'arrêt dans quelque chose sur lequel vous travaillez, sans avoir à utiliser un débogueur. Considérez un test unitaire comme quelque chose qui vous indique si vous avez cassé quelque chose sur lequel vous ne travaillez pas.

7
Tim Post

Ce sont deux outils utilisés pour aider à 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 créez et de l'endroit où votre temps est le mieux dépensé. Sans oublier que vous avez quelques écoles de réflexion à ce sujet.

Pour commencer, si vous utilisez une langue sans mot clé assert, vous ne pouvez pas utiliser d'assertions (du moins pas de la manière dont nous parlons ici). Pendant longtemps, Java n'avait pas de mot clé assert, et un certain nombre de langues n'en ont 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 (à nouveau avec Java ici). Lorsque les protections ne sont pas toujours là, ce n'est pas une fonctionnalité très utile.

Il y a une école de pensée qui dit que si vous "affirmez" quelque chose, vous pourriez aussi bien écrire un bloc d'exceptions significatif if/throw. Ce processus de réflexion provient de nombreuses assertions placées au début d'une méthode pour garantir que toutes les valeurs sont dans des limites. Tester vos conditions préalables est un élément très important pour avoir une postcondition attendue.

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

Personnellement, je trouve que j'obtiens plus de kilométrage avec les tests unitaires que les assertions par aspersion, mais c'est à cause des 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 "Design by Contract" (voir ci-dessous) pour fournir encore plus de garanties. Si je travaillais avec l'une de ces langues, je pourrais utiliser le DBC plus que les tests unitaires.

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

5
Berin Loritsch