Heim  >  Artikel  >  Backend-Entwicklung  >  Müheloses HTTP-Client-Testen in Go

Müheloses HTTP-Client-Testen in Go

王林
王林Original
2024-07-17 12:24:281184Durchsuche

Effortless HTTP Client Testing in Go

Einführung

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:

  • Implementieren Sie einen Client-Wrapper, der vom Hauptanwendungscode verspottet werden kann, was beim Testen immer noch eine Lücke hinterlässt
  • Analyse und Verarbeitung der Testantwort getrennt von der eigentlichen Anforderungsausführung. Während es wahrscheinlich eine gute Idee ist, diese untergeordnete Einheit einzeln zu testen, wäre es schön, wenn dies problemlos zusammen mit den tatsächlichen Anforderungen abgedeckt werden könnte
  • Verschieben Sie Tests zu Integrationstests, die die Entwicklung verlangsamen können und einige Fehlerszenarien nicht testen können und möglicherweise durch die Zuverlässigkeit anderer Dienste beeinträchtigt werden

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:

  • Führen Sie Ihren Code bis hinunter zur HTTP-Ebene aus, damit Sie Ihre Anwendung durchgängig testen können
  • Sie können reale Antworten nehmen und die generierten Vorrichtungen modifizieren, um die Reaktionszeit zu verlängern, eine Ratenbegrenzung zu bewirken usw., um Fehlerszenarien zu testen, die nicht oft organisch auftreten
  • Wenn Ihr Code ein externes Paket/eine externe Bibliothek für die Interaktion mit einer API verwendet, wissen Sie möglicherweise nicht genau, wie eine Anfrage und eine Antwort aussehen, sodass VCR-Tests dies automatisch herausfinden können
  • Generierte Fixtures können auch zum Debuggen von Tests und zum Sicherstellen verwendet werden, dass Ihr Code die erwartete Anforderung ausführt

Tieferer Tauchgang mit Go

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.

Beispiel für einen URL-Shortener

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:

  • Der Server hat ein Ratenlimit von 2 Anfragen/Sekunde, was bei vielen Tests ein Problem sein könnte
  • Wenn der Server ausfällt oder eine Weile braucht, um zu antworten, können unsere Tests fehlschlagen
  • Obwohl die verkürzten URLs zwischengespeichert werden, können wir nicht garantieren, dass wir jedes Mal die gleiche Ausgabe erhalten
  • Es ist einfach unhöflich, unnötigen Datenverkehr an eine kostenlose API zu senden!

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.

Spottfehler

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.

Abschluss

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!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn