Wenn Sie ein System wie eine KI erstellen, die sehr schnell viele verschiedene Pfade einschlagen kann, oder einen Algorithmus mit mehreren verschiedenen Eingaben, kann die mögliche Ergebnismenge eine große Anzahl von Permutationen enthalten.
Welchen Ansatz sollte man wählen, um TDD zu verwenden, wenn man ein System erstellt, das viele, viele verschiedene Permutationen von Ergebnissen ausgibt?
Antworten:
Einen praktischeren Ansatz für die Antwort von pdr wählen . Bei TDD geht es eher um Software-Design als um Testen. Sie verwenden Unit-Tests, um Ihre Arbeit zu überprüfen.
Auf der Ebene der Einheitentests müssen Sie die Einheiten so entwerfen, dass sie vollständig deterministisch getestet werden können. Sie können dies tun, indem Sie alles nehmen, was die Einheit nicht deterministisch macht (wie z. B. einen Zufallszahlengenerator), und das abstrahieren. Nehmen wir an, wir haben ein naives Beispiel für eine Methode, die entscheidet, ob ein Zug gut ist oder nicht:
Diese Methode ist sehr schwer zu testen und das einzige, was Sie in Unit-Tests wirklich überprüfen können, sind die Grenzen ... aber das erfordert eine Menge Versuche, um an die Grenzen zu gelangen. Lassen Sie uns stattdessen den Randomisierungsteil abstrahieren, indem Sie eine Schnittstelle und eine konkrete Klasse erstellen, die die Funktionalität umschließt:
Die
Decider
Klasse muss nun die konkrete Klasse durch ihre Abstraktion, dh das Interface, verwenden. Diese Vorgehensweise wird als Abhängigkeitsinjektion bezeichnet (das folgende Beispiel ist ein Beispiel für die Konstruktorinjektion, aber Sie können dies auch mit einem Setter tun):Sie könnten sich fragen, warum dieser "Code aufblähen" notwendig ist. Nun, für den Anfang können Sie jetzt das Verhalten des zufälligen Teils des Algorithmus verspotten, da der
Decider
nun eine Abhängigkeit hat, die demIRandom
s "Vertrag" folgt . Sie können hierfür ein Mocking-Framework verwenden. Dieses Beispiel ist jedoch einfach genug, um sich selbst zu codieren:Das Beste daran ist, dass dies die "tatsächliche" konkrete Implementierung vollständig ersetzen kann. Der Code wird so einfach zu testen:
Hoffe, dies gibt Ihnen Anregungen, wie Sie Ihre Anwendung so gestalten können, dass die Permutationen erzwungen werden, sodass Sie alle Randfälle und so weiter testen können.
quelle
Strict TDD funktioniert bei komplexeren Systemen in der Regel etwas schlechter, aber das ist praktisch nicht so wichtig. Wenn Sie nicht mehr in der Lage sind, einzelne Eingänge zu isolieren, wählen Sie einfach einige Testfälle aus, die eine angemessene Abdeckung bieten, und verwenden Sie diese.
Dies erfordert einige Kenntnisse darüber, wie die Implementierung erfolgreich sein wird. Dies ist jedoch eher ein theoretisches Problem. Es ist sehr unwahrscheinlich, dass Sie eine KI erstellen, die von nicht-technischen Benutzern detailliert spezifiziert wurde. Es ist in der gleichen Kategorie wie das Bestehen von Tests durch Hardcodierung der Testfälle - offiziell ist der Test die Spezifikation und die Implementierung ist sowohl korrekt als auch die schnellstmögliche Lösung, aber es passiert nie wirklich.
quelle
Bei TDD geht es nicht um Testen, sondern um Design.
Weit davon entfernt, mit der Komplexität auseinanderzufallen, ist es unter diesen Umständen hervorragend. Es wird Sie veranlassen, das größere Problem in kleineren Teilen zu betrachten, was zu einem besseren Design führt.
Versuchen Sie nicht, jede Permutation Ihres Algorithmus zu testen. Erstellen Sie einfach Test für Test und schreiben Sie den einfachsten Code, damit der Test funktioniert, bis Ihre Grundlagen abgedeckt sind. Sie sollten verstehen, was ich mit dem Auflösen des Problems meine, da Sie aufgefordert werden, beim Testen anderer Teile Teile des Problems auszutricksen, damit Sie nicht 10 Milliarden Tests für 10 Milliarden Permutationen schreiben müssen.
Edit: Ich wollte ein Beispiel hinzufügen, hatte aber vorher keine Zeit.
Betrachten wir einen In-Place-Sortieralgorithmus. Wir könnten Tests schreiben, die das obere Ende des Arrays, das untere Ende des Arrays und alle möglichen komischen Kombinationen in der Mitte abdecken. Für jedes müssten wir ein komplettes Array von Objekten erstellen. Das würde Zeit brauchen.
Oder wir könnten das Problem in vier Teilen angehen:
Der erste ist der einzig komplizierte Teil des Problems, aber indem Sie es vom Rest abstrahieren, haben Sie es viel, viel einfacher gemacht.
Die zweite Aufgabe wird mit ziemlicher Sicherheit vom Objekt selbst übernommen, zumindest optional. In vielen Frameworks mit statischem Typ gibt es eine Schnittstelle, die anzeigt, ob diese Funktionalität implementiert ist. Sie müssen das also nicht testen.
Der dritte ist unglaublich einfach zu testen.
Der vierte behandelt nur zwei Zeiger, fordert die Traversal-Klasse auf, die Zeiger zu verschieben, fordert einen Vergleich an und fordert auf der Grundlage des Ergebnisses dieses Vergleichs die auszutauschenden Elemente an. Wenn Sie die ersten drei Probleme durchgespielt haben, können Sie dies ganz einfach testen.
Wie haben wir hier zu einem besseren Design geführt? Nehmen wir an, Sie haben es einfach gehalten und eine Blasensorte implementiert. Es funktioniert, aber wenn Sie in die Produktion gehen und eine Million Objekte bearbeiten müssen, ist es viel zu langsam. Alles, was Sie tun müssen, ist, neue Traversal-Funktionen zu schreiben und diese einzutauschen. Sie müssen sich nicht mit der Komplexität der Behandlung der anderen drei Probleme befassen.
Sie werden feststellen, dass dies der Unterschied zwischen Unit Testing und TDD ist. Der Unit-Tester sagt, dass dies Ihre Tests zerbrechlich gemacht hat. Wenn Sie einfache Ein- und Ausgaben getestet hätten, müssten Sie jetzt keine weiteren Tests für Ihre neue Funktionalität schreiben. Der TDDer wird sagen, dass ich Bedenken angemessen getrennt habe, so dass jede Klasse, die ich habe, eine Sache und eine Sache gut macht.
quelle
Es ist nicht möglich, jede Permutation einer Berechnung mit vielen Variablen zu testen. Aber das ist nichts Neues, es war schon immer wahr für jedes Programm, das über die Komplexität des Spielzeugs hinausgeht. Der Zweck von Tests besteht darin, die Eigenschaft der Berechnung zu überprüfen . Zum Beispiel ist das Sortieren einer Liste mit 1000 Nummern etwas mühsam, aber jede einzelne Lösung kann sehr einfach überprüft werden. Jetzt, obwohl es 1000 gibt! Mögliche (Klassen von) Eingaben für dieses Programm und Sie können nicht alle testen. Es ist völlig ausreichend, nur 1000 Eingaben zufällig zu generieren und zu überprüfen, ob die Ausgabe tatsächlich sortiert ist. Warum? Weil es fast unmöglich ist, ein Programm zu schreiben, das 1000 zufällig erzeugte Vektoren zuverlässig sortiert, ohne im Allgemeinen auch korrekt zu sein (es sei denn, Sie manipulieren es absichtlich, um bestimmte magische Eingaben zu manipulieren ...)
Im Allgemeinen sind die Dinge etwas komplizierter. Es gibt wirklich haben Fehler gewesen , wo ein Mailer keine E - Mails an Benutzer liefern würden , wenn sie ein ‚f‘ in ihren Benutzernamen und den Tag der Woche haben Freitag ist. Aber ich halte es für vergeudete Mühe, eine solche Verrücktheit vorherzusehen. Ihre Testsuite sollte Ihnen die Gewissheit geben, dass das System die von Ihnen erwarteten Eingaben ausführt. Wenn es in bestimmten Fällen funky ist, werden Sie es früh genug bemerken, nachdem Sie den ersten funky Fall ausprobiert haben. Dann können Sie einen Test speziell für diesen Fall schreiben (der normalerweise auch eine ganze Klasse ähnlicher Fälle abdeckt).
quelle
Nehmen Sie die Randfälle und einige zufällige Eingabe.
So nehmen Sie das Sortierbeispiel:
Wenn dies schnell funktioniert, können Sie sicher sein, dass es für alle Eingaben funktioniert.
quelle