8
votes

Test de l'unité - algorithme ou échantillon basé?

Dis que j'essaie de tester une classe de jeu simple xxx

et supposons que j'essaie de tester qu'aucune valeur en double ne peut exister dans l'ensemble. Ma première option consiste à insérer quelques données d'échantillons dans l'ensemble et à tester des doublons à l'aide de mes connaissances des données que j'ai utilisées, par exemple: xxx

Ma deuxième option est de tester Pour ma condition générique de manière générique: xxx

Bien sûr, dans cet exemple, j'ai facilement une implémentation définie pour vérifier, ainsi que le code pour comparer les collections (CollectionAssert). Mais que se passe-t-il si je n'avais pas non plus? Ce code serait définitivement plus compliqué que celui de l'option précédente! Et c'est la situation lorsque vous testez votre logique commerciale personnalisée de la vie réelle.

accordé, le test des conditions attendues couvre génériquement plus de cas - mais il devient très similaire à la mise en œuvre de la logique (qui est à la fois fastidieuse et inutile - vous ne pouvez pas utiliser le même code pour vérifier!). Fondamentalement, je demande si mes tests devraient ressembler à "insérer 1, 2, 3 vérifier quelque chose d'environ 3" ou "insert 1, 2, 3 et vérifiez quelque chose en général"

modifier - pour aider Moi comprennent, veuillez indiquer dans votre réponse si vous préférez l'option 1 ou l'option 2 (ou ni, ni que cela dépend de l'affaire, etc.). Juste pour clarifier, il est assez clair que dans cette affaire ( INTSE ), l'option 2 est meilleure dans tous les aspects. Cependant, ma question concerne les cas où vous ne faites pas avoir une mise en œuvre alternative à vérifier. Le code de l'option 2 serait donc définitivement plus compliqué que l'option 1. >


0 commentaires

6 Réponses :


2
votes

Si vous avez une mise en œuvre alternative, utilisez-le définitivement.

Dans certaines situations, vous pouvez éviter de réimplémenter une mise en œuvre alternative, mais de tester toujours la fonctionnalité en général. Par exemple, dans votre exemple, vous pouvez d'abord générer un ensemble de valeurs uniques, puis des éléments en double au hasard avant de la transmettre à votre implémentation. Vous pouvez tester que la sortie est équivalente à votre vecteur de départ, sans avoir à réimplémenter le tri.

J'essaie de prendre cette approche chaque fois que c'est réalisable.

Mise à jour: essentiellement, je préconise "l'option n ° 2" de l'OP. Avec cette approche, il y a précisément un vecteur de sortie qui permettra au test de passer. Avec "Option n ° 1", il existe un nombre infini de vecteurs de sortie acceptables (il teste un invariant, mais il ne teste pas de relation avec les données d'entrée).


8 commentaires

TRUE (+1), mais dans RL, vous n'aurez pas le code équivalent à celui qui compare les collections (CollectionSassert). Écrire ce code est presque comme écrire le Set à nouveau (bien que vous puissiez le faire moins efficacement, bien sûr)


@ohadsc: vraiment? Cette fonction particulière n'est qu'un test simple de comparaison. (a) Pourquoi n'auriez-vous pas accès à cela? (b) Même si vous ne l'avez pas fait, il serait trivial d'écrire une méthode pour comparer deux vecteurs.


Peut-être que j'ai mal mangé - oublie l'ensemble. Mon point est que dans les cas rl, vous n'aurez pas le code équivalent à celui de la comparaison des collections - qui fait partie du code qui vérifie votre condition.


@OHADSC: Oh, je vois! Cependant, écrire un test implique toujours d'écrire une sorte de code PASS / FAIL ...


Vrai, mais la question est de savoir comment est "intelligent" que le code PASS / FAIL? est-il suffisamment intelligent de travailler uniquement avec les données que vous avez insérées, ne vérifiant éventuellement qu'une seule partie de celui-ci, rester simple - ou est-il générique dans le sens où il fonctionnerait pour toutes les données et couvre probablement plus de cas (au détriment de être plus compliqué)?


@OHADSC: Je prends ton point. Vous avez raison, il n'est pas toujours trivial d'écrire un code de passe / défaillance générique. Mais espérons-le dans de nombreux cas, vous devriez être capable de construire un test qui se résume à une sorte de simple "cela correspond à cela?".


@Oli - Je pense que c'est exactement l'option 1, dans ma terminologie?


@OHADSC: Non, je préconise l'option n ° 2 chaque fois que c'est possible. Avec l'option n ° 1, il existe un nombre infini de vecteurs de sortie qui passeraient le test. Avec l'option n ° 2, il y a précisément une façon pour le test de passer.



1
votes

Selon Modèles de test Xunit , il est Habituellement plus favorable pour tester l'état du système sous test. Si vous souhaitez tester son comportement et la manière dont l'algorithme fonctionne, vous pouvez utiliser des tests d'objet simulés.

Cela étant dit, vos deux tests sont appelés tests basés sur des données. Ce qui est généralement acceptable, c'est d'utiliser autant de connaissances que l'API fournit. N'oubliez pas que ces tests servent également de documentation pour votre logiciel. Par conséquent, il est essentiel de les garder aussi simples que possible - quoi que ce soit pour votre cas spécifique.


1 commentaires

Oui, j'essaie de vérifier si l'ensemble a maintenant des duplicats ou non - c'est l'état. Ma question reste - devrais-je opter pour la première option, ou la seconde?



1
votes

essentiellement, je demande si mes tests devrait ressembler à "insérer 1, 2, 3 puis Vérifiez quelque chose d'environ 3 "ou" insérer 1, 2, 3 et vérifiez quelque chose dans Général "

Je ne suis pas puriste TDD, mais il semble que les gens disent que le test devrait casser si la condition que vous essayez de tester est cassée. E.I. Si vous implémentez un test qui vérifie une condition générale, votre test se cassera dans plus de quelques cas, ce qui n'est pas optimal.

Si je teste de ne pas pouvoir ajouter des doublons, je ne testerais que cela. Donc, dans ce cas, je dirais que j'irais avec premier .

(mise à jour)

OK, vous avez maintenant mis à jour le code et j'ai besoin de mettre à jour ma réponse.

Lequel choisirais-je? Cela dépend de la mise en œuvre de CollectionAssert.Areequivalent (nouveau hashset (valeurs), définie); . Par exemple, ienumerable conserve la commande tandis que hashset ne peut même pas casser le test pendant que cela ne devrait pas. Pour moi premier est toujours supérieur.


3 commentaires

Je ne suis pas d'accord: les deux extraits de code d'exemple teste la même contrainte (en ce sens qu'ils échouent tous les deux si le module-sous-test se casse de quelque manière que ce soit). C'est juste que la deuxième approche le fait de manière plus simple (moins de code de test doit être écrit).


Oh, cela a été mis à jour il y a 5 minutes et je n'ai pas vu cela. Permettez-moi de lire à nouveau.


Oubliez cet exemple spécifique, je parle de l'affaire équivalente à ne pas avoir CollectionSsert.Areequivalent du tout (ce qui signifie que vous devriez la mettre en œuvre vous-même si vous vouliez cette logique)!



2
votes

Je préfère généralement tester les cas d'utilisation un par un - cela fonctionne bien la TDD de manière: "Code un peu, testez un peu". Bien sûr, après un certain temps, mes cas de test commencent à contenir du code dupliqué, donc j'ai refacteur. La méthode réelle de vérification des résultats n'a pas d'importance pour moi tant qu'elle fonctionne à coup sûr et ne se met pas dans la voie de l'essai lui-même. Donc, s'il existe une "mise en œuvre de référence" à tester, c'est tout meilleur.

Une chose importante, cependant, est que les tests doivent être reproducables et il devrait être clair que chaque méthode de test teste réellement . Pour moi, insérer des valeurs aléatoires dans une collection n'est ni - bien sûr s'il existe un énorme de cas de données / utilisations impliqués, chaque outil ou chaque approche est accueilli pour mieux gérer la situation sans me bercer dans un faux sens de la sécurité.


10 commentaires

Donc, vous dites - aller pour l'option 2 quand facile / pratique, mais l'option 1 ferait aussi?


@ohadsc, fondamentalement oui. Dans mes tests de l'unité, ce chèque serait extrait de toute façon une méthode d'assistance, donc je ne serais donc pas dérangé par la différence :-)


@ Péter Török - Lorsque vous dites "Ce chèque" voulez-vous dire CollectionSsert.AreeeQuivalent (Nouveau HASHSET (valeurs), ensemble) ?


@OHADSC, soit que cela soit ou votre variante 1. Je veux dire, la mise en œuvre réelle de la méthode d'assistance serait, bien, un détail de la mise en œuvre :-)


Droite :) Cependant, mon point est que, dans de nombreux cas d'option 2, la mise en œuvre de cette méthode d'assistance devrait être très similaire à la logique du code testé. Par exemple, dites que vous avez une fonction qui obtient un arbre et retourne ses feuilles - comment vérifieriez-vous généralement sans comparer à une autre mise en œuvre d'une fonction qui fait la même chose?


@ohadsc, tant que j'ai une implémentation de référence qui est prouvée comme étant correcte, je ne me soucie pas de ses détails de mise en œuvre pendant le test de l'unité . Bien sûr, cela peut être intéressant et important de voir l'état de la technique lorsque vous écrivez ma propre implémentation .


@ Péter Török - Ma question est précisément que faites-vous lorsque vous ne avez-vous une opération de référence?


@ohadsc, désolé, je vous ai mal compris. OK, quand il n'y a pas de mise en œuvre de référence, il devrait toujours y avoir une sorte de spécification. Donc, j'écris le code le plus simple qui puisse vérifier qu'une partie donnée de la spécification est remplie. Comme j'ajoute plus de cas de test, le code de vérification peut être refactored / étendu pour répondre aux besoins plus généraux. Et je m'efforce de tester le code de test lui-même également, par «briser» mes tests intentionnellement et en vérifiant qu'ils échouent effectivement quand ils sont censés.


"Alors j'écris le code le plus simple qui puisse vérifier qu'une partie donnée de la spécification est remplie" - vous choisiriez donc entre les options 1 et 2 en fonction de ce qui est plus simple?


@OHADSC, lorsque je n'ai pas de fonctionnement de référence, il n'y a pas d'option 2 :-) Mais en général, oui, je suis le principe XP / agile de sélection de la solution la plus simple qui pourrait éventuellement fonctionner.



0
votes

La première étape doit démontrer l'exactitude de la méthode d'ajout à l'aide d'un diagramme d'activité / un organigramme. L'étape suivante serait de prouver formellement l'exactitude de la méthode d'ajout si vous avez le temps. Ensuite, des tests avec un ensemble spécifique de données dans lequel vous vous attendez à des doubles emplois et à des non-duplications (c'est-à-dire que certains ensembles de données ont des duplications et certains ensembles ne le font pas et que vous constatez si la structure de données fonctionne correctement - il est important d'avoir des cas qui devraient réussir ( Pas de duplicats) et de vérifier qu'ils ont été ajoutés correctement à la définition plutôt que de simplement tester des cas d'échec (cas dans lesquels des doublons devraient être trouvés)). Et enfin vérifier génériquement. Même s'il est maintenant quelque peu obsolète, je suggérerais de construire des données pour exercer pleinement chaque chemin d'exécution dans la méthode testée. À tout moment, vous avez effectué un changement de code, commencez ensuite sur l'application des tests de régression.


0 commentaires

0
votes

Je choisirais l'approche algorithmique, mais de préférence sans s'appuyer sur une mise en œuvre alternative telle que hashset. Vous testez réellement pour plus que «pas de doublons» avec le match de hashset. Par exemple, le test échouera si des éléments ne l'entraient pas dans l'ensemble résultant, et vous avez probablement d'autres tests qui vérifient cela.

Une vérification de nettoyage des attentes "Aucun doublure" pourrait être quelque chose comme ce qui suit : xxx


1 commentaires

En fait, je n'ai aucun problème avec mes tests en vérifiant plus d'une chose à la fois, tant que ce qui m'intéresse est l'un d'entre eux. Pourquoi n'utiliseriez-vous pas une mise en œuvre alternative, cependant?