https://d226lax1qjow5r.cloudfront.net/blog/blogposts/testing-external-apis-in-ruby-with-webmock-and-vcr/webmock_vcr.png

Testen externer APIs in Ruby mit Webmock und VCR

Zuletzt aktualisiert am November 18, 2021

Lesedauer: 12 Minuten

Automatisierte Tests sind seit langem ein fester Bestandteil des Softwareentwicklungs- und -einführungsprozesses, dessen Vorteile hinlänglich bekannt sind. Unter anderem verwenden wir Tests, um die Codequalität zu gewährleisten, Regressionen zu verhindern und im Rahmen der testgetriebenen Entwicklung (TDD) als Teil des Code-Schreibprozesses.

Als Entwickler sind wir mit dem Konzept des Schreibens automatisierter Testsuiten als Teil unseres Entwicklungsworkflows vertraut und fühlen uns wohl dabei. Wenn unsere Anwendung jedoch auf externe APIs zurückgreift, ist das Schreiben dieser Tests mit besonderen Herausforderungen verbunden. Lassen Sie uns einige davon anhand eines Beispiels erkunden.

Beispiel: Versenden einer SMS mit der Vonage Messages API

Stellen Sie sich ein Szenario vor, in dem wir eine Anwendung entwickeln, die die Funktion hat, Textnachrichten per SMS zu versenden. Dies wäre ein hervorragender Anwendungsfall für die Vonage Messages API. Um diese Funktionalität mit der Messages API zu implementieren, könnte unsere Anwendung vielleicht eine MessagesClient Klasse enthalten, die eine send_sms Methode definiert. Der Zweck dieser Methode wäre das Senden einer entsprechend formatierten POST Anfrage zur Verwendung des Messages API Endpunkt:

https://api.nexmo.com/v1/messages

In Bezug auf die zum Testen erforderlichen Abhängigkeiten könnte unsere Gemfile könnte etwa so aussehen (obwohl unsere Anwendung in Wirklichkeit wahrscheinlich einige zusätzliche Abhängigkeiten enthalten würde).

Gemfile

# Gemfile

source "https://rubygems.org"
ruby "3.0.0"

gem 'faraday'

group :test do
  gem 'rspec'
end

Die Faraday gem ist eine Ruby-Bibliothek zur Verwaltung von HTTP-Anfragen und -Antworten.

Unsere Anwendungsdatei würde die MessagesClient Klasse mit ihrer send_sms Methode definieren.

app.rb

# app.rb
require "json"

class MessagesClient
  URI = 'https://api.nexmo.com/v1/messages'

  def send_sms(from_number, to_number, message)
    headers = generate_headers(message)
    body = {
      message_type: 'text',
      channel: 'sms',
      from: from_number,
      to: to_number,
      text: message
    }
    Faraday.new.post(URI, body.to_json, headers)
  end

  # additional methods omitted for brevity

end

Im obigen Beispiel definiert unsere send_sms Methode definiert Parameter für den Nachrichtentext, die An-Nummer und die Von-Nummer. Sie fügt diese Angaben in eine Nachricht body die in einer POST Anforderung an den API-Endpunkt gesendet wird, indem Faraday's post Methode gesendet wird. Die Methode send_sms Methode gibt dann ein Objekt zurück, das die von der Faraday-Instanz empfangene HTTP-Antwort darstellt.

Wie würden wir also diese Methode testen? Ein Ansatz für das Testen des glücklichen Pfades wäre, einen Test zu schreiben, der die Methode aufruft, also auf den Live-API-Endpunkt zugreift, und zu behaupten, dass wir einen Antwortcode erhalten, der Erfolg anzeigt. Im Fall der Messages API v1 wäre dies ein HTTP-Antwortcode von 202.

messages_client_spec.rb

require "spec_helper"

describe MessagesClient do
  let(:app) { MessagesClient.new }

  describe "#send_sms" do
    let(:from_number) { "447700900000" }
    let(:to_number) { "447700900001" }
    let(:message) { "Hello world!" }

    it "returns status 202 Accepted" do
      response = app.send_sms(from_number, to_number, message)

      expect(response.status).to eq 202
    end
  end
end

Es gibt einige Probleme mit diesem Testansatz.

Zunächst einmal ist jeder Test, der mit einer externen Abhängigkeit, z. B. einer Datenbank oder einer externen API, interagiert, wahrscheinlich viel langsamer als ein Test, der dies nicht tut. Wenn wir uns einen Beispieldurchlauf für diesen Test ansehen, können wir feststellen, dass er 1.63 Sekunden.

Finished in 1.63 seconds (files took 0.36557 seconds to load)
1 example, 0 failures

Dies mag zwar nicht scheinen langsam erscheinen mag, handelt es sich um einen einzigen Test für eine einzige Methode. Je nach Größe und Komplexität unserer Anwendung könnten wir zahlreiche ähnliche Tests haben. In einem solchen Szenario würde die Ausführung der gesamten Testsuite schmerzhaft langsam werden.

Zweitens: Da unser Test darauf beruht, dass eine HTTP-Anfrage über das Netz gesendet und von einer externen Abhängigkeit verarbeitet wird, können wir nicht sicher sein, dass wir eine gültige Antwort oder überhaupt eine Antwort erhalten werden. Es kann zu Netzwerkproblemen, vorübergehenden Ausfällen oder anderen externen Faktoren kommen, auf die wir keinen Einfluss haben, so dass wir möglicherweise nicht jedes Mal die erwartete Antwort erhalten jedes Mal wir unseren Test durchführen.

Es gibt viele Diskussionen über die besten Praktiken für das Schreiben von automatisierten Tests und darüber, was die verschiedenen Testtypen tun und was sie nicht tun sollten. Einige allgemein anerkannte Grundsätze sind jedoch, insbesondere bei der Arbeit mit Unit-Tests und kleinen Integrationstests, dass wir versuchen sollten, unsere Tests so zu gestalten, dass sie schnell und deterministisch.

Je weiter wir in der "Testpyramide" nach unten gehen, desto mehr Tests haben wir und desto öfter führen wir sie durch. Schnelle Tests sind daher auf dieser Ebene wichtig. Wenn wir Tests als Teil des Entwicklungsprozesses oder für ein frühes Feedback verwenden, ist es außerdem wichtig, dass unsere Tests deterministisch sind, d. h. wir wollen sicher sein, dass der Test bei einer bestimmten Eingabe eine vorher festgelegte Ausgabe erzeugt.

Die Berücksichtigung dieser Grundsätze im Rahmen unseres send_sms Test zu berücksichtigen, ergibt sich ein Problem. Wir wollen, dass unser Test schnell und deterministisch ist, aber die Probleme, die mit dem Zugriff auf den Live-API-Endpunkt verbunden sind, widersprechen diesen Prinzipien.

Es gibt noch weitere mögliche Überlegungen bei der Verwendung einer externen API, wie zum Beispiel nicht-idempotente HTTP-Methoden (zum Beispiel wird die POST Anfrage in unserer Methode wird jedes Mal, wenn wir unseren Test ausführen, eine SMS-Nachricht erstellt), oder potenzielle Probleme mit Kosten oder Tarifgrenzen. Viele dieser zusätzlichen Probleme können durch die Verwendung einer API-Sandbox gelöst werden, und die Messages API bietet eine Sandbox für einige Messaging-Kanäle.

Die Verwendung einer Sandbox ist eher für Tests auf höherer Ebene relevant, z. B. für End-to-End- oder funktionale Tests und einige größere Integrationstests, und löst nicht wirklich unsere Probleme mit Geschwindigkeit und Determinismus. Was wir für unsere Tests auf niedrigerer Ebene wirklich wollen, ist ein Weg, um die externe Abhängigkeit überhaupt nicht zu treffen. Eine Lösung für dieses Problem ist mocking.

Verspottung

Für alle, die mit dem Begriff nicht vertraut sind: Mocking ist eine Technik beim Testen, bei der eine Scheinantwort als Ersatz für eine echte Antwort von einem internen oder externen Teil unserer Anwendung verwendet wird. Auf interner Ebene könnte dies bedeuten, dass ein Mock-Objekt von einer Methode oder einem Funktionsaufruf zurückgegeben wird. Im Zusammenhang mit externen APIs bedeutet dies im Allgemeinen, dass eine echte HTTP-Antwort durch eine nachgebildete ersetzt wird.

Ein großer Vorteil dieses Ansatzes bei der Arbeit mit einer externen API ist, dass ein Test viel schneller durchgeführt werden kann, da keine Anfrage über das Netzwerk gesendet und auf die Antwort gewartet werden muss. Durch die Vordefinition der HTTP-Antwort werden unsere Tests außerdem deterministisch.

Mocking scheint ideal geeignet zu sein, um die Probleme zu beheben, die wir bei unserem derzeitigen Testaufbau festgestellt haben, wie können wir es also in unseren send_sms Test implementieren?

Einführung in Webmock

Webmock ist eine Ruby-Bibliothek zum Stubbing und Setzen von Erwartungen an HTTP-Anfragen. Sie unterstützt eine Reihe von verschiedenen HTTP-Bibliotheken und kann in verschiedene Test-Frameworks integriert werden, darunter rspec.

Als mentales Modell auf hoher Ebene leistet Webmock im Wesentlichen Folgendes:

  • Fängt alle von unserer Anwendung ausgehenden HTTP-Anfragen ab

  • gleicht diese Anfragen mit einem vorregistrierten "Stub" ab

  • Gibt eine vordefinierte Antwort für diese Anfrage zurück, anstelle der eigentlichen HTTP-Antwort

Lassen Sie uns dieses mentale Modell in Aktion erkunden, indem wir Webmock zu unserem Testaufbau hinzufügen.

Aktualisiertes Beispiel: Mocking unserer HTTP-Antworten mit Webmock

Zuerst müssen wir webmock zu unserer Gemfile und führen bundle install.

Gemfile

# Gemfile

source "https://rubygems.org"
ruby "3.0.0"

gem 'faraday'

group :test do
  gem 'rspec'
  gem 'webmock'
end

Hinweis: Wir müssen auch require 'webmock/rspec' zu unserem spec_helper.

Wir können dann unseren Test aktualisieren, um Webmock zu verwenden.

messages_client_spec.rb

require "spec_helper"

describe MessagesClient do
  let(:app) { MessagesClient.new }

  describe "#send_sms" do
    let(:from_number) { "447700900000" }
    let(:to_number) { "447700900001" }
    let(:message) { "Hello world!" }

    it "returns status 202 Accepted" do
      stub_request(:post, "https://api.nexmo.com/v1/messages").to_return(status: 202)
      response = app.send_sms(from_number, to_number, message)

      expect(response.status).to eq 202
    end
  end
end

Unser aktualisierter Test registriert eine Stummelund verwendet die Webmock stub_request Methode. Der Stub ist so eingestellt, dass er mit jeder POST Anfrage an https://api.nexmo.com/v1/messagesund gibt eine :status von 202.

Webmock fängt die ausgehende HTTP-Anfrage ab und gibt stattdessen die vorher festgelegte Antwort zurück, sodass die Durchführung unseres Tests viel schneller als zuvor:

Finished in 0.00749 seconds (files took 0.50786 seconds to load)
1 example, 0 failures

Beschränkungen des Spottes

Das Hinzufügen von Webmock hat unseren Test schnell und deterministisch gemacht, und so scheint es eine gute Lösung für unsere Testanforderungen zu sein. Die Verwendung eines Mocking-Tools zum Testen externer APIs ist jedoch mit einigen Einschränkungen verbunden.

Das Schreiben von Mocks kann zeitaufwändig sein

Unser Beispiel-Mock definiert nur den :status Code für die Antwort. Andere Mocks müssen möglicherweise auch die Antwort definieren :headers und/ oder :body. Je nach der zu testenden API könnten diese Header und der Body ziemlich groß und komplex sein und einen erheblichen Zeitaufwand für die Definition im Mock erfordern.

Skalieren Sie das für mehrere Tests gegen mehrere API-Endpunkte, und wir könnten bald eine erhebliche Zeitinvestition für das Schreiben dieser Mocks sehen.

Mocks können schwierig zu pflegen sein

Mit diesem ersten Punkt verbunden ist Wartung. Externe APIs können sich im Laufe der Zeit ändern, indem neue Funktionen hinzugefügt oder neue Versionen veröffentlicht werden. Zum Beispiel hat die Vonage Messages API kürzlich eine neue Version veröffentlicht. Wenn wir eine große Anzahl komplexer Mocks für unsere Tests haben, kann die Pflege dieser Mocks, um mit den Änderungen Schritt zu halten, eine Menge Zeit und Mühe kosten, die besser an anderer Stelle investiert werden könnte.

Mocks können falsche Annahmen über Abhängigkeiten machen

Da Mocks geschrieben werden, um darstellen eine bestimmte Antwort zu repräsentieren, anstatt eine tatsächliche Antwort darstellen sollen, basieren sie zwangsläufig auf Annahmen darüber, wie diese tatsächliche Antwort aussehen würde. Für unseren Beispieltest mag das kein Problem sein, aber wenn die Antworten, die wir nachbilden wollen, komplexer werden, steigt auch die Möglichkeit, eine falsche Annahme über diese Antwort zu treffen. Solche falschen Annahmen könnten zu Code führen, der zwar den gespielten Test besteht, aber nicht korrekt funktioniert. Hoffentlich haben wir Tests, die weiter oben in der Pyramide angesiedelt sind und solche Probleme erkennen, aber idealerweise wollen wir sie so früh wie möglich mit unseren Tests auf niedrigerer Ebene erkennen.

Um diese Einschränkungen zu beheben, können wir eine andere Ruby-Bibliothek heranziehen: VCR.

VCR

VCR ist eine Bibliothek, die das Testmuster "Aufzeichnen und Wiedergeben" im Kontext von HTTP-Anfragen und -Antworten verfolgt. Im Wesentlichen zeichnet sie die HTTP-Interaktionen einer Testsuite auf und gibt sie bei zukünftigen Testläufen wieder.

VCR setzt dieses Muster durch die Verwendung von "Kassetten" um (in Anlehnung an die Kassettenbänder des veralteten Video-Kassettenrekorder Technologie). Jede "Kassette" ist eine Datei, die Daten enthält, die eine Aufzeichnung einer bestimmten HTTP-Interaktion darstellen. Bei der ersten Ausführung eines Tests wird ein tatsächlicher HTTP-Anfrage/Antwort-Zyklus durchgeführt und die Details dieses Zyklus werden als Kassette aufgezeichnet.

Die Kassette enthält Informationen sowohl über die Anfrage als auch über die Antwort. Die Anfragedaten werden bei nachfolgenden Testläufen für den Abgleich mit der Anfrage verwendet, und die Antwortdaten werden für das Mocking der erwarteten Antwort für diese Läufe verwendet. Da die "Mocks" die tatsächlichen Antwortdaten verwenden, werden die bereits erwähnten Probleme im Zusammenhang mit dem Zeitaufwand für die Erstellung und Pflege von Mocks und den dabei getroffenen Annahmen gelöst.

Die Funktionsweise von VCR lässt sich vielleicht besser veranschaulichen, wenn wir sie im Zusammenhang mit unserem Testaufbau betrachten.

Aktualisiertes Beispiel: Integration eines Videorekorders in unseren Testaufbau

Zunächst müssen wir vcr zu unserer Gemfile und führen bundle install

Gemfile

# Gemfile

source "https://rubygems.org"
ruby "3.0.0"

gem 'faraday'

group :test do
  gem 'rspec'
  gem 'webmock'
  gem 'vcr'
end

Wir können dann unseren Test aktualisieren, um VCR zu verwenden.

messages_client_spec.rb

require "spec_helper"
require "vcr"

VCR.configure do |config|
  config.cassette_library_dir = "spec/cassettes"
  config.hook_into :webmock
  config.configure_rspec_metadata!
end

describe MessagesClient do
  let(:app) { MessagesClient.new }

  describe "#send_sms" do
    let(:from_number) { "447700900000" }
    let(:to_number) { "447700900001" }
    let(:message) { "Hello world!" }

    it "returns status 202 Accepted" do
      response = VCR.use_cassette('send_sms') do
        app.send_sms(from_number, to_number, message)
      end

      expect(response.status).to eq 202
    end
  end
end

In dieser Datei benötigen wir vcr und konfigurieren dann unseren Videorekorder (wenn wir allerdings mehrere Spec-Dateien hätten, könnten wir die Konfiguration in unsere spec_helper Datei verschieben).

  • Die cassette_library_dir Konfiguration teilt VCR mit, wo die "Kassetten" gespeichert werden sollen. Hier geben wir ein Verzeichnis an spec/cassettesWenn dieses Verzeichnis nicht existiert, wird VCR es erstellen.

  • Die hook_into Konfiguration teilt VCR mit, wie es sich in die HTTP-Anfragen einklinken soll. Da wir bereits webmock als Abhängigkeit haben, geben wir es hier an (obwohl wir es auch aus unserem Gemfile entfernen und stattdessen direkt in Faraday einhaken).

Außerdem gibt es eine Vielzahl von andere Konfigurationsoptionen zur Verfügung.

Im Test selbst haben wir die Stubbed-Anfrage von Webmock entfernt. Stattdessen setzen wir die response Variable auf den Rückgabewert der VCR use_cassette Methode, an die wir einen Kassettennamen als Argument und einen Block übergeben. Wenn die Kassette in der Standardkonfiguration für die Aufzeichnung existiert, verwendet VCR sie, um ein Antwortobjekt zu erstellen, das wir dann in unserem Test überprüfen können. Existiert die Kassette nicht, ruft VCR den Block auf und verwendet das, was er zurückgibt, um die Kassette zu erstellen.

Als wir ersten Da die Kassette zu diesem Zeitpunkt noch nicht existiert, greifen wir auf den API-Endpunkt zu, und VCR verwendet diese HTTP-Interaktion zur Erstellung einer send_sms.yml Datei, in der die Details der HTTP-Anfrage und -Antwort gespeichert werden.

send_sms.yml

---
http_interactions:
- request:
    method: post
    uri: https://api.nexmo.com/v1/messages
    body:
      encoding: UTF-8
      string: '{"message_type":"text","channel":"sms","from":"447700900000","to":"447700900001","text":"Hello
        world!"}'
    headers:
      User-Agent:
      - Faraday v1.8.0
      Authorization:
      - Basic xxxxxxxxxxxxxxxxxxxxxxxxxxx==
      Content-Type:
      - application/json
      Host:
      - api.nexmo.com
      Content-Length:
      - '12'
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
      Accept:
      - "*/*"
  response:
    status:
      code: 202
      message: Accepted
    headers:
      Server:
      - nginx
      Date:
      - Thu, 18 Nov 2021 12:35:49 GMT
      Content-Type:
      - application/json
      Content-Length:
      - '55'
    body:
      encoding: UTF-8
      string: '{"message_uuid":"c26213be-2916-4c64-903e-4125158eedd8"}'
  recorded_at: Thu, 18 Nov 2021 12:35:49 GMT
recorded_with: VCR 6.0.0

Wenn der Test zum ersten Mal ausgeführt wird, da wir auf den API-Endpunkt zugreifen, ist die Ausführung genauso langsam, wie wenn wir Webmock oder VCR gar nicht verwenden würden.

Finished in 1.61 seconds (files took 0.58899 seconds to load)
1 example, 0 failures

Bei allen nachfolgenden Testläufen wird VCR die Kassette verwenden. Zunächst wird geprüft, ob die Anfragedaten für die Testdurchführung mit denen auf der Kassette übereinstimmen. Wenn sie übereinstimmen übereinstimmen, werden die Antwortdetails aus der Kassette verwendet, um ein Antwortobjekt zu erstellen. In diesem Fall lautet der aufgezeichnete Antwortcode 202 und wird daher als status für unser Antwortobjekt gesetzt. Da unser Test behauptet, dass response.status gleich sein sollte 202ist, besteht unser Test.

Auch diese nachfolgenden Testläufe sind viel schneller als der erste.

Finished in 0.01033 seconds (files took 0.55884 seconds to load)
1 example, 0 failures

Tipps und Tricks zum Videorekorder

VCR bietet eine große Flexibilität in Bezug auf die Konfigurationsmöglichkeiten.

Konfigurieren Sie den Abgleich von Anfragen

Um eine Aufnahme wiederzugeben, muss VCR neue HTTP-Anfragen mit den Details einer zuvor aufgezeichneten Aufnahme abgleichen. Diese Abgleich kann anhand verschiedener Elemente einer Anfrage durchgeführt werden.

Die Standardkonfiguration sieht einen Abgleich mit der HTTP-Methode und der URI vor. Diese Konfiguration kann jedoch so geändert werden, dass Host und Pfad (statt der vollständigen URI), Abfrageparameter, Anforderungsheader und Anforderungsrumpf separat abgeglichen werden.

Außerdem können benutzerdefinierte Abgleiche erstellt werden, um noch mehr Flexibilität zu bieten.

Neu aufnehmen, nicht ausblenden

Wie bereits erwähnt, können sich externe APIs im Laufe der Zeit ändern oder neue Versionen herausbringen, was bedeutet, dass die Aufzeichnungen veraltet sein können. Wenn dies geschieht, müssen wir nicht ein ganzes Mock neu schreiben (wie es bei einem Standard-Mocking-Setup der Fall wäre), sondern können eine neue HTTP-Interaktion aufzeichnen, um die veraltete zu ersetzen. Es gibt mehrere Möglichkeiten, die Neuaufzeichnung anzugehen:

  • Am einfachsten ist es, die Datei der aktuellen Aufzeichnung zu löschen. Wenn für einen bestimmten Test keine Aufzeichnung existiert, zeichnet VCR automatisch eine neue auf.

  • Außerdem gibt es verschiedene Aufnahmemodi die eingestellt werden können, um zu bestimmen, wann neue Aufnahmen gemacht werden. Zum Beispiel, :once (die Standardeinstellung) nur neue Interaktionen auf, wenn keine Kassettendatei vorhanden ist, während :new_episodes eine neue Interaktion aufzeichnet, wenn eine Datei für einen Test vorhanden ist, die Anfragedetails für diesen Test aber nicht genau mit den in der Datei aufgezeichneten übereinstimmen.

  • Wir können die automatische Wiederaufnahme aktivieren, um die Interaktionen in regelmäßigen Abständen erneut aufzuzeichnen. Wir können die Option :re_record_interval Option im Setup für eine bestimmte Kassette einstellen. Wenn diese Kassette dann benutzt wird, vergleicht VCR den recorded_at Zeitstempel auf der Kassette mit der aktuellen Zeit. Wenn mehr Zeit verstrichen ist als in der Option :re_record_intervalangegeben ist, wird die Interaktion neu aufgezeichnet.

Vorsicht bei sensiblen Daten

Wenn wir mit einer externen API interagieren, kann es je nach Authentifizierungsmethode für diese API durchaus vorkommen, dass wir sensible Daten wie API-Schlüssel als Teil unserer Anfragen einschließen, z. B. in einer Autorisierung Header. Diese Daten werden in der VCR-Aufzeichnung als Teil der Interaktion vorhanden sein. Wenn wir unseren Projektcode auch öffentlich zugänglich machen, indem wir ihn beispielsweise in ein öffentliches Repository auf GitHub stellen, kann dies ein Problem darstellen.

Eine Lösung könnte darin bestehen, einzelne Aufnahmen oder unser gesamtes cassettes Verzeichnis, in eine .gitignore Datei zu speichern. Alternativ dazu können wir die VCR filter_sensitive_data Konfigurationsoption verwenden, um eine Ersatzzeichenfolge für bestimmte Daten anzugeben, die in der Aufzeichnung anstelle der eigentlichen Daten angezeigt wird.

Verwenden Sie die Dokumentation

VCR bietet einige detaillierte Dokumentation zur Verwendung für diese und viele andere Konfigurationsoptionen, sowie eine API-Dokumentation für die Bibliothek selbst.

Alternative Werkzeuge

Die hier vorgestellten Werkzeuge sind im Ruby-Ökosystem gut etabliert, aber es gibt auch viele Alternativen, sowohl für Rubyisten als auch für Nicht-Rubyisten.

Was die Mocking-Fähigkeiten betrifft, so ist die rspec-mocks Bibliothek kann Mocking-Fähigkeiten bereitstellen für rspec. Einige HTTP-Bibliotheken wie Faraday bieten Adapter, mit denen Sie Stubbed-Requests definieren können. Die Faraday-Mocking-Funktionalität (neben anderen) ist auch mit VCR kompatibel.

Außerdem gibt es viele Portierungen von VCR in andere Programmiersprachen, wie z. B. vcrpy für Python, php-vcr für PHP und scotch und Betamax.Net für .net/ C#. Nock bietet ähnliche Funktionalität wie die Kombination von VCR und Webmock für Node.

Es gibt viele weitere Ports zu anderen Sprachen, die in der VCR READMEaufgeführt sind, so dass es für jede Sprache, die Sie verwenden, hoffentlich ein Aufzeichnungs- und Wiedergabewerkzeug geben sollte.

Viel Spaß beim Testen!

Teilen Sie:

https://a.storyblok.com/f/270183/373x376/e8d3211236/karl-lingiah.png
Karl LingiahRuby-Entwickler Advocate

Karl ist Developer Advocate bei Vonage und kümmert sich um die Wartung unserer Ruby Server SDKs und die Verbesserung der Entwicklererfahrung für unsere Community. Er liebt es zu lernen, Dinge zu entwickeln, Wissen zu teilen und alles, was allgemein mit Webtechnologie zu tun hat.