Wie schreibe ich Tests für einen eventuell konsistenten Dienst?

17

Ich baue einen Dienst auf dem Google App Engine-Datenspeicher auf, der letztendlich ein konsistenter Datenspeicher ist. Für meine Bewerbung ist das in Ordnung.

Ich entwickle jedoch Tests, die Dinge wie PUT-Objekt und dann GET-Objekt ausführen und Eigenschaften für das zurückgegebene Objekt überprüfen. Leider sind diese einfachen Tests nicht reproduzierbar, da der Datenspeicher schließlich konsistent ist.

Wie testen Sie einen eventuell konsistenten Service?

Doug Richardson
quelle
2
Warum testen Sie, ob Sie überhaupt eine Reproduzierbarkeit mit einem externen Dienst erwarten?
... und was versuchst du eigentlich zu testen? dein Code? oder googles?
5
Ich teste das gesamte System. Das heißt, sie sind Integrationstests, keine Unit-Tests.
Doug Richardson
3
How can I reproducibly test an eventually consistent service? - Das kannst du nicht. Sie müssen das Wort "reproduzierbar" oder das Wort "eventuell" entfernen. Sie können nicht beide haben.
Robert Harvey
1
Wenn es irgendwann konsistent ist, ob reproduzierbar oder nicht, wird jedes Ergebnis erfolgreich sein. Sie haben bereits gesagt, dass es für Ihre App in Ordnung ist. Was testen Sie also wirklich? Die Möglichkeit? Die Integration mit GAE? Dein Code?
Laiv

Antworten:

16

Berücksichtigen Sie nicht-funktionale Anforderungen beim Entwerfen Ihrer Funktionstests. Wenn für Ihren Service die nicht-funktionale Anforderung "Konsistent innerhalb von x (Sekunden / Minuten / usw.)" festgelegt ist, führen Sie einfach die PUT-Anforderungen aus, warten Sie x und führen Sie dann die GET-Anforderungen aus.

Wenn die Daten zu diesem Zeitpunkt noch nicht "eingetroffen" sind, können Sie die PUT-Anforderung als nicht Ihren Anforderungen entsprechend betrachten.

Dan Ambrogio
quelle
7

Sie möchten wirklich, dass Ihre Tests schnell und konsistent sind. Wenn Sie Tests erstellen, die gelegentlich aufgrund von Konsistenzfehlern fehlschlagen, ignorieren Sie den Test, wenn er fehlschlägt, und was nützt er dann?

Erstellen Sie einen gefälschten Dienst, der die PUT- und GET-Anforderungen verarbeitet, aber über eine zusätzliche Operation verfügt, um die Konsistenz zu gewährleisten. Ihr Test ist dann:

datastore.do_put(myobj);
datastore.make_consistent();
validate(datastore.do_get(), myobj);

Auf diese Weise können Sie das Verhalten Ihrer Software testen, wenn das GET das PUT-Objekt erfolgreich abruft. Außerdem können Sie das Verhalten Ihrer Software testen, wenn der GET das Objekt (oder das richtige Objekt) nicht findet, weil der Dienst noch nicht konsistent ist. Lass den Anruf einfach aus make_consistent().

Es lohnt sich immer noch, Tests durchzuführen, die mit dem eigentlichen Service interagieren. Sie sollten jedoch außerhalb Ihres normalen Entwicklungsworkflows ausgeführt werden, da sie niemals 100% zuverlässig sind (z. B. wenn der Service nicht verfügbar ist). Diese Tests sollten verwendet werden, um:

  1. Bereitstellung von Metriken für die durchschnittliche und die Worst-Case-Zeit zwischen einem PUT und einem anschließenden GET, die konsistent werden; und
  2. Stellen Sie sicher, dass sich Ihr gefälschter Dienst ähnlich wie der echte Dienst verhält. Siehe https://codewithoutrules.com/2016/07/31/verified-fakes/
Jonathan Giddy
quelle
6

OK, also. "Was testen Sie?" Ist die Schlüsselfrage.

  • Ich teste meine interne Logik, was passiert, wenn das Google-Zeug funktioniert

In diesem Fall sollten Sie die Google-Dienste verspotten und immer eine Antwort zurückgeben.

  • Ich teste meine Logik, um mit den vorübergehenden Fehlern fertig zu werden, von denen ich weiß, dass sie von Google erzeugt werden

In diesem Fall sollten Sie die Google-Dienste verspotten und den vorübergehenden Fehler immer vor der richtigen Antwort zurückgeben

  • Ich teste, ob mein Produkt tatsächlich mit dem echten Google-Service funktioniert

Sie sollten die echten Google-Dienste einbinden und den Test ausführen. Aber! In den Code, den Sie testen, sollte die Behandlung vorübergehender Fehler (Wiederholung) integriert sein. SO sollten Sie eine einheitliche Antwort erhalten. (es sei denn, Google ist sehr schlecht benommen)

Ewan
quelle
+1 für den Mock-Vorschlag - Ich würde mehr Up-Votes für die zusätzlichen Optionen geben, wenn ich könnte.
Mcottle
6

Verwenden Sie eine der folgenden Möglichkeiten:

  • Versuchen Sie nach dem PUT N-mal, bis der Vorgang erfolgreich war. Scheitern, wenn nach N Versuchen kein Erfolg eintritt.
  • Schlaf zwischen PUT und GET

Leider müssen Sie für beide Techniken magische Werte (N oder Schlafdauer) auswählen.

Doug Richardson
quelle
1
Könnten Sie klarstellen: Sind diese Alternativen oder komplementär? Ich denke, Sie wollen damit sagen, dass es sich um Alternativen handelt - und so denke ich über sie. Aber vielleicht irre ich mich.
Robin Green
1
Richtig, ich meinte sie als Alternativen.
Doug Richardson
2

Soweit ich weiß, ermöglicht der Google Cloud-Datenspeicher sowohl konsistente als auch letztendlich konsistente Abfragen .

Der Kompromiss ist, dass stark konsistente Abfragen ziemlich stark ratenbegrenzt sind (etwas, mit dem Sie während des Testens leben können).

Eine Möglichkeit besteht darin , Ihre Abfragen in einem Wrapper an den Datenspeicher zu übergeben, der zu Testzwecken eine starke Konsistenz ermöglicht.

Beispielsweise könnten Sie Methoden namens start_debug_strong_consistency()und haben end_debug_strong_consistency().

Die start-Methode erstellt einen Schlüssel, der als Vorgängerschlüssel für alle nachfolgenden Abfragen verwendet werden kann, und die end-Methode löscht den Schlüssel.

Die einzige Änderung an den tatsächlichen Abfragen, die Sie testen, ist das Aufrufen, setAncestor(your_debug_key)wenn dieser Schlüssel vorhanden ist.


quelle
1

Ein Ansatz, der in der Theorie nützlich, aber möglicherweise nicht immer praktisch ist, besteht darin, alle Schreiboperationen im zu testenden System idempotent zu machen . Das bedeutet, dass Sie, vorausgesetzt Ihr Testcode testet die Dinge in einer festgelegten Reihenfolge, alle Lese- und Schreibvorgänge einzeln wiederholen können, bis Sie das erwartete Ergebnis erhalten. Dies kann bis zu einem im Testcode definierten Zeitlimit dauern. Das heißt, führen Sie die Aktion A1 aus, und wiederholen Sie den Vorgang, falls erforderlich, bis das Ergebnis B1 ist. Führen Sie dann die Aktion A2 aus, und wiederholen Sie den Vorgang, falls erforderlich, bis das Ergebnis B2 ist, und so weiter.

In diesem Fall müssen Sie nicht nach den Voraussetzungen für Schreibvorgänge suchen, da diese bereits von den Schreibvorgängen überprüft werden. Sie müssen sie nur wiederholen, bis sie erfolgreich sind.

Verwenden Sie möglichst dieselben "Standard" - Zeitlimits, die erhöht werden können, wenn das gesamte System langsamer wird, und überschreiben Sie die Standardeinstellungen einzeln, wenn Sie besonders langsame Vorgänge wiederholen.

Robin Green
quelle
1

Ein Dienst wie der Google App Engine-Datenspeicher basiert auf der Datenreplikation über mehrere global verteilte Präsenzpunkte (POP). Jeder Integrationstest für einen eventuell konsistenten Dienst ist in Wirklichkeit ein Test der Replikationsrate dieses Dienstes für alle POPs. Die Rate, mit der Inhalte auf jeden POP in einem bestimmten Dienst verteilt werden, ist abhängig von einer Reihe von Faktoren, wie der Replikationsmethode und verschiedenen Internet-Transportproblemen, nicht für jeden POP im Dienst gleich - dies sind zwei Beispiele Diese Berichte machen einen Großteil der Berichte in einem eventuell konsistenten Datenspeicherdienst aus (zumindest war dies meine Erfahrung, als ich für ein großes CDN arbeitete).

Um die Replikation eines Objekts auf einer bestimmten Plattform effektiv zu testen, müssen Sie den Test so einstellen, dass dasselbe kürzlich platzierte Objekt von allen POPs des Dienstes angefordert wird. Ich schlage vor, die POPs-Liste ein bis fünf Mal zu testen, oder bis alle POPs in Ihrer POPs-Liste das Objekt melden. Hier finden Sie eine Reihe von Intervallen, in denen Sie den Test durchführen können: 1, 5, 60 Minuten, 12 Stunden, 25 Stunden, nachdem Sie ihn in den Datenspeicher gestellt haben. Der Schlüssel besteht darin, die Ergebnisse in jedem Intervall für eine spätere Überprüfung und Analyse zu protokollieren, um ein Gefühl für die Fähigkeit eines bestimmten Dienstes zu bekommen, Objekte global zu replizieren. Häufig ziehen Datenspeicherdienste eine lokale Kopie erst dann auf einen POP, wenn sie lokal angefordert wurde. [Das Routing erfolgt über das BGP-Protokoll. Daher muss der Test das Objekt von jedem bestimmten POP anfordern, damit es für eine bestimmte Plattform global gültig ist.] . Im Fall des Google-Datenspeichers möchten Sie Ihren Test so einrichten, dass ein bestimmtes Objekt von "über 70 Präsenzpunkten in 33 Ländern" abgefragt wird. Sie müssten wahrscheinlich die POP-spezifische Adress-URL-Liste vom Google-Support erhalten [Ref:https://cloud.google.com/about/locations/ ] oder Fastly Support [ https://www.fastly.com/resources ], wenn Google Fastly für die Replikation verwendet .

Einige Vorteile dieser Methode: 1) Sie erhalten ein Gefühl für die Replikationsplattform eines bestimmten Dienstes, kennen dessen Stärken und Schwachstellen insgesamt auf globaler Ebene [wie beim Integrationstest]. 2) Für jedes Objekt, das Sie testen, steht Ihnen ein Tool zum Erwärmen von Inhalten zur Verfügung [stellen Sie diese erste Anforderung, mit der die Kopie bei einem bestimmten lokalen POP erstellt wird]. Auf diese Weise können Sie sicherstellen, dass Inhalte global verbreitet werden, bevor Ihre Kunden sie anfordern überall auf der Erde.

Rami Kuttaineh
quelle
0

Ich habe Erfahrung mit Google App Engine Datastore. Überraschenderweise läuft es lokal eher "letztendlich" als "konsistent". Das einfachste Beispiel: Erstellen Sie eine neue Entität und rufen Sie sie ab. In den letzten 5 Jahren habe ich häufig festgestellt, dass das lokal ausgeführte SDK die neue Entität nicht sofort findet, sondern erst nach etwa einer halben Sekunde.

Auf den echten Google-Servern konnte ich dieses Verhalten jedoch nicht feststellen. Sie versuchen, Ihren Datastore-Client immer auf demselben Server laufen zu lassen, sodass Änderungen in der Regel sofort in Abfragen berücksichtigt werden.

Mein Rat für Integrationstests ist, sie auf den realen Servern auszuführen, und dann müssen Sie wahrscheinlich keine falschen Abfragen oder Verzögerungen durchführen, um Ihre Ergebnisse zu erhalten.

christian.simms
quelle
Dies ist zwar praktisch, kann jedoch zu geringfügigen Brüchen führen, bei denen mehrere Anwendungsserver in Ihren Integrationstests unentdeckt bleiben. Ich denke, sie haben den lokalen Server aus gutem Grund konsistent gemacht!
Robin Green