Bin ich aus TDD-Sicht eine schlechte Person, wenn ich einen Test mit einem Live-Endpunkt anstelle eines Schein-Endpunkts durchführe?

16

Ich folge TDD religiös. Meine Projekte haben in der Regel eine Testabdeckung von mindestens 85% mit aussagekräftigen Testfällen.

Ich arbeite viel mit HBase , und die Haupt-Client-Oberfläche, HTable, ist ein echtes Problem. Das Schreiben meiner Komponententests dauert drei- oder viermal länger als das Schreiben von Tests, die einen Live-Endpunkt verwenden.

Ich weiß, dass Tests, die Mocks verwenden, aus philosophischer Sicht Vorrang vor Tests haben sollten, die einen Live-Endpunkt verwenden. Aber das Verspotten von HTable ist ein schwerwiegender Schmerz, und ich bin mir nicht sicher, ob es einen großen Vorteil gegenüber dem Testen mit einer Live-HBase-Instanz bietet.

Jeder in meinem Team führt eine Einzelknoten-HBase-Instanz auf seiner Workstation aus, und auf unseren Jenkins-Boxen werden Einzelknoten-HBase-Instanzen ausgeführt, sodass es nicht auf die Verfügbarkeit ankommt. Die Live-Endpunkttests dauern offensichtlich länger als die Tests, die Mocks verwenden, aber das interessiert uns nicht wirklich.

Im Moment schreibe ich Live-Endpunkttests UND mockbasierte Tests für alle meine Klassen. Ich würde gerne die Mocks fallen lassen, aber ich möchte nicht, dass die Qualität infolgedessen abnimmt.

Was denkst du alle?

Gelassenheit
quelle
8
Live-Endpunkt ist nicht wirklich ein Unit-Test, oder? Es ist ein Integrationstest. Aber letztendlich ist es wahrscheinlich eine Frage des Pragmatismus; Sie können entweder die Zeit damit verbringen, Mocks zu schreiben, oder die Zeit damit verbringen, Features zu schreiben oder Fehler zu beheben.
Robert Harvey
4
Ich habe Geschichten von Leuten gehört, die Dienste von Drittanbietern heruntergefahren haben, indem sie einen Komponententest mit ihrem eigenen Code durchgeführt haben ... der mit einem Live-Endpunkt verbunden war. Ratenbegrenzung ist nichts, was Unit-Tests normalerweise tun oder interessieren.
14
Du bist kein schlechter Mensch. Du bist ein guter Mensch, der etwas Schlechtes tut.
Kyralessa
14
Ich folge TDD religiös Vielleicht ist das das Problem? Ich glaube nicht , dass diese Methodik genommen werden soll , dass ernst. ;)
FrustratedWithFormsDesigner
9
Wenn Sie TDD religiös befolgen, bedeutet dies, dass Sie den 15% igen unbedeckten Code verwerfen.
Mouviciel

Antworten:

23
  • Meine erste Empfehlung wäre, Typen, die Sie nicht besitzen , nicht zu verspotten . Sie haben erwähnt, dass HTable ein echtes Problem ist - vielleicht sollten Sie es stattdessen in einen Adapter einwickeln, der die 20% der von Ihnen benötigten Funktionen von HTable verfügbar macht, und den Wrapper bei Bedarf verspotten.

  • Angenommen, wir sprechen von Typen, die Sie alle besitzen. Wenn sich Ihre mockbasierten Tests auf glückliche Pfadszenarien konzentrieren, in denen alles reibungslos verläuft, verlieren Sie nichts, weil Ihre Integrationstests wahrscheinlich bereits genau dieselben Pfade testen.

    Isolierte Tests werden jedoch interessant, wenn Sie darüber nachdenken, wie Ihr zu testendes System auf alle im Vertrag des Mitarbeiters definierten Vorgänge reagieren soll, unabhängig davon, mit welchem ​​konkreten Objekt es tatsächlich zu tun hat. Das ist ein Teil dessen, was manche als grundlegende Korrektheit bezeichnen . Es könnte viele dieser kleinen Fälle und viele weitere Kombinationen geben. Hier werden Integrationstests langsam mies, während isolierte Tests schnell und einfach zu handhaben sind.

    Was passiert konkret, wenn eine der Methoden Ihres HTable-Adapters eine leere Liste zurückgibt? Was ist, wenn es null zurückgibt? Was ist, wenn eine Verbindungsausnahme ausgelöst wird? Es sollte im Vertrag des Adapters festgelegt werden, ob eine dieser Situationen eintreten könnte, und ein Verbraucher sollte darauf vorbereitet sein, mit diesen Situationen umzugehen , weshalb Tests für diese Situationen erforderlich sind.

Fazit: Sie werden keinen Qualitätsverlust feststellen, wenn Sie Ihre mockbasierten Tests entfernen, wenn sie genau die gleichen Tests wie Ihre Integrationstests durchführen . Der Versuch, sich zusätzliche Einzeltests (und Vertragstests ) vorzustellen, kann Ihnen dabei helfen, Ihre Schnittstellen / Verträge gründlich zu überdenken und die Qualität zu steigern, indem Sie Fehler beheben, die schwer zu überlegen und / oder mit Integrationstests nur langsam zu testen wären.

guillaume31
quelle
+1 Ich finde es viel einfacher, Randfälle mit Scheintests zu konstruieren, als die Datenbank mit diesen Fällen zu füllen.
Rob
Ich stimme den meisten Ihrer Antworten zu. Ich bin mir jedoch nicht sicher, ob ich dem Adapterteil zustimme. HTable ist ein Schmerz zu verspotten, weil es ziemlich Bare-Metal ist. Wenn Sie beispielsweise eine Batch-Abrufoperation ausführen möchten, müssen Sie eine Reihe von Abrufobjekten erstellen, diese in eine Liste aufnehmen und dann HTable.batch () aufrufen. Aus spöttischer Sicht ist dies ein schwerwiegender Schmerz, da Sie einen benutzerdefinierten Matcher erstellen müssen, der die Liste der Get-Objekte überprüft, die Sie an HTable.batch () übergeben, und dann die korrekten Ergebnisse für diese Liste der Get () -Objekte zurückgibt. Ein schwerer Schmerz.
Sangfroid
Ich nehme an, ich könnte eine nette, freundliche Wrapper-Klasse für HTable erstellen, die sich um all das Housekeeping kümmert, aber zu diesem Zeitpunkt habe ich das Gefühl, dass ich ein Framework um HTable aufbaue, und sollte das wirklich mein Job sein? Normalerweise "Bauen wir ein Framework!" ist ein Zeichen, dass ich in die falsche Richtung gehe. Ich könnte Tage damit verbringen, Klassen zu schreiben, um HBase freundlicher zu machen, und ich weiß nicht, ob das eine gute Verwendung meiner Zeit ist. Außerdem gehe ich um eine Schnittstelle oder einen Wrapper, anstatt nur um das einfache alte HTable-Objekt, und das würde meinen Code definitiv komplexer machen.
Sangfroid
Aber ich stimme in deinem Hauptpunkt überein, dass man keine Mocks für Klassen schreiben sollte, die sie nicht besitzen. Und stimmen Sie definitiv zu, dass es keinen Sinn macht, einen Mock-Test zu schreiben, der das Gleiche wie ein Integrationstest testet. Es scheint, dass Mocks am besten zum Testen von Schnittstellen / Verträgen geeignet sind. Danke für den Rat - das hat mir sehr geholfen!
Sangfroid
Ich habe keine Ahnung, was HTable wirklich macht und wie Sie es verwenden. Nehmen Sie also nicht mein Deckblattbeispiel für den Brief. Ich hatte einen Wrapper / Adapter erwähnt, weil ich dachte, das zu verpackende Ding sei relativ klein. Sie müssen HTable nicht mit einer Eins-zu-Eins-Replik ausstatten. Dies wäre natürlich ein Problem, geschweige denn ein ganzes Framework. Sie benötigen jedoch eine Nahtstelle , eine Schnittstelle zwischen dem Bereich Ihrer Anwendung und dem von HTable. Einige Funktionen von HTable sollten in den Begriffen Ihrer Anwendung neu formuliert werden. Das Repository-Muster ist eine perfekte Verkörperung einer solchen Naht, wenn es um den Datenzugriff geht.
Guillaume31
11

Aus philosophischer Sicht sollten Tests, die Mocks verwenden, Vorrang vor Tests haben, die einen Live-Endpunkt verwenden

Ich denke zumindest, das ist ein Punkt, an dem TDD-Befürworter derzeit kontrovers diskutiert werden.

Meine persönliche Meinung geht darüber hinaus, dass ein mockbasierter Test meistens eine Art Schnittstellenvertrag darstellt ; im Idealfall bricht es, wenn und nur wenn Sie die Schnittstelle ändern . Als solches ist es in relativ stark typisierten Sprachen wie Java und bei Verwendung einer explizit definierten Schnittstelle fast völlig überflüssig: Der Compiler hat Ihnen bereits mitgeteilt, ob Sie die Schnittstelle geändert haben.

Die Hauptausnahme ist, wenn Sie eine sehr allgemeine Benutzeroberfläche verwenden, die möglicherweise auf Anmerkungen oder Überlegungen basiert und die der Compiler nicht automatisch überwachen kann. Selbst dann sollten Sie überprüfen, ob es eine Möglichkeit gibt, die Validierung programmgesteuert (z. B. eine SQL-Syntaxprüfungsbibliothek) und nicht manuell mithilfe von Mocks durchzuführen.

Dies ist der letztere Fall, den Sie durchführen, wenn Sie mit einer "Live" -Lokaldatenbank testen. Die Implementierung von htable beginnt und führt eine viel umfassendere Validierung des Schnittstellenvertrags durch, als Sie jemals gedacht hätten, dies von Hand zu schreiben.

Leider ist der Test, dass:

  • gilt für den Code, der zum Zeitpunkt der Testerstellung angegeben war
  • bietet keine Garantie für andere Eigenschaften des Codes als die, die vorhanden sind und die Art der Ausführung
  • schlägt jedes Mal fehl, wenn Sie diesen Code ändern

Solche Tests sollten natürlich sofort gelöscht werden.

Soru
quelle
1
Ich kann das nicht genug unterstützen. Ich hätte lieber eine 1-teilige Abdeckung mit großartigen Tests als eine 100-teilige Abdeckung mit Füllstoff.
Ian
3
Mock-basierte Tests beschreiben zwar den Vertrag, mit dem zwei Objekte miteinander kommunizieren, aber sie gehen weit über das hinaus, was das Typsystem einer Sprache wie Java kann. Es geht nicht nur um Methodensignaturen, sondern sie können auch gültige Wertebereiche für Argumente oder zurückgegebene Ergebnisse angeben, welche Ausnahmen in welcher Reihenfolge und wie oft Methoden aufgerufen werden können usw. Der Compiler allein wird Sie nicht warnen, wenn dies der Fall ist sind Änderungen in denen. Insofern halte ich sie überhaupt nicht für überflüssig. Weitere Informationen zu mockbasierten Tests finden Sie unter infoq.com/presentations/integration-tests-scam .
Guillaume31
1
... stimme zu, dh teste die Logik rund um den Schnittstellenaufruf
Rob
1
Kann sicherlich ungeprüfte Ausnahmen, nicht deklarierte Voraussetzungen und implizite Zustände zu der Liste von Dingen hinzufügen, die eine Schnittstelle weniger statisch typisieren, und so mockbasiertes Testen anstelle einfacher Kompilierung rechtfertigen. Das Problem ist jedoch, dass , wenn diese Aspekte nicht ändern, ihre Spezifikation ist implizit und bei den Tests aller Clients verteilt. Welche werden wahrscheinlich nicht aktualisiert, und so sitzen still einen Fehler hinter einem grünen Häckchen versteckt.
Soru
"Ihre Spezifikation ist implizit": nicht, wenn Sie Vertragstests für Ihre Schnittstellen schreiben ( blog.thecodewhisperer.com/2011/07/07/contract-tests-an-example ) und diese beim Einrichten von Mocks einhalten .
Guillaume31
5

Wie lange dauert die Ausführung eines endpunktbasierten Tests als eines scheinbasierten Tests? Wenn es erheblich länger dauert, lohnt es sich, Zeit für das Schreiben von Tests zu investieren, um die Komponententests zu beschleunigen - denn Sie müssen sie viele Male ausführen. Wenn es nicht wesentlich länger dauert, obwohl es sich bei den endpunktbasierten Tests nicht um "reine" Einheitentests handelt, gibt es keinen Grund, religiös zu sein, solange sie die Einheit gut testen.

Carl Manaster
quelle
4

Ich bin mit der Antwort von guillaume31 völlig einverstanden, verspotte niemals Typen, die du nicht besitzt !.

Normalerweise spiegelt ein Testschmerz (das Verspotten einer komplexen Benutzeroberfläche) ein Problem in Ihrem Design wider. Möglicherweise benötigen Sie eine gewisse Abstraktion zwischen Ihrem Modell und Ihrem Datenzugriffscode, beispielsweise mithilfe einer hexagonalen Architektur und eines Repository-Musters, um diese Art von Problemen zu lösen.

Wenn Sie einen Integrationstest zur Überprüfung durchführen möchten, führen Sie einen Integrationstest durch. Wenn Sie einen Komponententest durchführen möchten, weil Sie Ihre Logik testen, führen Sie einen Komponententest durch und isolieren Sie die Persistenz. Aber wenn Sie einen Integrationstest durchführen, weil Sie nicht wissen, wie Sie Ihre Logik von einem externen System isolieren können (oder weil es ein Problem ist), ist dies ein großer Geruch. Sie wählen Integration anstelle von Einheit, da Ihr Design nur begrenzt ist und keine wirklichen Anforderungen bestehen um die Integration zu testen.

Schauen Sie sich dieses Gespräch von Ian Cooper an: http://vimeo.com/68375232 , er spricht über sechseckige Architektur und Tests, er spricht darüber, wann und was verspottet werden soll, ein wirklich inspiriertes Gespräch, das viele Fragen wie Ihre über echtes TDD löst .

AlfredoCasado
quelle
1

TL; DR - Wie ich das sehe, hängt davon ab, wie viel Aufwand Sie für Tests aufwenden und ob es besser gewesen wäre, mehr davon für Ihr tatsächliches System auszugeben.

Lange Version:

Hier einige gute Antworten, aber ich nehme es anders an: Testen ist eine wirtschaftliche Aktivität, die sich auszahlen muss, und wenn die von Ihnen aufgewendete Zeit nicht für die Entwicklung und Systemzuverlässigkeit aufgewendet wird (oder für alles andere, was Sie herausfinden möchten) von Tests), dann können Sie eine schlechte Investition machen; Sie sind im Bauwesen tätig und schreiben keine Tests. Daher ist die Reduzierung des Aufwands zum Schreiben und Verwalten von Tests von entscheidender Bedeutung.

Einige der wichtigsten Werte, die ich durch Tests erhalte, sind:

  • Zuverlässigkeit (und damit Entwicklungsgeschwindigkeit): Code umgestalten / ein neues Framework integrieren / eine Komponente / einen Port auf eine andere Plattform tauschen, darauf vertrauen, dass alles noch funktioniert
  • Design-Feedback: klassisches TDD / BDD-Feedback zur Verwendung Ihres Codes für Ihre Low- / Mid-Level-Schnittstellen

Das Testen gegen einen Live-Endpunkt sollte diese weiterhin bereitstellen.

Einige Nachteile beim Testen mit einem Live-Endpunkt:

  • Umgebungssetup - Das Konfigurieren und Standardisieren der Testlaufumgebung ist aufwändiger, und geringfügig unterschiedliche Umgebungssetups können zu geringfügig unterschiedlichem Verhalten führen
  • Zustandslosigkeit - Wenn Sie gegen einen Live-Endpunkt arbeiten, kann dies dazu führen, dass Schreibtests gefördert werden, die sich auf einen mutierenden Endpunktstatus stützen, der fragil und schwer zu begründen ist (dh, wenn etwas ausfällt, scheitert es an einem seltsamen Status?).
  • Die Testlaufumgebung ist instabil. Wenn ein Test fehlschlägt, ist dies der Test, der Code oder der Live-Endpunkt?
  • Laufgeschwindigkeit - Ein Live-Endpunkt ist normalerweise langsamer und manchmal schwieriger zu parallelisieren
  • Erstellen von Edge Cases zum Testen - normalerweise trivial mit einem Mock, manchmal schmerzhaft mit einem Live-Endpunkt (z. B. sind Transport- / HTTP-Fehler schwierig einzurichten)

Wenn ich mich in dieser Situation befände und die Nachteile kein Problem zu sein schienen, während das Verspotten des Endpunkts mein Testschreiben erheblich verlangsamte, würde ich sofort gegen einen Live-Endpunkt testen, solange ich mir sicher bin, dass dies der Fall ist Überprüfen Sie nach einiger Zeit erneut, ob die Nachteile in der Praxis kein Problem darstellen.

Orip
quelle
1

Aus der Sicht der Tests gibt es einige Anforderungen, die ein absolutes Muss sind:

  • Tests (Einheit oder auf andere Weise) dürfen niemals die Möglichkeit haben, Produktionsdaten anzufassen
  • Die Ergebnisse eines Tests dürfen niemals die Ergebnisse eines anderen Tests beeinflussen
  • Sie müssen immer von einer bekannten Position aus starten

Dies ist eine große Herausforderung, wenn Sie eine Verbindung zu einer Quelle herstellen, die den Status außerhalb Ihrer Tests beibehält. Es ist kein "reines" TDD, aber die Ruby on Rails-Crew hat dieses Problem auf eine Weise gelöst, die möglicherweise für Ihre Zwecke angepasst werden kann. Das Rails Test Framework funktionierte folgendermaßen:

  • Die Testkonfiguration wurde automatisch ausgewählt, wenn Unit-Tests ausgeführt wurden
  • Die Datenbank wurde zu Beginn der Ausführung von Komponententests erstellt und initialisiert
  • Die Datenbank wurde gelöscht, nachdem Unit-Tests ausgeführt wurden
  • Bei Verwendung von SqlLite wurde in der Testkonfiguration eine RAM-Datenbank verwendet

All diese Arbeiten wurden in das Testgeschirr eingebaut und funktionieren recht gut. Es steckt noch viel mehr dahinter, aber die Grundlagen reichen für dieses Gespräch aus.

In den verschiedenen Teams, mit denen ich im Laufe der Zeit zusammengearbeitet habe, haben wir Entscheidungen getroffen, die das Testen von Code fördern, auch wenn dies nicht der richtige Weg ist. Im Idealfall würden wir alle Aufrufe an einen Datenspeicher mit dem von uns gesteuerten Code umschließen. Theoretisch könnten wir, wenn eines dieser alten Projekte neue Mittel erhalten würde, zurückgehen und es von einer datenbankgebundenen zu einer Hadoop-gebundenen Aufgabe machen, indem wir unsere Aufmerksamkeit auf nur eine Handvoll Klassen konzentrieren.

Wichtig ist, dass Sie sich nicht mit den Produktionsdaten anlegen und sicherstellen, dass Sie wirklich das testen, was Sie zu testen glauben. Es ist sehr wichtig, den externen Dienst bei Bedarf auf einen bekannten Basiswert zurücksetzen zu können - auch von Ihrem Code aus.

Berin Loritsch
quelle