J'essaie de pratiquer le TDD, en l'utilisant pour développer un simple comme Bit Vector. Il se trouve que j'utilise Swift, mais c'est une question indépendante de la langue.
My BitVector
est un struct
qui stocke un seul UInt64
et présente une API qui vous permet de le traiter comme une collection. Les détails importent peu, mais c'est assez simple. Les 57 bits supérieurs sont des bits de stockage et les 6 bits inférieurs sont des bits de "comptage", qui vous indiquent combien de bits de stockage stockent réellement une valeur contenue.
Jusqu'à présent, j'ai une poignée de capacités très simples:
- Un initialiseur qui construit des vecteurs de bits vides
- Une
count
propriété de typeInt
- Une
isEmpty
propriété de typeBool
- Un opérateur d'égalité (
==
). NB: il s'agit d'un opérateur d'égalité de valeurs semblable àObject.equals()
Java, pas d'un opérateur d'égalité de référence comme==
en Java.
Je rencontre un tas de dépendances cycliques:
Le test unitaire qui teste mon initialiseur doit vérifier que le nouveau modèle a été construit
BitVector
. Il peut le faire de trois manières:- Vérifier
bv.count == 0
- Vérifier
bv.isEmpty == true
- Regarde ça
bv == knownEmptyBitVector
La méthode 1 s'appuie sur
count
, la méthode 2 s'appuie surisEmpty
(qui elle-même s'appuiecount
, il n'y a donc aucun intérêt à l'utiliser), la méthode 3 s'appuie sur==
. En tout cas, je ne peux pas tester mon initialiseur isolément.- Vérifier
Le test pour les
count
besoins de fonctionner sur quelque chose, qui teste inévitablement mes initialiseur (s)La mise en œuvre de
isEmpty
s'appuie surcount
La mise en œuvre de
==
s'appuie surcount
.
J'ai pu résoudre en partie ce problème en introduisant une API privée qui construit un à BitVector
partir d'un modèle binaire existant (en tant que UInt64
). Cela m'a permis d'initialiser les valeurs sans tester les autres initialiseurs, afin de pouvoir "booter" en montant.
Pour que mes tests unitaires soient vraiment des tests unitaires, je me retrouve à faire un tas de hacks, ce qui complique considérablement ma prod et mon code de test.
Comment contournez-vous exactement ce genre de problèmes?
la source
BitVector
est une taille d'unité parfaitement fine pour les tests unitaires et résout immédiatement vos problèmes dont les membres du public ontBitVector
besoin les uns des autres pour effectuer des tests significatifs.Réponses:
Vous vous inquiétez trop des détails de mise en œuvre.
Peu importe que dans votre implémentation actuelle ,
isEmpty
s'appuie surcount
(ou sur toute autre relation que vous pourriez avoir): tout ce dont vous devriez vous soucier est l'interface publique. Par exemple, vous pouvez avoir trois tests:count == 0
.isEmpty == true
Ce sont tous des tests valides, et deviennent particulièrement importants si vous décidez de refactoriser les internes de votre classe afin d'
isEmpty
avoir une implémentation différente qui ne dépende pascount
- tant que vos tests réussissent tous, vous savez que vous n'avez pas régressé n'importe quoi.Des choses similaires s'appliquent à vos autres points - n'oubliez pas de tester l'interface publique, pas votre implémentation interne. Vous pouvez trouver TDD utile ici, car vous écririez alors les tests dont vous avez besoin
isEmpty
avant d'avoir écrit une implémentation pour cela.la source
Vous révisez votre réflexion sur ce qu'est un «test unitaire».
Un objet qui gère des données mutables en mémoire est fondamentalement une machine à états. Ainsi, tout cas d'utilisation utile va, au minimum, invoquer une méthode pour mettre des informations dans l'objet et invoquer une méthode pour lire une copie des informations de l'objet. Dans les cas d'utilisation intéressants, vous allez également invoquer des méthodes supplémentaires qui modifient la structure des données.
En pratique, cela ressemble souvent à
ou
La terminologie du «test unitaire» - eh bien, elle n'est pas très bonne depuis longtemps.
Kent a écrit la première version de SUnit en 1994 , le port vers JUnit était en 1998, la première version du livre TDD était début 2002. La confusion avait beaucoup de temps à se propager.
L'idée clé de ces tests (plus précisément appelés "tests programmeurs" ou "tests développeurs") est que les tests sont isolés les uns des autres. Les tests ne partagent aucune structure de données mutable, ils peuvent donc être exécutés simultanément. Il n'y a aucun souci que les tests doivent être exécutés dans un ordre spécifique pour mesurer correctement la solution.
Le principal cas d'utilisation de ces tests est qu'ils sont exécutés par le programmeur entre les modifications de son propre code source. Si vous effectuez le protocole de refactorisation rouge vert, un ROUGE inattendu indique toujours une erreur dans votre dernière modification; vous annulez cette modification, vérifiez que les tests sont VERTS et réessayez. Il n'y a pas beaucoup d'avantages à essayer d'investir dans une conception où chaque bogue possible est détecté par un seul test.
Bien sûr, si une fusion introduit une faute, trouver que la faute n'est plus anodine. Vous pouvez suivre différentes étapes pour vous assurer que les défauts sont faciles à localiser. Voir
la source
En général (même si vous n'utilisez pas TDD), vous devez vous efforcer d'écrire des tests autant que possible tout en faisant semblant de ne pas savoir comment il est mis en œuvre.
Si vous faites du TDD, cela devrait déjà être le cas. Vos tests sont une spécification exécutable du programme.
L'aspect du graphe d'appel sous les tests n'a pas d'importance, tant que les tests eux-mêmes sont raisonnables et bien entretenus.
Je pense que votre problème est votre compréhension du TDD.
À mon avis, votre problème est que vous "mélangez" vos personnages TDD. Vos personnages "test", "code" et "refactor" fonctionnent de manière totalement indépendante les uns des autres, idéalement. En particulier, vos personnes de codage et de refactoring n'ont aucune obligation envers les tests autres que de les faire / maintenir vertes.
Bien sûr, en principe, il serait préférable que tous les tests soient orthogonaux et indépendants les uns des autres. Mais ce n'est pas une préoccupation de vos deux autres personnages TDD, et ce n'est certainement pas une exigence stricte stricte ou même nécessairement réaliste de vos tests. Fondamentalement: ne jetez pas vos sentiments de bon sens sur la qualité du code pour essayer de répondre à une exigence que personne ne vous demande.
la source