
Teilen Sie:
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.
Testen externer APIs in Ruby mit Webmock und VCR
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'
endDie 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
endIm 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
endEs 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 failuresDies 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'
endHinweis: 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
endUnser 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'
endWir 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
endIn 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_dirKonfiguration teilt VCR mit, wo die "Kassetten" gespeichert werden sollen. Hier geben wir ein Verzeichnis anspec/cassettesWenn dieses Verzeichnis nicht existiert, wird VCR es erstellen.Die
hook_intoKonfiguration teilt VCR mit, wie es sich in die HTTP-Anfragen einklinken soll. Da wir bereitswebmockals Abhängigkeit haben, geben wir es hier an (obwohl wir es auch aus unseremGemfileentfernen 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.0Wenn 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 failuresBei 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_episodeseine 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_intervalOption im Setup für eine bestimmte Kassette einstellen. Wenn diese Kassette dann benutzt wird, vergleicht VCR denrecorded_atZeitstempel 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:
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.