Testgetriebene Entwicklungskriege: Detroit gegen London, Klassiker gegen Spötter

Ich erinnere mich, als ich anfing, Programmieren zu lernen, und einer meiner Lehrer erklärte mir die Welt der Softwareentwicklung in einem einzigen Satz:

"Es sind nur ein paar Leute in einem Raum, die miteinander streiten."

Er fuhr fort und beschrieb, wie Sie in einigen Kernfragen wie Testen und Design niemals eine Konsensvereinbarung finden werden. Ein leitender Entwickler könnte eine Ansicht predigen, und ein anderer wird eine gegenteilige Ansicht predigen. Einer wird darauf bestehen, dass Sie zuerst eine Feature-Spezifikation schreiben, ein anderer wird Ihre Existenz verweigern, bis Sie auf der Ebene der Einheit beginnen.

Im Nachhinein wäre es nützlich gewesen, den gegnerischen Teststämmen und ihren verschiedenen Meinungsverschiedenheiten mehr Aufmerksamkeit zu schenken. Durch das Web zu navigieren und zahlreiche Artikel zu finden, die sich gegenseitig widersprechen, kann ungeheuerlich, aber zugegebenermaßen verwirrend sein. Welche Praxis wende ich in meinem Alltag an? Was ist, wenn mir die klassizistische Sichtweise gefällt und intuitiv Sinn macht, aber diejenigen in meinem Team verärgert?

Aber was ist Klassiker und was ist Spottist?

Diese beiden Camps werden allgemein als Detroit School of TDD (oder Classicist, State Based Testing, Inside-Out, Black-Box-Testing) und London School of TDD (oder Mockist, Interaction Testing, Outside-In-Testing, White-Box-Testing) bezeichnet. Box testen). Beide sind sich einig, dass TDD ein effektives Designtool ist, aber wie sie TDD anwenden, unterscheidet sich genug, um sie in zwei Denkrichtungen einzuteilen. Für die Dauer dieses Artikels werden diese Begriffe synonym verwendet.

Sie haben vielleicht von berühmten klassizistischen TDD-Praktizierenden gehört, darunter Onkel Bob und Kent Beck. Wenn Sie ein Beispiel für einen Mockist suchen, sind Sandi Metz, J.B. Rainsberger oder Steve Freeman genau das Richtige

Die Detroit School of TDD beginnt mit einem Inside-Out-Ansatz zum Testen. Das Testen beginnt auf der Ebene der Einheit, und das Design soll aus den Tests hervorgehen. Ich gebe zu, dass ich ein wenig zusammenzucke, wenn ich das lese. Nur weil Sie Tests schreiben, bedeutet dies nicht, dass Ihr Design zu einem beneidenswerten Kunstwerk technischer Kunst wird. Um ein gutes Design zu schreiben, muss man etwas über Design lernen.

Sie können alles lernen, was Sie zum Testen von Strategien und Theorien benötigen, aber ohne eine gewisse Designgrundlage können Sie nicht damit rechnen, ein klar definiertes und erweiterbares System zu erstellen, das gegen zukünftige Geschäftsentwicklungen immun ist.

Ein wesentliches Merkmal der Detroit School ist die Vermeidung von Verspottungen. Erst beim Lesen wurde mir klar, dass mir jahrelang nur die Londoner Schule von TDD beigebracht wurde (ich lebe schließlich in London). Ich habe immer angenommen, dass dies der Weg ist. Angenommen, Sie haben eine Ledger-Klasse, die Geschäftslogik ausführt, und am Ende eine Berechnungsmethode, die an ein anderes Objekt delegiert:

def berechne_gesamtsaldo (x, y)
 # komplexe Geschäftslogik, die x und y mutiert
 some_calculator_object.sum (x, y)
Ende

Die Klassiker würden dies testen, indem sie eine Aussage über die Methode berechne_gesamtsaldo treffen und annehmen, dass sowohl x als auch y 2 und 5 sind. Der endgültige Test würde einen Wert von 7 erwarten. Die Detroit-Schule hat also keine Angst davor, die Kollaborateure einer Instanz indirekt zu testen die Schnittstelle einer anderen Klasse. Hier würden keine Mocks eingeführt.

Wenn ein Praktizierender der Londoner Schule diese individuelle Methode testen würde, wäre die Erwartung nicht 7, aber wir hätten so etwas

erwarten (any_instance_of (Calculator)). zu erhalten (: summe) .with (2,5)

Das Argument von Mockists ist, dass die Summenmethode der Calculator-Instanz an einer anderen Stelle getestet wird und wir nicht möchten, dass ein Komponententest für unsere Ledger-Klasse aufgrund einer Änderung an der Implementierung von Calculator unterbrochen wird. Es ist ausreichend zu testen, ob die Nachricht zwischen den Objekten gesendet wird. Obwohl wir durch diesen Komponententest nicht bestätigt haben, dass das Verhalten tatsächlich funktioniert, sind wir zuversichtlich, dass dies der Fall ist, da wir die Interaktion zwischen den Mitarbeitern getestet haben und es einen weiteren Komponententest geben wird, der tatsächlich Aussagen zur Calculator-Summeninstanz macht Methode.

Die Klassiker werden darauf bestehen, dass der Mockist-Ansatz nichts unternimmt, um den Arbeitszustand der Anwendung zu bestätigen, und auch die Tests an die Implementierung bindet. Wenn wir unsere obige Methode ändern, um beispielsweise einen CalculatorService zu verwenden, bleibt das Verhalten unverändert, und es wird weiterhin die Nummer 7 zurückgegeben. Unser Test würde jedoch jetzt einen Fehlalarm auslösen, obwohl alles einwandfrei funktioniert.

Die Mockisten kommen zurück und sagen, dass wir nicht möchten, dass ein Mitarbeiter (z. B. der Taschenrechner) ausfällt, weil unsere Komponententests isoliert sein sollten, und dass die Tests bei ihren Anrufern fehlschlagen. Ja, die Anwendung ist fehlerhaft, aber wir würden es vorziehen, wenn ein einzelner Test anstelle mehrerer Tests fehlschlägt. Auch dafür sind integrierte Tests gedacht. um die Integration zwischen mehreren Objekten zu testen

Ein anderer Weg, um die Unterschiede zwischen der klassizistischen und der mockistischen Sichtweise des Testens zu beschreiben, ist die Verwendung von zustandsbasierten Tests und interaktionsbasierten Tests. Wie Sie am Beispiel von Mockist sehen können, würde die Londoner Schule Wert darauf legen, die Interaktion zwischen Objekten zu testen und sicherzustellen, dass die richtigen Nachrichten gesendet werden. Die Schule in Detroit argumentiert, dass eine eher zustandsbasierte Testarchitektur eine rücksichtslose Umgestaltung ermöglichen würde. Wenn Sie sich übermäßig lustig machen, binden Sie Ihre Objekte an die Benutzeroberflächen ihrer Mitarbeiter und erstellen so eine unflexibelere Testsuite.

Mocks brechen das alte Mantra, dass man die Schnittstelle testen soll, nicht die Implementierung. Wenn Sie die Art der Anrufe an die Mitbearbeiter in irgendeiner Weise ändern, werden die Tests abgebrochen, unabhängig davon, ob sich die Funktionalität in einem funktionierenden Zustand befindet. Dies ist eine der häufigsten Kritikpunkte an der London School of Testing.

Sie werden häufig von Black-Box-Tests im Vergleich zu White-Box-Tests hören. Black-Box-Tests sind mit der Detroit-Schule verbunden, während letztere mit der London-Schule verbunden sind. Genau wie in unserem obigen Beispiel für ein Ledger würden die Klassiker das Collaborator-Objekt unserer Ledger-Klasse nicht verspotten, sondern den gesamten Methodenaufruf wie eine Blackbox behandeln. Ich rufe eine Methode auf und erwarte, dass dieses Ergebnis zurückkommt. Es ist mir egal, wie.

Die Mockisten bevorzugen White-Box-Tests mit den Worten: Ich rufe eine Methode auf und kümmere mich nicht um die allgemeine Richtigkeit der Methode, da dies einem anderen Objekt mitteilt, dass ich den Rückgabewert an anderer Stelle testen werde. Mir ist nur wichtig, dass es effektiv kommuniziert.

Ich wünschte, ich wäre schlau genug, diesen Blog-Beitrag zu beenden, indem ich eine solide Empfehlung gebe, welcher Testansatz besser ist. Wie viele Dinge in der Software gibt es jedoch Kompromisse.

Wenn wir nützliche, sehr wertvolle Tests klassifizieren würden, die einen echten Geschäftswert erbringen können, würde ich davon ausgehen, dass effektive Tests:

  1. Haben Sie eine hohe Chance, Regressionen zu fangen
  2. Haben Sie eine geringe Wahrscheinlichkeit, falsch positive Ergebnisse zu erzielen
  3. Geben Sie schnelles Feedback

Es ist oft sehr schwierig, jedes einzelne davon zu maximieren, ohne eines der anderen zu opfern. Während so ziemlich alle Komponententests schnelles Feedback liefern und sowohl mockistische als auch klassizistische Testaufbauten ein gutes Sicherheitsnetz zum Auffangen von Regressionen bieten, ist dies der zweite Punkt, der häufig umstritten ist. Eine von einem Londoner Schulpraktiker entworfene Testarchitektur mit einer umfangreichen Reihe von Verspottungen wird wahrscheinlich viele Fehlalarme hervorrufen und zukünftige Umgestaltungsbemühungen behindern. Während eine Classicist-Testsuite weniger isolierte Komponententests enthält, die ohne das Verschulden des jeweiligen Prüflings brechen würden. Redundante Abdeckung ist ein Anti-Pattern, bei dem ein Teil des Produktionscodes von mehreren Tests abhängt, und dies ist etwas, das üblicherweise mit der Detroit-Schule assoziiert wird.

Ich hatte Spaß daran, die verschiedenen Gedankengänge in diesen gegnerischen Lagern zu erforschen. Seit ich Programm gelernt habe, war ich hauptsächlich Mockist-inspirierten Testsuiten und Londoner Schulpraktikern ausgesetzt. Mir wurde gesagt, dass es teuflisch ist, den Rückgabewert eines Mitarbeiters indirekt durch Vermeidung von Spott zu testen. Das Wissen über die gegensätzlichen Meinungen und Kompromisse zwischen den einzelnen Ansätzen ist von unschätzbarem Wert und stellt sicher, dass ich nicht so schnell urteilen sollte, wenn ich eine Codebasis ohne Spott sehe. Und du auch nicht.