Heim > Artikel > Backend-Entwicklung > Müheloses HTTP-Client-Testen in Go
Als Softwareentwickler sind Sie wahrscheinlich mit dem Schreiben von Code für die Interaktion mit externen HTTP-Diensten vertraut. Schließlich ist es eines der häufigsten Dinge, die wir tun! Ob es darum geht, Daten abzurufen, Zahlungen mit einem Anbieter abzuwickeln oder Social-Media-Beiträge zu automatisieren, bei unseren Anwendungen handelt es sich fast immer um externe HTTP-Anfragen. Damit unsere Software zuverlässig und wartbar ist, benötigen wir eine Möglichkeit, den Code zu testen, der für die Ausführung dieser Anforderungen und die Behandlung der möglicherweise auftretenden Fehler verantwortlich ist. Dies lässt uns ein paar Optionen:
Diese Optionen sind nicht so schlimm, vor allem, wenn sie alle zusammen verwendet werden können, aber wir haben eine bessere Option: VCR-Tests.
VCR-Tests, benannt nach dem Videokassettenrecorder, sind eine Art Scheintests, bei denen Testvorrichtungen aus tatsächlichen Anforderungen generiert werden. Die Vorrichtungen zeichnen die Anfrage und Antwort auf, um sie in zukünftigen Tests automatisch wiederzuverwenden. Auch wenn Sie die Fixtures anschließend möglicherweise ändern müssen, um dynamische zeitbasierte Eingaben zu verarbeiten oder Anmeldeinformationen zu entfernen, ist dies viel einfacher, als Mocks von Grund auf zu erstellen. Das VCR-Testen bietet einige zusätzliche Vorteile:
Da Sie nun die Motivation hinter dem VCR-Testen erkennen, wollen wir uns genauer mit der Implementierung in Go mit dnaeon/go-vcr befassen.
Diese Bibliothek lässt sich nahtlos in jeden HTTP-Clientcode integrieren. Wenn Ihr Client-Bibliothekscode das Festlegen von *http.Client oder http.Transport des Clients nicht bereits zulässt, sollten Sie dies jetzt hinzufügen.
Für diejenigen, die es nicht wissen: Ein http.Transport ist eine Implementierung von http.RoundTripper, bei dem es sich im Grunde um eine clientseitige Middleware handelt, die auf die Anfrage/Antwort zugreifen kann. Dies ist nützlich, um automatische Wiederholungsversuche für Antworten der Stufen 500 oder 429 (Ratenbegrenzung) zu implementieren oder Metriken hinzuzufügen und Anfragen zu protokollieren. In diesem Fall ermöglicht es go-vcr, Anfragen an seinen eigenen In-Process-HTTP-Server umzuleiten.
Beginnen wir mit einem einfachen Beispiel. Wir möchten ein Paket erstellen, das Anfragen an die kostenlose https://cleanuri.com-API stellt. Dieses Paket stellt eine Funktion bereit: Shorten(string) (string, error)
Da es sich um eine kostenlose API handelt, können wir sie vielleicht einfach testen, indem wir Anfragen direkt an den Server stellen? Dies funktioniert möglicherweise, kann jedoch zu einigen Problemen führen:
Ok, was wäre, wenn wir eine Schnittstelle erstellen und sie verspotten? Unser Paket ist unglaublich einfach, das würde es also überkomplizieren. Da die unterste Ebene, die wir verwenden, *http.Client ist, müssten wir eine neue Schnittstelle darum herum definieren und einen Mock implementieren.
Eine weitere Option besteht darin, die Ziel-URL zu überschreiben, um einen lokalen Port zu verwenden, der von httptest.Server bereitgestellt wird. Dies ist im Grunde eine vereinfachte Version der Funktion von go-vcr und würde in unserem einfachen Fall ausreichen, ist jedoch in komplexeren Szenarien nicht wartbar. Selbst in diesem Beispiel werden Sie sehen, dass die Verwaltung generierter Fixtures einfacher ist als die Verwaltung verschiedener Mock-Server-Implementierungen.
Da unsere Schnittstelle bereits definiert ist und wir durch das Ausprobieren der Benutzeroberfläche unter https://cleanuri.com einige gültige Eingaben/Ausgaben kennen, ist dies eine großartige Gelegenheit, testgetriebene Entwicklung zu üben. Wir beginnen mit der Implementierung eines einfachen Tests für unsere Shorten-Funktion:
package shortener_test func TestShorten(t *testing.T) { shortened, err := shortener.Shorten("https://dev.to/calvinmclean") if err != nil { t.Errorf("unexpected error: %v", err) } if shortened != "https://cleanuri.com/7nPmQk" { t.Errorf("unexpected result: %v", shortened) } }
Ganz einfach! Wir wissen, dass die Kompilierung des Tests fehlschlagen wird, weil shortener.Shorten nicht definiert ist, aber wir führen ihn trotzdem aus, sodass die Behebung zufriedenstellender ist.
Lassen Sie uns abschließend mit der Implementierung dieser Funktion fortfahren:
package shortener var DefaultClient = http.DefaultClient const address = "https://cleanuri.com/api/v1/shorten" // Shorten will returned the shortened URL func Shorten(targetURL string) (string, error) { resp, err := DefaultClient.PostForm( address, url.Values{"url": []string{targetURL}}, ) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("unexpected response code: %d", resp.StatusCode) } var respData struct { ResultURL string `json:"result_url"` } err = json.NewDecoder(resp.Body).Decode(&respData) if err != nil { return "", err } return respData.ResultURL, nil }
Jetzt ist unser Test bestanden! Es ist genauso befriedigend, wie ich es versprochen habe.
Um VCR verwenden zu können, müssen wir den Recorder initialisieren und shortener.DefaultClient zu Beginn des Tests überschreiben:
func TestShorten(t *testing.T) { r, err := recorder.New("fixtures/dev.to") if err != nil { t.Fatal(err) } defer func() { require.NoError(t, r.Stop()) }() if r.Mode() != recorder.ModeRecordOnce { t.Fatal("Recorder should be in ModeRecordOnce") } shortener.DefaultClient = r.GetDefaultClient() // ...
Führen Sie den Test aus, um Fixtures/dev.to.yaml mit Details zur Anfrage und Antwort des Tests zu generieren. Wenn wir den Test erneut ausführen, verwendet er die aufgezeichnete Antwort, anstatt sich an den Server zu wenden. Verlassen Sie sich nicht nur auf mein Wort; Schalten Sie das WLAN Ihres Computers aus und führen Sie die Tests erneut durch!
Möglicherweise stellen Sie auch fest, dass die zum Ausführen des Tests benötigte Zeit relativ konstant ist, da go-vcr die Antwortdauer aufzeichnet und wiedergibt. Sie können dieses Feld im YAML manuell ändern, um die Tests zu beschleunigen.
Um die Vorteile dieser Art von Tests weiter zu demonstrieren, fügen wir eine weitere Funktion hinzu: Wiederholen Sie den Versuch nach 429-Antwort aufgrund der Ratenbegrenzung. Da wir wissen, dass das Ratenlimit der API pro Sekunde liegt, kann Shorten automatisch eine Sekunde warten und es erneut versuchen, wenn es einen 429-Antwortcode erhält.
Ich habe versucht, diesen Fehler direkt über die API zu reproduzieren, aber es scheint, als ob sie mit vorhandenen URLs aus einem Cache reagiert, ohne die Ratenbegrenzung zu berücksichtigen. Anstatt den Cache mit gefälschten URLs zu verschmutzen, können wir dieses Mal unsere eigenen Mocks erstellen.
Dies ist ein einfacher Vorgang, da wir bereits Spielpläne generiert haben. Nachdem Sie Fixtures/dev.to.yaml in eine neue Datei kopiert/eingefügt haben, duplizieren Sie die erfolgreiche Anfrage-/Antwort-Interaktion und ändern Sie den Code der ersten Antwort von 200 auf 429. Dieses Fixture ahmt einen erfolgreichen Wiederholungsversuch nach einem Fehler bei der Ratenbegrenzung nach.
Der einzige Unterschied zwischen diesem Test und dem ursprünglichen Test ist der neue Dateiname des Fixtures. Die erwartete Ausgabe ist dieselbe, da Shorten den Fehler behandeln sollte. Das bedeutet, dass wir den Test in eine Schleife werfen können, um ihn dynamischer zu gestalten:
func TestShorten(t *testing.T) { fixtures := []string{ "fixtures/dev.to", "fixtures/rate_limit", } for _, fixture := range fixtures { t.Run(fixture, func(t *testing.T) { r, err := recorder.New(fixture) if err != nil { t.Fatal(err) } defer func() { require.NoError(t, r.Stop()) }() if r.Mode() != recorder.ModeRecordOnce { t.Fatal("Recorder should be in ModeRecordOnce") } shortener.DefaultClient = r.GetDefaultClient() shortened, err := shortener.Shorten("https://dev.to/calvinmclean") if err != nil { t.Errorf("unexpected error: %v", err) } if shortened != "https://cleanuri.com/7nPmQk" { t.Errorf("unexpected result: %v", shortened) } }) } }
Wieder einmal schlägt der neue Test fehl. Diesmal aufgrund der nicht behandelten 429-Antwort, also implementieren wir die neue Funktion, um den Test zu bestehen. Um die Einfachheit zu wahren, behandelt unsere Funktion den Fehler mithilfe von time.Sleep und einem rekursiven Aufruf, anstatt sich mit der Komplexität der Berücksichtigung maximaler Wiederholungsversuche und exponentieller Backoffs zu befassen:
func Shorten(targetURL string) (string, error) { // ... switch resp.StatusCode { case http.StatusOK: case http.StatusTooManyRequests: time.Sleep(time.Second) return Shorten(targetURL) default: return "", fmt.Errorf("unexpected response code: %d", resp.StatusCode) } // ...
Jetzt führen Sie die Tests noch einmal durch und sehen Sie, wie sie bestehen!
Gehen Sie selbst noch einen Schritt weiter und versuchen Sie, einen Test für eine fehlerhafte Anfrage hinzuzufügen, die auftritt, wenn eine ungültige URL wie „my-fake-url“ verwendet wird.
Der vollständige Code für dieses Beispiel (und den Bad-Request-Test) ist auf Github verfügbar.
Die Vorteile von VCR-Tests werden bereits anhand dieses einfachen Beispiels deutlich, aber sie sind noch wirkungsvoller, wenn es um komplexe Anwendungen geht, bei denen die Anforderungen und Antworten unhandlich sind. Anstatt sich mit langwierigen Simulationen herumzuschlagen oder sich dafür zu entscheiden, überhaupt keine Tests durchzuführen, empfehle ich Ihnen, dies in Ihren eigenen Anwendungen auszuprobieren. Wenn Sie bereits auf Integrationstests angewiesen sind, ist der Einstieg in VCR noch einfacher, da Sie bereits über echte Anforderungen verfügen, die Fixtures generieren können.
Weitere Dokumentation und Beispiele finden Sie im Github-Repository des Pakets: https://github.com/dnaeon/go-vcr
Das obige ist der detaillierte Inhalt vonMüheloses HTTP-Client-Testen in Go. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!