Was tun Sie, wenn Sie einen Test schreiben und an den Punkt gelangen, an dem Sie den Test bestehen müssen, und wenn Sie feststellen, dass Sie eine zusätzliche Funktionalität benötigen, die in ihre eigene Funktion unterteilt werden sollte? Diese neue Funktion muss ebenfalls getestet werden, aber der TDD-Zyklus sagt, dass ein Test fehlschlagen soll. Lassen Sie ihn bestehen und überarbeiten Sie ihn. Wenn ich mich in der Phase befinde, in der ich versuche, meinen Test zu bestehen, darf ich keinen weiteren fehlgeschlagenen Test starten, um die neuen Funktionen zu testen, die ich implementieren muss.
Zum Beispiel schreibe ich eine Punktklasse , die eine Funktion WillCollideWith ( LineSegment ) hat :
public class Point {
// Point data and constructor ...
public bool CollidesWithLine(LineSegment lineSegment) {
Vector PointEndOfMovement = new Vector(Position.X + Velocity.X,
Position.Y + Velocity.Y);
LineSegment pointPath = new LineSegment(Position, PointEndOfMovement);
if (lineSegment.Intersects(pointPath)) return true;
return false;
}
}
Ich habe gerade einen Test für CollidesWithLine geschrieben, als mir klar wurde, dass ich eine LineSegment.Intersects ( LineSegment ) -Funktion benötigen würde . Aber sollte ich einfach aufhören, was ich in meinem Testzyklus mache, um diese neue Funktionalität zu erstellen? Das scheint das "Rot, Grün, Refaktor" -Prinzip zu brechen.
Sollte ich nur den Code schreiben, der diese lineSegments-Überschneidung in der CollidesWithLine- Funktion erkennt, und ihn nach der Ausführung umgestalten? Das würde in diesem Fall funktionieren , da ich über LineSegment auf die Daten zugreifen kann. Was ist jedoch mit den Fällen, in denen diese Art von Daten privat ist?
quelle
Im TDD-Zyklus:
In der Phase "Den Test bestehen" sollten Sie die einfachste Implementierung schreiben , die den Test bestehen wird . Um Ihren Test zu bestehen, haben Sie sich entschieden, einen neuen Mitarbeiter zu erstellen, der die fehlende Logik handhabt, da es möglicherweise zu viel Arbeit war, Ihre Punkteklasse zu füllen, um Ihren Test zu bestehen. Hier liegt das Problem. Ich nehme an, der Test, den Sie machen wollten, war ein zu großer Schritt . Ich denke, das Problem liegt in Ihrem Test selbst. Sie sollten diesen Test löschen / kommentieren und einen einfacheren Test finden, mit dem Sie einen kleinen Schritt machen können, ohne den Teil LineSegment.Intersects (LineSegment) einzuführen. Wenn Sie diesen Test bestanden haben, können Sie ihn umgestaltenIhren Code (hier wenden Sie das SRP-Prinzip an), indem Sie diese neue Logik in eine LineSegment.Intersects-Methode (LineSegment) verschieben. Ihre Tests werden weiterhin bestanden, da Sie kein Verhalten geändert haben, sondern nur Code verschoben haben.
Auf Ihrer aktuellen Designlösung
Für mich ist jedoch das Problem, dass Sie gegen das Prinzip der Einzelverantwortung verstoßen, noch schwerwiegender . Die Rolle eines Punktes ist ... ein Punkt zu sein, das ist alles. Es ist nicht klug, ein Punkt zu sein, es ist gerecht und x- und y-Wert. Punkte sind Werttypen . Dies gilt auch für Segmente. Segmente sind Wertetypen, die aus zwei Punkten bestehen. Sie können beispielsweise ein bisschen "Schlauheit" enthalten, um ihre Länge basierend auf ihrer Punkteposition zu berechnen. Aber das ist es.
Die Entscheidung, ob ein Punkt und ein Segment kollidieren, liegt in eigener Verantwortung. Und es ist mit Sicherheit zu viel Arbeit, als dass ein Punkt oder ein Segment alleine fertig werden könnte. Es kann nicht zur Point-Klasse gehören, da Points sonst Informationen zu Segmenten erhält. Und es kann nicht zu Segmenten gehören, da Segmente bereits die Verantwortung haben, sich um die Punkte innerhalb des Segments zu kümmern und möglicherweise auch die Länge des Segments selbst zu berechnen.
Diese Verantwortung sollte also einer anderen Klasse gehören, wie zum Beispiel einem "PointSegmentCollisionDetector", der eine Methode wie die folgende haben würde:
bool AreInCollision (Punkt p, Segment s)
Und das ist etwas, das Sie separat von Punkten und Segmenten testen würden.
Das Schöne an diesem Design ist, dass Sie Ihren Kollisionsdetektor jetzt anders implementieren können. So wäre es zum Beispiel einfach, Ihre Game-Engine (ich nehme an, Sie schreiben ein Spiel: p) einem Benchmark zu unterziehen, indem Sie Ihre Kollisionserkennungsmethode zur Laufzeit wechseln. Oder um zur Laufzeit visuelle Überprüfungen / Experimente zwischen verschiedenen Kollisionserkennungsstrategien durchzuführen.
Wenn Sie diese Logik in Ihre Punkteklasse aufnehmen, sperren Sie momentan die Dinge ein und verlagern zu viel Verantwortung auf die Punkteklasse.
Hoffe es macht Sinn,
quelle
Am einfachsten ist es, eine Schnittstelle für LineSegment zu extrahieren und die Methodenparameter so zu ändern, dass sie die Schnittstelle übernehmen. Dann könnten Sie das Eingabezeilensegment verspotten und die Intersect-Methode unabhängig codieren / testen.
quelle
Mit jUnit4 können Sie die
@Ignore
Annotation für die Tests verwenden, die Sie verschieben möchten.Fügen Sie die Anmerkung zu jeder Methode hinzu, die Sie verschieben möchten, und setzen Sie das Schreiben von Tests für die erforderliche Funktionalität fort. Kreise zurück, um die älteren Testfälle später zu überarbeiten.
quelle