Je suis difficile de comprendre pourquoi il n'y a qu'un seul test par fonction dans la plupart des indicatifs de TDD professionnels que j'ai vus. Lorsque j'ai approché TDD, j'ai initialement eu tendance à parcourir 4-5 tests par fonction si elles étaient liées mais je vois que cela ne semble pas être la norme. Je sais que c'est plus descriptif d'avoir un seul test par fonction, car vous pouvez plus facilement réduire ce que le problème est, mais je me trouve avoir du mal à proposer des noms de fonction pour différencier les différents tests car beaucoup sont si similaires. p>
Donc, ma question est la suivante: est-ce vraiment une mauvaise pratique de mettre plusieurs tests dans une seule fonction et si oui pourquoi? Y a-t-il un consensus là-bas? Merci p>
EDIT: WOW Tons de bonnes réponses. Je suis convaincu. Vous devez vraiment les séparer tous. J'ai parcouru des tests récents que j'avais écrites et les séparais tous et que c'était bien plus facile de lire et a aidé à mieux comprendre ce que je testais. En outre, en donnant aux tests leurs propres noms verbeux, cela m'a donné des idées comme "Oh, attends, je n'ai pas testé cette autre chose", alors tout autour, je pense que c'est la voie à suivre. p>
Grandes réponses. Va être difficile à choisir un gagnant p>
8 Réponses :
On dirait que vous demandez "pourquoi il n'y a qu'un assertion em> par test dans la plupart des codes de TDD professionnels que j'ai vus". C'est probablement d'augmenter l'isolement des tests, ainsi que la couverture des tests en présence d'échecs. C'est certainement la raison pour laquelle j'ai fait ma bibliothèque TDD (pour PHP) de cette façon. Dites que vous avez function testFoo()
{
$this->assertEquals(1, foo(10));
$this->assertEquals(2, foo(20));
$this->assertEquals(3, foo(30));
}
Hmm point intéressant. Pour changer cela, je suppose que mon code de test va probablement exploser en taille. Je suppose que c'est juste le coût de faire des affaires alors.
@John Baker: Oui, le côté obscur de tests unitaires d'affirmation à une seule assertion est BLOAT. Mais cela m'a clairement clair pour moi peu après que je suis entré dans l'entreprise TDD que la rédaction des tests d'unité dans la langue de mise en œuvre est une erreur, cette entreprise vraiment i> appelle à une DSL découplée, quelque chose dans lequel vous pouvez écrire Multi Les tests -Sétertioin et maintiennent la propriété «Je veux voir tous les échecs» de tests d'affirmation à un seul impression.
@Just quelqu'un: wow tu as juste soufflé mon esprit. Y a-t-il des cadres là-bas pour une chose comme ça?
Ouais, intéressant. J'ai deuxième que la question de savoir s'il existe un exemple spécifique que vous connaissez déjà.
@John Baker & @tchalvak: Malheureusement, je n'ai pas monté de prolonger ma bibliothèque TDD avec une telle interface depuis l'interface "native", n'est pas vraiment brisée car elle obtient le travail si avec une certaine duplication et < / I> Pas mal de programmeurs de PHP serait vraiment consterné face à une petite langue spécialisée. :] Cela dit, l'écriture a été sur le Todo depuis trois ans maintenant ... oui, le temps vole comme une flèche.
Ahem. Pour répondre à vos questions: je ne suis au courant d'un outil de test de l'unité qui fait exactement ce que j'ai décrit. Cependant, il existe des outils qui génèrent des tests i>. Une des histoires de beau codes mentionne cette approche. Vérifiez votre librairie locale ou Amazon.com.
@Just Quelqu'un: Une autre chose que vous pouvez faire dans ce scénario est de refacteur de vos tests pour supprimer la duplication ou créer plusieurs appareils, qui effectuent une configuration appropriée pour un sous-ensemble de tests.
@kyoryu: Oui, mais cela conduit rapidement à un code de test (presque) aussi complexe que la coupe, qui est un i> souhaitable. Le code de test complexe est le fléau de TDD; C'est vraiment une ligne fine de marcher: afin de tester une coupe complexe avec n'importe quel degré de confiance, les tests doivent être plus simples que la coupe ou vous creusez vous-même un trou noir récursif. Et si vous souhaitez conserver le code de test de manière provocable i> (sinon dans le sens mathématique) correct, vous devez sortir de la langue de mise en œuvre coupée. sinon, vous devez écrire des tests d'unité pour vos tests de l'unité, puis ... et ensuite ... vous avez traversé l'horizon d'événement.
@Just Quelqu'un: Oui, et c'est souvent un signe de classe trop grande, une des leçons que vous apprenez dans TDD est que la douleur dans les tests est généralement un signal d'un problème de conception. Et l'une des valeurs des tests d'unité est qu'ils vous montrent si le test et le code sont d'accord - le test et le code étant incorrect de la même manière sont improbables.
@kyoryu: la discipline à l'affirmation unique met assez de tension sur les tests: vous gardez le code de test "peu profond" et il s'aggrave plus rapidement que la zone testée de la coupe ou de votre ingénieur des tests de sorte que leur surface se développe au pire linéairement avec La coupe, mais leur complexité se développe au mieux plus rapidement que la complexité coupée. Couper la taille de la classe n'a pas grand chose à dire dans cela. Les tests d'affirmation unique ont tendance à "exploser" même avec des classes très petites Reall, tout ce qui est nécessaire, c'est que la classe génère des objets avec des états riches. Les tests à un cul semblent mieux s'adapter au code sans effet de l'effet secondaire, à un point.
@John Baker: Je ne sais pas si vous demandiez à propos de PHP en général, mais il y a quelque peu une chose ... cxxtest ( CXXTEST.COM/GUIDE.HTML ) Pour les tests C ++ utilise Python ou Perl pour générer la structure de test pour vous, bien que le code de test réel soit écrit en C ++ (pour lier contre les objets que vous testez , bien sûr).
@Just_Somebody: Code Free Effets secondaire d'accord. J'avais réellement affirmer que TDD et, en particulier les "tests unitaires", fonctionnent mieux sur le code sans effet latéral et, en fait, une partie de la valeur d'eux est qu'elles poussent autant de code que possible pour ne pas avoir d'effets secondaires. Je vois ça comme une bonne chose.
Lorsqu'une fonction de test effectue un seul test, il est beaucoup plus facile d'identifier quel cas a échoué. p>
Vous isolez également les tests, une défaillance d'un test n'affecte pas l'exécution des autres tests. P>
Il semble que une seule échec dans une fonction multi-testicule devrait entraîner une défaillance de tous, non? Généralement tester les tests de cadre de test, il suffit de passer l'échec, avec une méthode multitesses signifierait que vous devez déterminer manuellement lequel des tests multiples échouerait, car si vous exécutez une énorme liste de tests, la première défaillance exécutée serait entraîner une défaillance globale pour la fonction et d'autres tests ne feraient pas échouer. P>
La granularité dans les tests est bonne. Si vous allez écrire 5 tests, les avoir chacun dans leur propre fonction ne semble pas plus difficile que les avoir tous au même endroit, mis à part la dépendance mineure de la création de la nouvelle fonction de la chaudière à chaque fois. Avec l'IDE de droite, même cela peut être plus simple que la copie et le collage. P>
"Vous devriez comprendre manuellement lequel des multiples tests échoueraient" i> Ceci est normalement très facile, cependant: vous pouvez dire par le numéro de ligne dans la trace de la pile.
"Création d'une nouvelle fonction de chaudière Chaque fois que [...] peut être plus simple que la copie et le coller" i> la fausse dichotomie. Il s'agit d'avoir la chaudière ou de ne pas l'avoir; La copie et le collage n'est pas un bon choix de toute façon.
Re: Jason, Re: Stack Trace, dépend du volume de tests dans votre suite de tests, je suppose. Re: Boiserie: Je dis: Je dis principalement que la création d'une nouvelle fonction de test pourrait être encore moins utile si vous avez une IDE qui fait la répétabilité pour vous à certains égards. Quoi qu'il en soit, la communauté wiki-ed, n'hésitez pas à corriger / à ajouter à votre choix. haussement d'épaules i>
La granularité élevée des tests est recommandée, pas seulement pour faciliter l'identification des problèmes, mais que des tests de séquençage à l'intérieur d'une fonction peuvent cacher accidentellement les problèmes. Supposons par exemple que la méthode d'appel Oui, nommer les choses bien (y compris les tests) n'est pas un problème trivial, mais il ne doit pas être considéré comme une excuse pour éviter une granularité appropriée. Un indice de nommage utile: chaque test vérifie un comportement spécifique fort> - par exemple, quelque chose comme "Pâques en 2008 chutes le 23 mars" - pas em> pour générique "fonctionnalité ", comme" Compute la date de Pâques correctement ". P> foo code> avec argument
bar code> est censée renvoyer
23 code> - mais en raison d'un bogue dans la manière dont l'objet initialise son Etat, il renvoie
42 code> si elle s'appelle la toute première méthode sur l'objet nouvellement construit (après cela, il bascule correctement pour renvoyer
23 code>). Si votre test de
foo code> ne vient pas juste après la création de l'objet, vous allez manquer ce problème; Et si vous testez jusqu'à 5 à la fois, vous n'avez que 20% de chances de l'obtenir accidentellement. Avec un test par fonction (et un dispositif de configuration / déchirure qui réinitialise et reconstruisent tout ce qui est proprement chaque fois, bien sûr), vous allez clouer le bug immédiatement. Maintenant, il s'agit d'un problème artificiellement simple pour des raisons d'exposition, mais la question générale - que les tests ne devraient pas s'influencer mutuellement mutuellement, mais ne seront souvent que si elles ne sont chacune entretenue par la configuration et la démolition de démolition. p>
HMMM, semble être basé sur cet argument, vous auriez seulement le problème inverse. Si vous avez un bug où le premier retour est vrai, mais des retours supplémentaires sont de buggy, vous n'auriez jamais attrapé cela avec des tests granulaires et uniques, neh?
... À l'exception de la nécessité de vérifier explicitement que les appels de méthode successive s'influencent la façon dont ils sont censés (via l'état de l'objet), y compris ne pas vous influencer les autres lorsqu'ils ne sont pas censés - par exemple. Puisque vous devez probablement vérifier que deux appels code> FOO code> renvoient la même valeur (si l'état de l'objet est supposé être non altéré entre eux), puis l'un de vos tests sera deux foo code> Les rappels à l'arrière - tant que ce test est dans sa propre fonction, il résumera des problèmes avec l'un ou l'autre des appels (seulement s'il suit les autres tests non liés, des problèmes peuvent être masqués).
+1 Maintenir les tests Simple est un conseil d'or. La dernière chose que vous voulez est un bug dans les tests.
Lutter avec cela au travail tout le temps. Tests si complexes qu'ils ont besoin de documentation de conception eux-mêmes. Me conduit fou!
Je pense que le bon moyen n'est pas de penser à la durée du nombre de tests par fonction, mais doit penser à terme couverture de code : p>
- Couverture de la fonction - a chaque fonction (ou sous-programme) de
programme a été appelé? LI>- Couverture du relevé - Chaque noeud du programme a-t-il été exécuté? li>
- Couverture de la succursale - Est-ce que chaque avantage dans le programme était de
exécuté? li>- Couverture de décision - a chaque structure de contrôle (telle qu'un si déclaration) évaluée à la fois pour vrais et faux? li>
- Couverture de condition - a chaque sous-expression booléenne évaluée à la fois vrai et faux? Cela ne fait pas impliquer nécessairement la couverture de la décision. LI>
- Condition / Couverture de décision - Couverture de décision et de condition
devrait être satisfait. li> ul> blockQuote>
edit: strong> Je relis ce que j'ai écrit et j'ai trouvé ça genre de "effrayant" ... cela me rappelle une bonne pensée que je entendu Quelques semaines, une couverture de code: p> La couverture de code est comme le marché boursier Investissement! Vous devez investir assez temps d'avoir une bonne couverture mais pas trop pour ne pas perdre votre temps et coupez-vous votre projet! p> blockQuote>
Cela signifie-t-il que vous diriez que c'est bien de coller la plupart de vos tests dans une seule fonction?
Si vous souhaitez coller aux principes TDD, vous devez faire de petites étapes qui impliquent: une granularité plus fin et plus de tests ...
Je pense que c'est parfaitement amende à hiérarchiser i> quels tests vous écrivez en premier, cependant, en vous concentrant sur la fonctionnalité la plus importante et la plus importante avant de poursuivre la couverture. Surtout si vous travaillez sur un prototype où les entrailles peuvent être rapidement modifiées pendant un moment.
J'ai du mal à comprendre pourquoi il n'y a qu'un seul test par fonction dans la plupart des indicateurs de TDD professionnels que j'ai vu p>
Je suppose que vous voulez dire « assert » quand vous dites « test ». En général, un test ne doit tester qu'un seul «cas d'utilisation» d'une fonction. Par « cas d'utilisation » Je veux dire: un chemin que le code peut circuler à travers via des instructions de contrôle de flux (ne pas oublier les exceptions traitées, etc.). Essentiellement, vous testez toutes les « exigences » de cette fonction. Par exemple, supposons que vous avez une fonction telle que: p>
Public Sub ParseFileTest_TestFileIsParsedCorrectly() Dim target as new FileParser() Dim actual as SomeBusinessObject = target.ParseFile(TestHelper.GetTestData("ParseFileTest.csv"))) Assert.Equals(actual.Header,"EXPECTED HEADER FROM TEST DATA FILE") Assert.Equals(actual.Footer,"EXPECTED FOOTER FROM TEST DATA FILE") Assert.Equals(actual.Body,"TEST DATA BODY") End Sub
Oui, vous devez tester un comportement par fonction dans TDD. Voici pourquoi. P>
Et une dernière question - pourquoi pas em> avoir un test par fonction? Quel est l'avantage? Je ne pense pas qu'il y ait une taxe sur les déclarations de la fonction. P>
Considérez cet homme de paille (en C #) tandis que "une assertion par fonction de test" est bon conseil TDD, Il manque deux choses: une fois et une seule fois (alias sèche) et "une classe d'essai par scénario". Ce dernier est le moins connu: au lieu d'une classe de test / un appareil de test contenant toutes les méthodes de test, a des classes imbriquées pour des scénarios non triviaux. Comme ceci: p> Vous n'avez maintenant pas de duplication et que chaque méthode de test affirme exactement une chose à propos du code sous test. P> s'il n'était pas 't tellement verbeux, j'envisagerais d'écrire tous mes tests de cette façon, de telle sorte que les méthodes de test sont toujours des méthodes mono-ligne triviales avec une seule assertion. Cependant, il semble trop maladroit lorsqu'un test ne partage pas le code "Configuration" avec un autre test. P> (J'ai évité les détails spécifiques à la technologie de test unitaire, par exemple Nunit ou Mstest. Vous devrez Ajustez pour s'adapter à ce que vous utilisez, mais les principes sont sonores.) p> p>
Que faire dans ces types de cas, lorsque chaque test a des configurations subtilement différentes? c = nouveau c (a); c.foo (); Affirmer (c.x == 7); c = nouveau c (b); c.foo (); Affirmer (c.x == -7);
@dvide: Si c'est aussi simple, alors gardez-les séparément. S'ils ont tous les deux c.foo (); c.bar (); c.baz (); "alors je voudrais considérer cela une odeur de code sur code> c
. Peut-être une nouvelle méthode code> facture ()` qui fait
foo / bar / baz code code baz > N'oubliez pas que les tests d'unités sont un client légitime de vos classes. Faire fonctionner vos cours bien dans les tests est susceptible de les faire fonctionner de bien travailler dans la production.