La méthodologie TDD peut-elle être appliquée de haut en bas?

13

Je ne sais pas comment TDD, la méthodologie, gère le cas suivant. Supposons que je veuille implémenter l'algorithme de fusion, en Python. Je commence par écrire

assert mergesort([]) === []

et le test échoue avec

NameError: le nom 'mergesort' n'est pas défini

J'ajoute ensuite

def mergesort(a):
    return []

et mon test réussit. Ensuite j'ajoute

assert mergesort[5] == 5

et mon test échoue avec

AssertionError

avec qui je fais passer

def mergesort(a):
    if not a:
        return []
    else:
        return a

Ensuite, j'ajoute

assert mergesort([10, 30, 20]) == [10, 20, 30]

et je dois maintenant essayer de faire ce passage. Je "connais" l'algorithme de fusion, donc j'écris:

def mergesort(a):
    if not a:
        return []
    else:
        left, right = a[:len(a)//2], a[len(a)//2:]
        return merge(mergesort(left)), mergesort(right))

Et cela échoue avec

NameError: le nom «fusion» n'est pas défini

Voici maintenant la question. Comment puis-je exécuter et commencer à implémenter en mergeutilisant TDD? Il semble que je ne peux pas parce que j'ai ce "suspendre" non rempli, échec du test mergesort, qui ne passera pas tant que ce mergen'est pas terminé! Si ce test traîne, je ne pourrai jamais vraiment faire de TDD car je ne serai pas "vert" lors de la construction de mes itérations TDD merge.

Il semble que je sois coincé avec les trois scénarios laids suivants et que j'aimerais savoir (1) lequel de ceux-ci la communauté TDD préfère, ou (2) y a-t-il une autre approche qui me manque? J'ai regardé plusieurs procédures pas à pas d'oncle Bob TDD et je ne me souviens pas avoir vu un cas comme celui-ci auparavant!

Voici les 3 cas:

  1. Implémentez la fusion dans un répertoire différent avec une suite de tests différente.
  2. Ne vous inquiétez pas d'être écologique lorsque vous développez la fonction d'assistance, il vous suffit de suivre manuellement les tests que vous souhaitez vraiment passer.
  3. Mettez en commentaire (GASP!) Ou supprimez les lignes de mergesortcet appel merge; puis après avoir commencé mergeà travailler, remettez-les en place.

Tout cela me semble stupide (ou est-ce que je regarde mal?). Quelqu'un connaît-il l'approche préférée?

Ray Toal
la source
2
Une partie de l'objectif de TDD est de vous aider à créer une conception logicielle. Une partie de ce processus de conception consiste à découvrir ce qui est nécessaire pour produire le résultat souhaité. Dans le cas de mergesort, puisqu'il s'agit déjà d'un algorithme très bien défini, ce processus de découverte n'est pas nécessaire, et il s'agit alors de mapper ce que vous savez déjà être la conception à une série de tests unitaires. Vraisemblablement, votre test de niveau supérieur affirme que votre méthode sous test accepte une collection non triée et retourne une collection triée ...
Robert Harvey
1
... Les tests unitaires ultérieurs approfondiraient progressivement la mécanique réelle de a mergesort. Si vous cherchez la "bonne" façon de procéder, il n'y en a pas d'autre que d'être précis dans le mappage de l' mergesortalgorithme avec une série de tests unitaires; c'est-à-dire qu'ils devraient refléter ce que mergesortfait réellement.
Robert Harvey
4
Le design ne se développe pas uniquement à partir des tests unitaires; si vous vous attendez à ce qu'un mergesortdesign émerge naturellement du refactor rouge-vert, cela ne se produira que si vous guidez le processus en fonction de vos connaissances existantes mergesort.
Robert Harvey
1
En TDD mergedoit être inventé uniquement sur l'étape de "refactoring". Si vous voyez que cette mergeméthode peut être introduite pour réussir votre test, mergesortfaites d'abord passer vos tests sans mergeméthode. Refactorisez ensuite votre implémentation en introduisant la mergeméthode.
Fabio

Réponses:

13

Voici quelques façons alternatives d'examiner vos options. Mais d'abord, les règles de TDD, de l' oncle Bob avec emphase par moi:

  1. Vous n'êtes pas autorisé à écrire un code de production, sauf pour effectuer un test unitaire échoué.
  2. Vous n'êtes pas autorisé à écrire plus d'un test unitaire que ce qui est suffisant pour échouer; et les échecs de compilation sont des échecs.
  3. Vous n'êtes pas autorisé à écrire plus de code de production qu'il n'en faut pour réussir le test unitaire ayant échoué.

Ainsi, une façon de lire la règle numéro 3 est que vous ayez besoin de la mergefonction pour réussir le test, afin que vous puissiez l'implémenter - mais uniquement sous sa forme la plus élémentaire.

Ou, vous pouvez également commencer par écrire l'opération de fusion en ligne, puis la refactoriser dans une fonction après avoir fait fonctionner le test.

Une autre interprétation est que vous écrivez mergesort, vous savez que vous aurez besoin d'une mergeopération (c'est-à-dire que ce n'est pas YAGNI, ce que la règle "suffisante" tente de limiter). Par conséquent, vous devriez avoir commencé avec des tests pour la fusion, puis seulement procéder à des tests pour le tri global.

kdgregory
la source
Ce sont vraiment de bonnes observations. J'avais pensé à l'inline et à l'affacturage plus tôt, mais comme mergec'est étonnamment désordonné, au cas par cas (ainsi qu'utile en tant que autonome), l'idée de le faire en tant que fonction distincte avait plus de sens. Cependant, le style de le faire en ligne dans sa forme de base, puis de le prendre en compte au stade du chapeau bleu semble vraiment être juste et correspond vraiment à ce que je cherchais.
Ray Toal
@RayToal - Je penche en fait vers l'approche de test complet de l' mergeopération avant de faire le tri (ainsi que des tests séparés de l' partitionopération). Je pense que la conception émergente des avantages revendiqués vient du fait de travailler lentement vers un objectif connu. Dans le cas du mergesort, je ne pense pas que l'objectif soit le tri en général (car vous vous retrouverez alors avec le tri à bulles). Vous connaissez les opérations de base, vous travaillez donc vers ces opérations; le tri est surtout une réflexion après coup.
kdgregory
1
Il y a une quatrième option. Passez la mergefonction mergesortet simulez son comportement. Revenez ensuite en arrière et implémentez le mergetest en premier. Les délégués sont géniaux ™.
RubberDuck
@RubberDuck Le fait de se moquer d'une partie intégrante du domaine principal peut entraîner des problèmes, plus précisément lorsque vous exécutez le programme et que la fonction simulée et fusionnée diffère dans les moindres détails. Une approche comme celle-ci devrait être laissée pour les cas où vous travaillez avec des ressources externes comme d'où vient la liste à trier.
cllamach