API-Wrapper mit TDD in Ruby schreiben

Früher oder später müssen alle Entwickler mit einer API interagieren. Der schwierigste Teil bezieht sich immer auf das zuverlässige Testen des Codes, den wir schreiben. Da wir sicherstellen möchten, dass alles ordnungsgemäß funktioniert, führen wir ständig Code aus, der die API selbst abfragt. Dieser Prozess ist langsam und ineffizient, da Netzwerkprobleme und Dateninkonsistenzen auftreten können (die API-Ergebnisse können sich ändern). Sehen wir uns an, wie wir all diese Anstrengungen mit Ruby vermeiden können.


Unser Ziel

"Der Fluss ist wichtig: Schreiben Sie die Tests, führen Sie sie aus und sehen Sie, wie sie fehlschlagen, und schreiben Sie dann den minimalen Implementierungscode, damit sie bestanden werden. Wenn alle dies tun, können Sie bei Bedarf umgestalten."

Unser Ziel ist einfach: Schreiben Sie einen kleinen Wrapper um die Dribbble-API, um Informationen über einen Benutzer abzurufen (in der Dribbble-Welt "Player" genannt)..
Da wir Ruby verwenden werden, folgen wir auch einem TDD-Ansatz: Wenn Sie mit dieser Technik nicht vertraut sind, hat Nettuts + eine gute Grundierung auf RSpec, die Sie lesen können. Kurz gesagt, werden wir Tests schreiben, bevor wir unsere Codeimplementierung schreiben. Dadurch wird es einfacher, Fehler zu finden und eine hohe Codequalität zu erreichen. Der Fluss ist wichtig: Schreiben Sie die Tests, führen Sie sie aus und sehen Sie, wie sie fehlschlagen, und schreiben Sie dann den minimalen Implementierungscode, damit sie erfolgreich sind. Sobald dies der Fall ist, überarbeiten Sie gegebenenfalls.

Die API

Die Dribbble-API ist ziemlich unkompliziert. Zu diesem Zeitpunkt unterstützt es nur GET-Anforderungen und erfordert keine Authentifizierung: ein idealer Kandidat für unser Tutorial. Darüber hinaus gibt es ein Limit von 60 Aufrufen pro Minute. Eine Einschränkung, die perfekt zeigt, warum die Arbeit mit APIs einen intelligenten Ansatz erfordert.


Schlüssel Konzepte

In diesem Lernprogramm muss davon ausgegangen werden, dass Sie mit Testkonzepten vertraut sind: Fixtures, Mocks, Erwartungen. Testen ist ein wichtiges Thema (vor allem in der Ruby-Community). Selbst wenn Sie kein Rubyist sind, würde ich Sie dazu ermutigen, tiefer in die Angelegenheit einzutauchen und nach gleichwertigen Tools für Ihre Alltagssprache zu suchen. Vielleicht möchten Sie „The RSpec book“ von David Chelimsky et al. Lesen, eine hervorragende Einführung in die Behavior Driven Development.

Hier sind drei Schlüsselbegriffe, die Sie kennen müssen:

  • Spotten: auch Double genannt, ist ein Mock "ein Objekt, das in einem Beispiel für ein anderes Objekt steht". Das heißt, wenn wir die Interaktion zwischen einem Objekt und einem anderen testen möchten, können wir das zweite Objekt verspotten. In diesem Lernprogramm werden wir die Dribbble-API überlisten. Zum Testen unseres Codes benötigen wir nicht die API selbst, sondern etwas, das sich wie es verhält und die gleiche Schnittstelle verfügbar macht.
  • Fixture: Eine Datenmenge, die einen bestimmten Zustand im System wiederherstellt. Ein Fixture kann verwendet werden, um die benötigten Daten zu erstellen, um eine Logik zu testen.
  • Erwartung: Ein Testbeispiel schrieb das aus der Sicht des Ergebnisses, das wir erzielen wollen.

Unsere Werkzeuge

"Führen Sie Tests in der Regel jedes Mal durch, wenn Sie sie aktualisieren."

WebMock ist eine Ruby-Verspottungsbibliothek, die zum Verspotten (oder Stub) von HTTP-Anforderungen verwendet wird. Mit anderen Worten, Sie können damit jede HTTP-Anfrage simulieren, ohne eine zu erstellen. Der Hauptvorteil besteht darin, in der Lage zu sein, einen beliebigen HTTP-Dienst zu entwickeln und zu testen, ohne den Dienst selbst zu benötigen und ohne sich auf verwandte Probleme (wie API-Beschränkungen, IP-Einschränkungen usw.) zu beziehen..
VCR ist ein ergänzendes Tool, das alle realen http-Anforderungen aufzeichnet und ein Gerät erstellt, eine Datei, die alle erforderlichen Daten enthält, um diese Anforderung zu replizieren, ohne sie erneut auszuführen. Wir konfigurieren es für die Verwendung von WebMock. Mit anderen Worten, unsere Tests werden nur einmal mit der echten Dribbble-API interagieren. Danach werden alle Anforderungen von WebMock dank der vom VCR aufgezeichneten Daten angehalten. Wir werden eine perfekte Kopie der lokal aufgezeichneten Dribbble-API-Antworten haben. Darüber hinaus ermöglicht uns WebMock, Kantenfälle (wie das Zeitlimit der Anforderung) einfach und konsistent zu testen. Eine wunderbare Konsequenz unseres Setups ist, dass alles extrem schnell sein wird.

Bei den Unit-Tests verwenden wir Minitest. Es ist eine schnelle und einfache Komponententestbibliothek, die auch die Erwartungen in der RSpec-Art erfüllt. Es bietet einen kleineren Funktionsumfang, aber ich finde, dass dies Sie tatsächlich dazu ermutigt und dazu drängt, Ihre Logik in kleine, testbare Methoden zu unterteilen. Minitest ist Teil von Ruby 1.9. Wenn Sie es verwenden (hoffe ich), müssen Sie es nicht installieren. Bei Ruby 1.8 ist es nur eine Frage von gem installieren minitest.

Ich werde Ruby 1.9.3 verwenden: Wenn Sie dies nicht tun, werden Sie wahrscheinlich auf einige Probleme stoßen required_relative, aber ich habe in einen Kommentar Fallback-Code direkt darunter eingefügt. In der Regel sollten Sie jedes Mal, wenn Sie ein Update durchführen, Tests durchführen, auch wenn ich diesen Schritt nicht explizit im gesamten Tutorial erwähne.


Konfiguration

Wir werden das konventionelle verwenden / lib und / spez Ordnerstruktur, um unseren Code zu organisieren. Den Namen unserer Bibliothek nennen wir es Gericht, Befolgung der Dribbble-Konvention der Verwendung von Basketball-verwandten Begriffen.

Das Gemfile enthält alle unsere Abhängigkeiten, auch wenn diese recht klein sind.

 Quelle: rubygems gem 'httparty' Gruppe: test do gem 'webmock' gem 'vcr' gem 'wiederum' gem 'Rechen' Ende

Httparty ist ein einfach zu verwendender Edelstein, um HTTP-Anfragen zu bearbeiten. Es wird der Kern unserer Bibliothek sein. In der Testgruppe fügen wir auch Turn hinzu, um die Ausgabe unserer Tests zu ändern, um beschreibender zu sein und die Farbe zu unterstützen.

Das / lib und / spez Ordner haben eine symmetrische Struktur: für jede im / lib / dish Ordner sollte sich eine Datei darin befinden / spec / Gericht mit demselben Namen und dem Suffix '_spec'.

Beginnen wir mit dem Erstellen eines /lib/dish.rb Datei und fügen Sie den folgenden Code hinzu:

 fordern Sie "httparty" an [File.dirname (__ FILE__) + '/dish/*.rb'o.each.out | file | Dateiendung erforderlich

Es macht nicht viel: Es erfordert 'httparty' und iteriert dann über jeden .rb Datei innen / lib / dish um es zu verlangen Wenn diese Datei vorhanden ist, können wir alle Funktionen in separaten Dateien in hinzufügen / lib / dish und laden Sie es automatisch, indem Sie diese einzelne Datei anfordern.

Gehen wir zum / spez Mappe. Hier ist der Inhalt des spec_helper.rb Datei.

 #Wir benötigen die eigentliche Bibliotheksdatei "request_relative" ... / lib / dish "# Für Ruby < 1.9.3, use this instead of require_relative # require(File.expand_path('… /… /lib/dish', __FILE__)) #dependencies require 'minitest/autorun' require 'webmock/minitest' require 'vcr' require 'turn' Turn.config do |c| # :outline - turn's original case/test outline mode [default] c.format = :outline # turn on invoke/execute tracing, enable full backtrace c.trace = true # use humanized test names (works only with :outline format) c.natural = true end #VCR config VCR.config do |c| c.cassette_library_dir = 'spec/fixtures/dish_cassettes' c.stub_with :webmock end

Hier gibt es einige Dinge, die es zu beachten gilt, also lassen wir es Stück für Stück aufbrechen:

  • Zunächst benötigen wir die lib-Hauptdatei für unsere App, um den Code, den wir testen möchten, für die Testsuite verfügbar zu machen. Das required_relative Anweisung ist eine Ergänzung von Ruby 1.9.3.
  • Wir benötigen dann alle Bibliotheksabhängigkeiten: Minitest / Autorun enthält alle Erwartungen, die wir verwenden werden, webmock / minitest fügt die erforderlichen Bindungen zwischen den beiden Bibliotheken hinzu, während vcr und Wende sind ziemlich selbsterklärend.
  • Der Turn-Konfigurationsblock muss lediglich unsere Testausgabe optimieren. Wir werden das Gliederungsformat verwenden, wo wir die Beschreibung unserer Spezifikationen sehen können.
  • Die VCR-Konfigurationsblöcke weisen VCR an, die Anforderungen in einem Geräteordner zu speichern (beachten Sie den relativen Pfad) und WebMock als Stubbing-Bibliothek zu verwenden (VCR unterstützt einige andere)..

Zu guter Letzt die Rakefile das enthält einige Support-Code:

 erfordern 'rake / testtask' Rake :: TestTask.new do | t | t.test_files = Dateiliste ['spec / lib / dish / * _ spec.rb'] t.verbose = true Endaufgabe: default =>: test

Das Rechen / Testaufgabe Bibliothek enthält eine TestTask Klasse, die nützlich ist, um den Speicherort unserer Testdateien festzulegen. Von nun an werden wir nur noch tippen, um unsere Spezifikationen auszuführen Rechen aus dem Bibliotheksstammverzeichnis.

Um unsere Konfiguration zu testen, fügen Sie den folgenden Code hinzu /lib/dish/player.rb:

 Modul Dish-Klasse Spielerende

Dann /spec/lib/dish/player_spec.rb:

 required_relative '… /… / spec_helper' # Für Ruby < 1.9.3, use this instead of require_relative # require (File.expand_path('./… /… /… /spec_helper', __FILE__)) describe Dish::Player do it "must work" do "Yay!".must_be_instance_of String end end

Laufen Rechen sollte einen Test bestanden haben und keine Fehler. Dieser Test ist für unser Projekt keineswegs nützlich, stellt aber implizit sicher, dass unsere Bibliotheksdateistruktur vorhanden ist (die beschreiben Block würde einen Fehler werfen, wenn die Gericht :: Spieler Modul wurde nicht geladen).


Erste Spezifikationen

Um richtig zu funktionieren, benötigt Dish die Httparty-Module und die korrekten base_uri, die Basis-URL der Dribbble-API. Schreiben wir die relevanten Tests für diese Anforderungen in player_spec.rb:

… Beschreiben Dish :: Player Beschreibe "Standardattribute" do it "muss httparty-Methoden enthalten" do Dish :: Player.must_include HTTParty end it "Die Basis-URL muss auf den Dribble-API-Endpunkt" do Dish :: Player.base_uri "gesetzt sein .must_equal 'http://api.dribbble.com' end end end

Wie Sie sehen, sind die Erwartungen von Minitest selbsterklärend, insbesondere wenn Sie ein RSpec-Benutzer sind..

Beim Ausführen dieser Tests werden ein Fehler und ein Fehler angezeigt. Damit sie bestanden werden können, fügen wir unsere ersten Zeilen des Implementierungscodes hinzu player.rb:

 Modul Dish-Klasse Der Player umfasst HTTParty base_uri 'http://api.dribbble.com' end end

Laufen Rechen sollte wieder die beiden Spezifikationen zeigen. Jetzt unser Spieler Klasse hat Zugriff auf alle Httparty-Klassenmethoden, wie z erhalten oder Post.


Aufnahme unserer ersten Anfrage

Da werden wir an dem arbeiten Spieler Klasse müssen wir API-Daten für einen Spieler haben. Auf der Dribbble-API-Dokumentationsseite wird angezeigt, dass der Endpunkt zum Abrufen von Daten zu einem bestimmten Player vorhanden ist http://api.dribbble.com/players/:id

Wie in typischer Rails-Mode, :Ich würde ist entweder die Ich würde oder der Nutzername eines bestimmten Spielers. Wir werden verwenden einfache bits, der Benutzername von Dan Cederholm, einem der Gründer von Dribbble.

Um die Anfrage mit dem Videorecorder aufzuzeichnen, aktualisieren wir unser player_spec.rb Datei, indem Sie Folgendes hinzufügen beschreiben Blockieren Sie die Spezifikation direkt nach der ersten:

… Beschreiben "GET-Profil", bevor VCR.insert_cassette 'player' ausgeführt wird: record =>: new_episodes endet nach VCR.eject_cassette endet, wenn es "das Gerät aufnimmt" do Dish :: Player.get Ende Ende Ende

Nach dem Rennen Rechen, Sie können überprüfen, ob das Gerät erstellt wurde. Ab sofort sind alle unsere Tests vollständig netzwerkunabhängig.

Das Vor block wird verwendet, um einen bestimmten Teil des Codes vor jeder Erwartung auszuführen: Wir fügen damit das VCR-Makro hinzu, mit dem ein Fixture aufgenommen wird, das wir als "Player" bezeichnen. Dadurch wird ein erstellt player.yml Datei unter spec / fixtures / geschirrkassetten. Das :Aufzeichnung Die Option ist so eingestellt, dass alle neuen Anforderungen einmalig aufgezeichnet und bei jeder nachfolgenden identischen Anforderung wiedergegeben werden. Als Proof of Concept können wir eine Spezifikation hinzufügen, deren einziges Ziel es ist, ein Fixture für das Simplebits-Profil aufzunehmen. Das nach dem Die Direktive weist den Videorecorder an, die Kassette nach den Tests zu entfernen, um sicherzustellen, dass alles ordnungsgemäß isoliert ist. Das erhalten Methode auf der Spieler Klasse wird verfügbar gemacht, dank der Einbeziehung der Httparty Modul.

Nach dem Rennen Rechen, Sie können überprüfen, ob das Gerät erstellt wurde. Ab sofort sind alle unsere Tests vollständig netzwerkunabhängig.


Spielerprofil abrufen

Jeder Dribbble-Benutzer verfügt über ein Profil, das eine recht umfangreiche Datenmenge enthält. Lassen Sie uns darüber nachdenken, wie wir möchten, dass unsere Bibliothek tatsächlich verwendet wird: Dies ist eine nützliche Methode, um unser DSL-System auszubauen. Folgendes wollen wir erreichen:

 simplebits = Dish :: Player.new ('simplebits') simplebits.profile => Gibt einen Hash mit allen Daten aus der API zurück Simplebits.username => 'simplebits' simplebits.id => 1 simplebits.shots_count => 157

Einfach und effektiv: Wir möchten einen Player mithilfe seines Benutzernamens instanziieren und dann auf seine Daten zugreifen, indem Sie Methoden in der Instanz aufrufen, die den von der API zurückgegebenen Attributen zugeordnet sind. Wir müssen mit der API selbst konsistent sein.

Lassen Sie uns jeweils eine Sache in Angriff nehmen und einige Tests schreiben, die sich auf das Abrufen der Spielerdaten aus der API beziehen. Wir können unsere ändern "GET-Profil" block zu haben:

 Beschreibe "GET-Profil" do (: player) Dish :: Player.new, bevor VCR.insert_cassette 'player':: record =>: new_episodes endet, nachdem VCR.eject_cassette beendet ist, muss es eine Profilmethode "tun" player.must_respond_to: profile end it "muss die Antwort der API von JSON auf Hash parsen" do player.profile.must_be_instance_of Hash end it "muss die Anforderung ausführen und die Daten" do player.profile ["username"]. must_equal 'simplebits abrufen ende ende

Das Lassen Direktive an der Spitze erstellt ein Gericht :: Spieler Instanz in den Erwartungen verfügbar. Als Nächstes möchten wir sicherstellen, dass unser Player über eine Profilmethode verfügt, deren Wert ein Hash ist, der die Daten aus der API darstellt. Als letzten Schritt testen wir einen Beispielschlüssel (den Benutzernamen), um sicherzustellen, dass die Anforderung tatsächlich ausgeführt wird.

Beachten Sie, dass wir noch nicht festlegen, wie der Benutzername festgelegt werden soll, da dies ein weiterer Schritt ist. Die minimale erforderliche Implementierung ist folgende:

… Klasse Spieler enthalten HTTParty base_uri 'http://api.dribbble.com' def Profil self.class.get '/ players / simplebits' end end… 

Eine sehr kleine Menge Code: Wir packen gerade einen Get-Anruf in der Profil Methode. Dann übergeben wir den fest codierten Pfad, um die Daten von Simplebits abzurufen, die wir dank VCR bereits gespeichert hatten.

Alle unsere Tests sollten bestanden sein.


Einstellen des Benutzernamens

Nun, da wir eine funktionierende Profilfunktion haben, können wir uns um den Benutzernamen kümmern. Hier sind die relevanten Spezifikationen:

 Beschreibe "Standardinstanzattribute" do let (: player) Dish :: Player.new ('simplebits') es muss ein id-Attribut haben "do player.must_respond_to: username end es muss die richtige id haben" do player .username.must_equal 'simplebits' end Ende beschreiben "GET-Profil" do (: player) Dish :: Player.new ('simplebits'), bevor VCR.insert_cassette 'base':: record =>: new_episodes danach endet do VCR.eject_cassette end it "muss eine Profilmethode haben" do player.must_respond_to: profile end it "muss die API-Antwort von JSON auf Hash parsen" do player.profile.must_be_instance_of Hash end es muss das richtige Profil "do player" .profile ["Benutzername"]. must_equal "simplebits" end end

Wir haben einen neuen Beschreibungsblock hinzugefügt, um den hinzugefügten Benutzernamen zu überprüfen und den Spieler Initialisierung in der GET-Profil blockieren, um die DSL zu reflektieren, die wir haben wollen. Wenn Sie die Spezifikationen jetzt ausführen, werden viele Fehler auftauchen Spieler Klasse akzeptiert keine Argumente bei der Initialisierung (vorerst).

Die Umsetzung ist sehr unkompliziert:

… Class Player attr_accessor: username include HTTParty base_uri 'http://api.dribbble.com' def initialize (Benutzername) self.username = Benutzername end def Profil self.class.get "/players/#self.username" end Ende… 

Die Initialisierungsmethode erhält einen Benutzernamen, der dank der Funktion in der Klasse gespeichert wird attr_accessor oben hinzugefügte Methode. Wir ändern dann die Profilmethode, um das Benutzername-Attribut zu interpolieren.

Wir sollten alle Tests erneut bestehen lassen.


Dynamische Attribute

Grundsätzlich ist unsere Bibliothek ziemlich gut in Form. Da das Profil ein Hash ist, können Sie hier aufhören und es bereits verwenden, indem Sie den Schlüssel des Attributs übergeben, für das Sie den Wert erhalten möchten. Unser Ziel ist es jedoch, ein einfach zu bedienendes DSL zu schaffen, das für jedes Attribut eine Methode hat.

Denken wir darüber nach, was wir erreichen müssen. Nehmen wir an, wir haben eine Spielerinstanz und stubben, wie es funktionieren würde:

 player.username => 'simplebits' player.shots_count => 157 player.foo_attribute => NoMethodError

Lassen Sie uns dies in Spezifikationen übersetzen und sie dem hinzufügen GET-Profil Block:

… Beschreibe "dynamische Attribute" do do do.playe end it "muss den Attributwert zurückgeben, wenn er im Profil vorhanden ist" do player.id.must_equal 1 end it "muss die Methode erhöhen, wenn das Attribut nicht vorhanden ist" do lambda player. foo_attribute .must_raise NoMethodError end end… 

Wir haben bereits eine Spezifikation für den Benutzernamen, so dass wir keine weitere hinzufügen müssen. Beachten Sie einige Dinge:

  • wir rufen ausdrücklich an Spielerprofil in einem Vorher-Block, andernfalls wird es Null sein, wenn wir versuchen, den Attributwert zu erhalten.
  • um das zu testen foo_attribute Wenn eine Ausnahme auftritt, müssen wir sie in ein Lambda einwickeln und überprüfen, ob der erwartete Fehler auftritt.
  • das testen wir Ich würde gleich 1, da wir wissen, dass dies der erwartete Wert ist (dies ist ein rein datenabhängiger Test).

In Bezug auf die Implementierung könnten wir eine Reihe von Methoden definieren, um auf das System zuzugreifen Profil hash, aber dies würde eine Menge duplizierter Logik erzeugen. Darüber hinaus würde sich das API-Ergebnis darauf verlassen, immer die gleichen Schlüssel zu haben.

"Wir werden uns darauf verlassen method_missing um mit diesen Fällen umzugehen und all diese Methoden "zu erzeugen". "

Stattdessen werden wir uns darauf verlassen method_missing um mit diesen Fällen umzugehen und all diese Methoden "zu erzeugen". Aber was heißt das? Ohne zu viel Metaprogrammierung zu unternehmen, können wir einfach sagen, dass jedes Mal, wenn wir eine Methode aufrufen, die auf dem Objekt nicht vorhanden ist, Ruby eine a aufwirft NoMethodError durch die Nutzung method_missing. Durch die Neudefinition dieser Methode innerhalb einer Klasse können wir ihr Verhalten ändern.

In unserem Fall werden wir die abfangen method_missing Aufruf, überprüfen Sie, ob der aufgerufene Methodenname ein Schlüssel im Profilhash ist, und geben Sie im Falle eines positiven Ergebnisses den Hashwert für diesen Schlüssel zurück. Wenn nicht, rufen wir an Super einen Standard anheben NoMethodError: Dies ist erforderlich, um sicherzustellen, dass sich unsere Bibliothek genau wie jede andere Bibliothek verhält. Mit anderen Worten, wir möchten die geringste Überraschung garantieren.

Fügen wir dem folgenden Code hinzu Spieler Klasse:

 def method_missing (name, * args & & block), wenn profile.has_key? (name.to_s) Profil [name.to_s] oder sonstiges Ende ist

Der Code macht genau das, was oben beschrieben wurde. Wenn Sie jetzt die Spezifikationen ausführen, sollten Sie sie alle bestehen lassen. Ich würde Sie ermuntern, den spec-Dateien weitere Attribute hinzuzufügen, z. B. shots_count.

Diese Implementierung ist jedoch kein wirklich idiomatischer Ruby. Es funktioniert, aber es kann zu einem ternären Operator rationalisiert werden, einer kondensierten Form einer if-else-Bedingung. Es kann umgeschrieben werden als:

 def method_missing (name, * args, & block) profile.has_key? (name.to_s)? Profil [name.to_s]: Super End

Es ist nicht nur eine Frage der Länge, sondern auch der Konsistenz und der gemeinsamen Konventionen zwischen Entwicklern. Durchsuchen des Quellcodes von Ruby-Edelsteinen und -Bibliotheken ist eine gute Möglichkeit, sich an diese Konventionen zu gewöhnen.


Caching

Als letzten Schritt möchten wir sicherstellen, dass unsere Bibliothek effizient ist. Es sollte nicht mehr Anforderungen als benötigt stellen und möglicherweise Daten intern zwischenspeichern. Lassen Sie uns noch einmal darüber nachdenken, wie wir es verwenden könnten:

 player.profile => führt die Anforderung aus und gibt einen Hash zurück. player.profile => gibt denselben Hash zurück. player.profile (true) => erzwingt das erneute Laden der http-Anfrage und gibt dann den Hash zurück (falls erforderlich, mit Datenänderungen).

Wie können wir das testen? Mit WebMock können Sie Netzwerkverbindungen zum API-Endpunkt aktivieren und deaktivieren. Selbst wenn VCR-Geräte verwendet werden, kann WebMock ein Netzwerk-Timeout oder eine andere Antwort auf den Server simulieren. In unserem Fall können wir die Zwischenspeicherung testen, indem wir das Profil einmal abrufen und anschließend das Netzwerk deaktivieren. Durch anrufen Spielerprofil wieder sollten wir die gleichen Daten sehen, indem wir anrufen Spielerprofil (wahr) wir sollten eine bekommen Timeout :: Fehler, da die Bibliothek versuchen würde, eine Verbindung zum (deaktivierten) API-Endpunkt herzustellen.

Fügen wir einen weiteren Block hinzu player_spec.rb Datei gleich danach Erzeugung dynamischer Attribute:

 Beschreibe "caching" do # wir verwenden Webmock, um die Netzwerkverbindung nach dem # Abrufen des Profils zu deaktivieren, bevor do.... Profile stub_request (: any, /api.dribbble.com/).to_timeout ende "das Profil muss zwischenspeichern". profile.must_be_instance_of Hash end muss "das Profil aktualisieren, wenn erzwungen wird" do lambda player.profile (true) .must_raise Timeout :: Fehler end end

Das stub_request Diese Methode fängt alle Aufrufe des API-Endpunkts ab und simuliert ein Timeout, wodurch das erwartete erhöht wird Timeout :: Fehler. Wie zuvor testen wir das Vorhandensein dieses Fehlers in einem Lambda.

Die Implementierung kann schwierig sein, daher teilen wir sie in zwei Schritte auf. Zuerst verschieben wir die eigentliche http-Anfrage in eine private Methode:

… Def Profil get_profile end… private def get_profile self.class.get ("/ players / # self.username") end ... 

Dies wird unsere Spezifikationen nicht vergehen lassen, da wir das Ergebnis nicht im Cache speichern get_profile. Um dies zu tun, ändern wir das Profil Methode:

… Def profile @profile || = get_profile end… 

Wir speichern den Ergebnis-Hash in einer Instanzvariablen. Beachten Sie auch die || = Betreiber, dessen Anwesenheit das sicherstellt get_profile wird nur ausgeführt, wenn @profile einen falschen Wert (wie Null).

Als nächstes können wir die Direktladeanweisung hinzufügen:

… Def Profil (force = false) force? @profile = get_profile: @profile || = get_profile end… 

Wir benutzen wieder ein Ternär: Wenn Macht ist falsch, wir führen durch get_profile und zwischenspeichern, andernfalls verwenden wir die in der vorherigen Version dieser Methode geschriebene Logik (d. h. die Anforderung wird nur ausgeführt, wenn noch kein Hash vorhanden ist)..

Unsere Angaben sollten jetzt grün sein und damit ist auch unser Tutorial beendet.


Einpacken

In diesem Tutorial wollten wir eine kleine und effiziente Bibliothek schreiben, um mit der Dribbble-API zu interagieren. Wir haben den Grundstein dafür gelegt. Die meiste Logik, die wir geschrieben haben, kann abstrahiert und wiederverwendet werden, um auf alle anderen Endpunkte zuzugreifen. Minitest, WebMock und VCR haben sich als wertvolle Werkzeuge für die Gestaltung unseres Codes erwiesen.

.