2009-08-25 5 views
5

Il y a quelques semaines, j'ai commencé mon premier projet avec TDD. Jusqu'à maintenant, je n'ai lu qu'un livre à ce sujet.Comment développer des méthodes complexes avec TDD

Ma principale préoccupation: Comment écrire des tests pour des méthodes/classes complexes. J'ai écrit un cours qui calcule une distribution binomiale. Ainsi, une méthode de cette classe prend n, k, et p comme entrée, et calcule le resp. probabilité. (En fait, c'est un peu plus, c'est pourquoi je devais écrire moi-même, mais restons collés à cette description de la classe, pour faciliter l'argument.)

Ce que j'ai fait pour tester cette méthode est: copier tables avec différents n j'ai trouvé dans le web dans mon code, en choisissant au hasard une entrée dans cette table, nourri le resp. valeurs pour n, k et p dans ma fonction, et a regardé si le résultat était proche de la valeur dans la table. Je répète cela un certain nombre de fois pour chaque table.

Tout cela fonctionne bien maintenant, mais après avoir écrit le test, j'ai dû mettre en réserve pendant quelques heures pour vraiment coder la fonctionnalité. En lisant le livre, j'ai eu l'impression que je ne devrais pas coder plus de quelques minutes, jusqu'à ce que le test redevienne vert. Qu'est-ce que j'ai fait de mal ici? Bien sûr, j'ai cassé cette tâche dans beaucoup de méthodes, mais elles sont toutes privées.

Une question connexe: Était-ce une mauvaise idée de choisir aléatoirement des nombres de la table? En cas d'erreur, je vais afficher la graine aléatoire utilisée par cette série, afin que je puisse reproduire le bug.

+0

"J'ai eu l'impression que je ne devrais pas coder plus de quelques minutes, jusqu'à ce que le test redevienne vert." Où - spécifiquement - avez-vous eu cette impression? S'il vous plaît fournir le devis ou la référence. C'est rarement vrai, et je suis curieux de savoir où vous avez lu/vu/entendu cela. –

+0

Il était dans un livre allemand, "Testegetriebene Entwicklung mit JUnit & FIT", par Frank Westphal, 1ère édition. Par exemple. à la page 13, les deux premières phrases. – matthias

+1

Et comme vous n'avez probablement pas accès au livre, j'essaie une traduction: "L'interactivité entre le développement piloté par les tests et la conception simple se traduit par une boucle de codage par minute. juste quelques minutes, sans fermer la boucle de retour au moyen de tests. " (Eh bien, j'aborde les limites de mon anglais ici, j'espère que cette traduction est correcte.) – matthias

Répondre

3

"J'ai eu l'impression que je ne devrais pas coder plus de quelques minutes, jusqu'à ce que le test redevienne vert.

Westphal est correct jusqu'à un certain point.

Certaines fonctionnalités démarrent simplement et peuvent être testées simplement et codées simplement.

Certaines fonctionnalités ne sont pas simples. Simple est difficile à réaliser. EWD dit que la simplicité n'est pas appréciée parce qu'elle est si difficile à réaliser.

Si votre corps de fonction est difficile à écrire, ce n'est pas simple. Cela signifie que vous devez travailler beaucoup plus dur pour le réduire à quelque chose de simple. Après avoir finalement atteint la simplicité, vous aussi, vous pouvez écrire un livre montrant à quel point il est simple.

Jusqu'à ce que vous atteigniez la simplicité, il faudra beaucoup de temps pour écrire des choses.

"Était-ce une mauvaise idée de choisir au hasard des nombres de la table?"

Oui. Si vous avez des données d'exemple, exécutez votre test contre tous les données d'exemple. Utilisez une boucle ou quelque chose, et testez tout ce que vous pouvez tester.

Ne sélectionnez pas une ligne - aléatoirement ou autrement, sélectionnez toutes les lignes.

+0

Merci pour votre commentaire. J'ai l'impression que tu as raison. Je dois simplement apprendre beaucoup. Et tant que je n'ai pas de mois/années d'expérience, je dois accepter que mes solutions ne sont pas optimales. – matthias

+0

Ce n'est pas une question de "optimal". Le problème est que «simple» est vraiment, vraiment difficile à réaliser. Certains précieux peuvent faire un bon travail pour atteindre la vraie simplicité. Les exemples de livres contraires sont les pires, puisque l'auteur a eu beaucoup de temps pour arriver à simple. Nous devons tous y travailler; ils peuvent dissimuler la grande quantité d'effort nécessaire pour arriver à un exemple simple. –

0

Il est difficile de répondre à votre question sans en savoir un peu plus sur les choses que vous vouliez implémenter. On dirait qu'ils n'étaient pas facilement partageables en parties testables. Soit la fonctionnalité fonctionne dans son ensemble, soit elle ne fonctionne pas. Si tel est le cas, il n'est pas étonnant que vous ayez des heures d'outils pour l'implémenter.

Quant à votre deuxième question: Oui, je pense que c'est une mauvaise idée de rendre le dispositif de test aléatoire. Pourquoi avez-vous fait cela en premier lieu? Changer le projecteur change le test.

1

Vous devriez TDD en utilisant les étapes de bébé. Essayez de penser à des tests qui nécessiteront moins de code à écrire. Ensuite, écrivez le code. Ensuite, écrivez un autre test, et ainsi de suite. Essayez de diviser votre problème en petits problèmes (vous avez probablement utilisé d'autres méthodes pour terminer votre code). Vous pourriez TDD ces petites méthodes.

--edit - sur la base des commentaires

méthodes privées d'essai ne sont pas nécessairement mauvaises choses. Ils contiennent parfois des détails d'implémentation, mais parfois ils peuvent aussi agir comme une interface (dans ce cas, vous pourriez suivre ma suggestion au paragraphe suivant). Une autre option consiste à créer d'autres classes (implémentées avec des interfaces injectées) pour prendre certaines des responsabilités (peut-être certaines de ces méthodes plus petites), et les tester séparément, et les mocker lors du test de votre classe principale. Enfin, je ne pense pas que passer plus de temps à coder est un gros problème. Certains problèmes sont vraiment plus complexes à mettre en œuvre qu'à tester et nécessitent beaucoup de temps de réflexion.

+0

Oui, je pourrais TDD ces plus petites méthodes, mais j'ai eu tester la méthode privée s. Et comme je l'ai compris, ils font partie des détails de mise en œuvre, et ne devraient pas être testés. – matthias

+0

vient d'éditer ma réponse –

+0

Si cela vous aide à faire TDD comme vous le souhaitez, vous pouvez commencer avec ces méthodes privées publiques/protégées, et tester ces petites fonctionnalités. Lorsque vous avez créé les méthodes publiques, qui appellent à celles-ci, vous pouvez supprimer les tests qui testent directement les méthodes privées. Vous devriez également vous demander si les avoir à la visibilité protégée/par défaut, afin de tester à partir du même paquet, va gâcher l'encapsulation. Sinon, je dirais que c'est un compromis équitable. – Grundlefleck

0

Évitez de développer des méthodes complexes avec TDD avant d'avoir développé des méthodes simples en tant que blocs de construction pour les méthodes plus complexes. TDD serait typiquement utilisé pour créer une quantité de fonctionnalité simple qui pourrait être combinée pour produire un comportement plus complexe. Les méthodes/classes complexes doivent toujours pouvoir être décomposées en parties plus simples, mais il n'est pas toujours évident de savoir comment et est souvent spécifique au problème. Le test que vous avez écrit ressemble plus à un test d'intégration pour s'assurer que tous les composants fonctionnent correctement, bien que la complexité du problème que vous décrivez ne limite que le besoin d'un ensemble de composants pour le résoudre. La situation que vous décrivez ressemble à ceci:

class A { publique doLotsOfStuff() // Appel doTask1..n privé doTask1() doTask2 privé() doTask3 privé() }

Vous allez trouver assez difficile à développer avec TDD si vous commencez par écrire un test pour la plus grande unité de fonctionnalité (par exemple doLotsOfStuff()). En décomposant le problème en plusieurs parties plus maniables et en l'approchant de la fin des fonctionnalités les plus simples, vous pourrez également créer des tests plus discrets (beaucoup plus utiles que des tests qui vérifient tout!). Peut-être votre solution potentielle pourrait être reformulé comme ceci:

class A { doLotsOfStuff public() // Appel doTask1..n publique doTask1() doTask2 (public) doTask3 publique() }

Tandis que vos méthodes privées peuvent être des détails d'implémentation, ce n'est pas une raison pour éviter de les tester isolément. Tout comme de nombreux problèmes, une approche de diviser pour régner se révélerait affective ici. La vraie question est de savoir quelle taille est un morceau de fonctionnalité convenablement testable et maintenable? Vous seul pouvez répondre à cette question en fonction de votre connaissance du problème et de votre propre jugement sur l'application de vos capacités à la tâche.

1

Vous avez raison en ce qui concerne les réfacteurs rapides, je vais rarement plus de quelques minutes entre la reconstruction et le test, quel que soit le degré de complexité du changement. Cela prend un peu de pratique.

Le test que vous avez décrit est plus un test système qu'un test unitaire. Un test unitaire ne tente jamais de tester plus d'une seule méthode - afin de réduire la complexité, vous devriez probablement décomposer votre problème en plusieurs méthodes.

Le test du système devrait probablement être effectué après que vous avez construit votre fonctionnalité avec de petits tests unitaires sur de petites méthodes simples.

Même si les méthodes prennent juste une partie de la formule avec une méthode plus longue, vous bénéficiez de la lisibilité (le nom de la méthode devrait être plus lisible que la partie de la formule) et si les méthodes sont définitives JIT devrait les aligner afin que vous ne perdiez aucune vitesse. D'un autre côté, si votre formule n'est pas très grande, peut-être écrivez-vous tout en une méthode et testez-la comme vous l'avez fait et prenez les temps d'arrêt - les règles sont faites pour être brisées.

6

Je ne suis pas d'accord avec les gens qui disent que c'est correct de tester le code privé, même si vous les transformez en classes séparées. Vous devriez tester les points d'entrée de votre application (ou de votre bibliothèque, s'il s'agit d'une bibliothèque que vous codez). Lorsque vous testez du code privé, vous limitez vos possibilités de recréation pour plus tard (car refactoriser vos classes privées signifie refactoriser votre code de test, ce que vous devez éviter de faire). Si vous finissez par réutiliser ce code privé ailleurs, alors créez des classes séparées et testez-les, mais jusqu'à ce que vous le fassiez, supposez que vous n'en aurez pas besoin.

Pour répondre à votre question, je pense que oui, dans certains cas, ce n'est pas une situation «2 minutes avant que vous deveniez vert». Dans ce cas, je pense que les tests peuvent prendre beaucoup de temps avant de devenir verts. Mais la plupart des situations sont "2 minutes avant de passer au vert". Dans votre cas (je ne sais pas squat sur la distribution binomiale), vous avez écrit que vous avez 3 arguments, n, k et p. Si vous conservez k et p constant, votre fonction est-elle plus simple à implémenter? Si oui, vous devriez commencer par créer des tests qui ont toujours la constante k et p. Lorsque vos tests passent, introduisez une nouvelle valeur pour k, puis pour p.

0

Je pense que le style de test que vous avez est totalement approprié pour le code c'est avant tout un calcul. Plutôt que de choisir une rangée aléatoire à partir de votre table de résultats connue, il serait préférable de simplement coder en dur les cas de bords significatifs. De cette façon, vos tests vérifient systématiquement la même chose, et quand on casse, vous savez ce que c'était.

Oui TDD prescrit de courtes durées de test à la mise en œuvre, mais ce que vous avez fait est encore bien au-delà des normes que vous trouverez dans l'industrie. Vous pouvez maintenant compter sur le code pour calculer ce qu'il doit faire et refactoriser/étendre le code avec un degré de certitude que vous ne le cassez pas. En apprenant plus de techniques de test, vous pouvez trouver une approche différente qui raccourcit le cycle rouge/vert. En attendant, ne vous sentez pas mal à ce sujet. C'est un moyen pour une fin, pas une fin en soi.

Questions connexes