11
votes

Comment utiliser TDD correctement pour implémenter une méthode numérique?

J'essaie d'utiliser le développement dirigé par des tests pour implémenter ma bibliothèque de traitement du signal. Mais j'ai un peu de doute: supposons que j'essaie de mettre en œuvre une méthode sinusoïdale (je ne suis pas):

  1. Écrivez le test (pseudo-code) XXX

  2. écrire la première implémentation XXX

  3. second test XXX

    à ce stade, dois-je:

    1. implémente un code intelligent qui fonctionnera pour PI et d'autres valeurs, ou
    2. implémente le code le plus stupide qui fonctionnera uniquement pour 0 et pi?

      Si vous choisissez la deuxième option, quand puis-je passer à la première option? Je vais devoir le faire éventuellement ...


5 commentaires

Lorsque vous vérifiez des flotteurs pour l'égalité, utilisez quelque chose comme Assertdoubleequal si votre bibliothèque lui fournit ou écrivez votre propre Aster (ABS (Float1-flott2)


J'utilise Matlab Xunit qui fournit une méthode d'affectation de l'opportunité


@Jader Dias: Veuillez afficher le code qui correspond à ce que vous faites réellement. Si vous utilisez des assertelementations, veuillez indiquer que dans votre code.


La principale préoccupation de cette question ne concerne pas la méthode d'assertion, mais sur les directives TDD.


Utiliser «Assertdoubleequal», «Assertelementalmmentalmmenteequal», etc. n'est qu'une première approximation et structurée ci-dessus susceptible de donner des résultats trompeurs. Vous avez besoin de l'analyse numérique pour déterminer quelle différence d'erreur est suffisamment proche et vous devez presque certainement envisager une erreur relative, pas une erreur absolue.


9 Réponses :


0
votes

Strictement à la suite de TDD, vous pouvez d'abord mettre en œuvre le code le plus stupide qui fonctionnera. Afin de passer à la première option (pour implémenter le code réel), ajoutez plus de tests: xxx

Si vous implémentez plus que ce qui est absolument requis par vos tests, vos tests ne seront pas complètement couvrir votre implémentation. Par exemple, si vous avez implémenté l'ensemble de la fonction sin () avec uniquement les deux tests ci-dessus, vous pouvez "casser la" casser accidentellement en renvoyant une fonction de triangle (qui ressemble presque à une fonction sinueuse) et à vos tests. ne serait pas capable de détecter l'erreur.

L'autre chose que vous devrez vous inquiéter pour les fonctions numériques est la notion d'égalité et de faire face à la perte de précision inhérente aux calculs de points flottants. C'est ce que je pensé votre question allait être à peu près après avoir lu le titre. :)


4 commentaires

Mais c'est peu pratique, à un moment donné, je devrai abandonner TDD pour éviter une boucle infinie.


Pouvez-vous élaborer sur ce que vous entendez par "boucle infinie"?


Il existe des fonctions distinctes infinies qui fournissent les mêmes résultats que le sinus pour N arguments testés, lorsque N est un nombre naturel. Donc, si vous avez moins que des tests infinis, il est toujours possible que votre méthode sinusoïde soit incorrecte, car elle peut différer de la définition sine que vous n'avez pas testé.


Oui c'est vrai. En général, les tests logiciels ne peuvent pas couvrir toutes les entrées possibles. Mais vous pouvez construire un test qui garantit la fonction requise est implémentée dans un niveau de confiance raisonnable . L'examen des tests peut être associé à l'inspection du code pour assurer la mise en œuvre de la fonction appropriée.



1
votes

Je crois que l'étape lorsque vous sautez à la première option, c'est quand vous voyez qu'il y a trop de "ifs" dans votre code "juste pour réussir les tests". Ce ne serait pas encore le cas, juste avec 0 et Pi.

Vous sentirez que le code commence à sentir et sera prêt à le refracter dès que possible. Je ne sais pas si c'est ce que Pure TDD dit, mais je le fais dans la phase de refacteurs (échec du test, passe-test, cycle de refacteurs). Je veux dire, à moins que vos tests d'échec ne demandent une implémentation différente.


2 commentaires

Les fonctions numériques comptent davantage dans d'autres fonctions mathématiques que si, alors, des déclarations d'autre. La question est que lorsque les tests suffisent?


Quand vous avez testé tout ce que vous pouvez. Une fois que vous n'avez pas tout le temps dans le monde, vous établissez des priorités, créez des "catégories" (comme des limites de test, des résultats exacts, etc.) pour tester et tester quelques exemples (ou peut-être qu'un seul) pour chaque catégorie



1
votes

Notez que (dans Nunit), vous pouvez également faire

Assert.That(2.1 + 1.2, Is.EqualTo(3.3).Within(0.0005);


0 commentaires

9
votes

À ce stade, devrais-je:

  1. Mise en œuvre du code réel qui fonctionnera en dehors des deux tests simples?

  2. Implémentez plus de code plus stupide qui ne fonctionnera que pour les deux tests simples?

    ni. Je ne sais pas où vous avez eu l'approche "Ecrire un seul test à la fois", mais c'est un moyen lent d'aller.

    Le point est d'écrire des tests clairs et d'utiliser ce test clair pour concevoir votre programme.

    Ainsi, écrivez suffisamment de tests pour valider une fonction sinusoïdale. Deux essais sont clairement inadéquats.

    Dans le cas d'une fonction continue, vous devez fournir une table de bonnes valeurs connues éventuellement. Pourquoi attendre?

    Cependant, tester des fonctions continues a des problèmes. Vous ne pouvez pas suivre une procédure Dumb TDD.

    Vous ne pouvez pas tester tous les valeurs de point flottant entre 0 et 2 * PI. Vous ne pouvez pas tester quelques valeurs aléatoires.

    Dans le cas des fonctions continues, un TDD "strict et impensable" ne fonctionne pas. Le problème ici est que vous connaissez votre mise en œuvre de la fonction sinusale sera basée sur un tas de symétries. Vous devez tester sur la base de ces règles de symétrie que vous utilisez. Les bugs se cachent dans des fissures et des coins. Les cas de bord et les caisses d'angle font partie de la mise en œuvre et si vous suivez impensément suivez TDD, vous ne pouvez pas tester cela.

    Toutefois, pour des fonctions continues, vous devez tester les cas de bord et de coin de la mise en œuvre.

    Cela ne signifie pas que TDD est cassé ou inadéquat. Il dit que la dévotion slave à un "test d'abord" ne peut pas travailler sans penser à ce que votre objectif réel est.


10 commentaires

Bien que votre réponse soit suffisamment claire pour apprendre à implémenter une fonction bien connue, il devient plus difficile de mettre en œuvre de nouvelles fonctions spécifiques à une application, pour laquelle il existe peu de bonnes valeurs. Je ne critique pas, je suis tout à fait d'accord avec vous.


@Jader Dias: Si vous n'avez que quelques bonnes valeurs connues, rien à propos de cette approche change. N'écrivez pas simplement un petit test à la fois. Écrivez autant de tests que vous avez besoin, puis implémentez.


Je ne sais pas "Ecrire un seul test à la fois", mais "faire un test de test à la fois" est l'un des locaux de TDD, est celui qui assure une couverture de code complète.


Je ne vois pas comment faire passer un test de test à la fois assure une couverture de code complète du tout. Concentrez-vous sur un test est une bonne chose. Écrire de très petits tests n'est pas aussi bonne chose.


Voir un test Échec de l'échec avant d'écrire du code pour que ce passage est un bon moyen de vous assurer que le code que vous venez d'écraser est couvert par des tests unitaires.


BTW Je ne défends pas la méthode de code de rédaction binaire. Je commencent parfois des morceaux de la fonction finie juste pour m'assurer que au moins un de mes tests échoue - cela vous aide à découvrir des cas de test que j'ai manqué.


@Marius Gedminas: Écrivez un test (qui échoue) suivi d'un code d'écriture est bon. Ecrivez un test minuscule, peu et pas très sensible d'une fonction continue n'est pas aussi bon.


@ S.Lott - Je pense que vous avez une idée fausse sur TDD. Ce n'est pas test-premier willy-nilly. Il pense que votre conception est d'abord alors mettre en œuvre un test pour cette pensée. Vous avez eu la conclusion correctement. Pensez à votre conception puis testez-le pour cela. TDD vous souhaite de tirer parti de votre expérience et de votre connaissance du domaine. Oui, il est assez ridicule d'écrire des tests pour des situations que vous savez où elle menait. La seule fois où j'écrirais un test - d'abord, c'est quand je n'ai pas eu d'indice où commencer. Cela pourrait alors être classé comme créer un Spike .


@Gtuzofter: "Ce n'est pas un test - premier willy-nilly" correct. Test d'abord sans le Willy-Nilly peut être très utile. Différente d'une pointe, on peut concevoir une API, écrire un test pour l'API, puis écrivez le code qui passe ce test.


Commentant: "Vous ne pouvez pas tester toutes les valeurs de points flottants entre 0 et 2 * pi". Une fois, j'ai lu un livre où il a été écrit: il n'y a que 2 ^ 32 floatts - alors testez-les tous. Plus bas, il a été écrit que: nous pouvons scinder le travail pour tester la fonction sinueuse: "Je fais le test pour tous les floats inférieurs à 2.1491e-08, et vous faites le reste".



5
votes

En nature de la Strict Baby-Step TDD, vous pouvez mettre en œuvre la méthode muette pour revenir au vert, puis refroidir la duplication inhérente dans le code muet (test de la valeur d'entrée est une sorte de duplication entre le test et le code) en produisant un algorithme réel. La partie difficile sur l'idée de se sentir pour TDD avec un tel algorithme est que vos tests d'acceptation sont vraiment assis à côté de vous (la table S. Lott suggère), de sorte que vous gardez un œil sur eux tout le temps. Dans une TDD plus typique, l'unité est suffisamment divorcée de l'ensemble que les tests d'acceptation ne peuvent pas être branchés là-bas, de sorte que vous ne commencez pas à penser à tester tous les scénarios, car tous les scénarios ne sont pas évidents.

Typiquement, vous pourriez avoir un véritable algorithme après un ou deux cas. L'important à propos de TDD est qu'il s'agit de conception de conduite, pas de l'algorithme. Une fois que vous avez suffisamment de cas pour satisfaire les besoins de conception, la valeur dans TDD tombe de manière significative. Ensuite, les tests sont plus convertis en cas d'angle couvrant pour vous assurer que votre algorithme est correct dans tous les aspects que vous pouvez penser. Donc, si vous êtes convaincu comment construire l'algorithme, allez-y. Les types de marches dont vous parlez ne sont que appropriés lorsque vous êtes incertain. En prenant de telles étapes pour bébé, vous commencez à construire les limites de ce que votre code doit couvrir, même si votre mise en œuvre n'est pas encore réelle. Mais comme je l'ai dit, c'est plus que lorsque vous n'êtes pas sûr de la construction de l'algorithme.


2 commentaires

Je suis d'accord avec votre commentaire. Je dirais que les fonctions mathématiques (qui ont des algorithmes bien connus) ne sont pas un bon ajustement pour TDD. TDD inclut une certaine quantité de "programmation exploratoire", qui est très courante dans la plupart des programmes, mais pas tous.


Je suis totalement d'accord avec votre assertion que la valeur principale de TDD est de conduire la conception de l'interface. C'est comme construire une serrure et une clé simutanée de manière à ce qu'ils s'adapteront lorsque les deux sont terminés.



1
votes

Vous devez coder tous vos tests de l'unité dans un coup (à mon avis). Tandis que l'idée de créer uniquement des tests couvrant spécifiquement ce qui doit être testé est correct, votre spécification particulière appelle à une fonction de fonctionnement sine () , pas a Sine ( ) fonction qui fonctionne pour 0 et pi.

trouver une source vous avez assez confiance (un ami mathématicien, des tables à l'arrière d'un livre de mathématiques ou un autre programme qui a déjà la fonction sinueuse mise en œuvre).

J'ai opté pour bash / bc parce que je suis trop paresseux pour taper tout en la main :-). Si elle était a Sine () fonction, je voudrais simplement exécuter le programme suivant et le coller dans le code de test. J'étais également une copie de ce script dans ce script en tant que commentaire, donc je peux la réutiliser si quelque chose change (tel que la résolution souhaitée si plus de 20 degrés dans ce cas ou la valeur de PI que vous souhaitez UTILISATION) destinée à faire. Mon point est que le test devrait valider pleinement le comportement du code dans cette itération. Si cette itération était de produire une fonction sine () fonctionne uniquement pour 0 et PI, alors c'est bien. Mais ce serait un grave gaspillage d'une itération à mon avis.

Il se peut que votre fonction soit si complexe que celle-ci doit être effectuée sur plusieurs itérations. Ensuite, votre approche est correcte et les tests doivent être mis à jour dans l'itération Suivant où vous ajoutez la fonctionnalité supplémentaire. Sinon, trouvez un moyen d'ajouter tous les tests de cette itération rapidement, alors vous n'aurez pas à vous soucier de la commutation entre le code réel et le code de test fréquemment.


2 commentaires

Notez que ce sont des valeurs plus ou moins aléatoires et ne testez pas du tout les cas de bord. Étant donné que la plupart des fonctions sinusoïdales utilisent un certain nombre de règles de symétrie pour les valeurs de 0 à PI / 4 Radians, le test doit être basé sur les règles de symétrie, non pas "tous les 20 degrés".


Les 20 degrés étaient juste pour que je ne remplissais pas la réponse avec des ordures :-) L'original avait 1 degré et vous pourriez facilement aller pour des dixièmes de degré ou même une résolution plus élevée si vous le souhaitez. Mais il n'est pas pertinent depuis que le questionneur a déclaré que n'était pas une fonction sinusoïdale. Le point que j'essayais de faire est dans le dernier paragraphe - écrire tous vos tests pour l'itération actuelle avant de développer; C'est mieux que de le faire au coupème.



0
votes

Je ne sais pas quelle langue vous utilisez, mais lorsque je traite une méthode numérique, j'écris généralement un test simple comme le vôtre pour vous assurer que le contour est correct, puis je nourris davantage de valeurs pour couvrir les cas où je soupçonne que les choses pourraient aller mal. En .NET, Nunit 2.5 a une belle fonctionnalité pour cela, appelée [TESTCASE] code>, où vous pouvez alimenter plusieurs valeurs d'entrée au même test comme ceci:

[TestCase(1,2,Result=3)]   
[TestCase(1,1,Result=2)]     
public int CheckAddition(int a, int b)   
{  
 return a+b;   
}


0 commentaires

5
votes

Ecrire des tests qui vérifient les identités.

pour l'exemple sin (x), réfléchissez à une formule à double angle et à une formule de demi-angle.

Ouvrez un manuel de traitement de signal. Trouvez les chapitres pertinents et implémentez chaque de ces théorèmes / corollaires en tant que code de test applicable à votre fonction. Pour la plupart des fonctions de traitement de signal, il faut des identités qui doivent être respectées pour les entrées et les sorties. Écrivez des tests qui vérifient ces identités, quelles que soient les entrées.

Pensez ensuite aux entrées.

  • Divisez le processus de mise en œuvre en étapes séparées. Chaque étape devrait avoir un objectif. Les tests de chaque étape seraient de vérifier cet objectif. (Note 1)
    1. Le but de la première étape est d'être "grossièrement correct". Pour l'exemple SIN (X), cela ressemblerait à une implémentation naïve à l'aide de la recherche binaire et de certaines identités mathématiques.
    2. L'objectif de la deuxième étape est d'être "assez précis". Vous allez essayer différentes manières de calculer la même fonction et de voir lequel obtient un meilleur résultat.
    3. L'objectif de la troisième étape est d'être "efficace".

      (note 1) Faites-le fonctionner, rendez-le correctement, faites-la rapidement, faites-la pas cher. - attribué à alan kay


2 commentaires

(Je ne suis pas pour ni contre TDD. Cependant, pardon mon arrogance, je considérerais les tests d'être incomplet (et la qualité de la bibliothèque discutable) à moins que vous ne vérifiez la bibliothèque contre toutes les identités connues applicables à ces fonctions.)


Je suppose que s lott. a donné cette réponse beaucoup plus tôt que moi. Ses «règles de symétrie» signifient probablement la même chose que les identités mathématiques. Donc, s'il vous plaît donner les crédits à S Lott à la place.



0
votes

réponse courte.

  • Écrivez un test à la fois.
  • Une fois qu'il échoue, revenez au vert en premier. Si cela signifie faire la chose la plus simple qui puisse fonctionner, faites-le. (Option 2)
  • Une fois que vous êtes dans le vert, vous pouvez regarder le code et choisir au nettoyage (option1). Ou vous pouvez dire que le code ne sent toujours pas cela beaucoup et d'écrire des tests ultérieurs qui mettent la lumière sur les odeurs.

    Une autre question que vous semblez avoir, c'est combien de tests devriez-vous écrire. Vous devez tester jusqu'à la peur (la fonction peut ne pas fonctionner) se transforme en ennui. Donc, une fois que vous avez testé toutes les combinaisons intéressantes de sortie d'entrée, vous avez terminé.


0 commentaires