Meine jüngste Arbeit war ein Cloud-basiertes Ruby-Projekt für die BBC News, die 2014 anstehen. Es erfordert schnelle E / A, Skalierbarkeit und muss gut getestet werden. Die "gut getestet" -Anforderung ist das, worauf ich mich in diesem Tutorial konzentrieren möchte.
Dieses Projekt nutzt einige verschiedene Amazon-Dienste wie:
Wir müssen in der Lage sein, Tests zu schreiben, die schnell sind und uns umgehend Feedback zu Problemen mit unserem Code geben.
Obwohl wir in diesem Lernprogramm keine Amazon-Dienste verwenden werden, erwähne ich sie, weil wir für diese Tests schnelle Tests benötigen, um diese externen Objekte zu fälschen (z. B. sollten wir keine Netzwerkverbindung benötigen, um unsere Tests, da diese Abhängigkeit zu langsamen Tests führen kann.
Neben dem technischen Leiter Robert Kenny (der sich mit TDD-basierten Ruby-Anwendungen sehr gut auskennt), haben wir verschiedene Tools verwendet, die diesen Prozess und unsere Programmierarbeit wesentlich vereinfacht haben.
Ich möchte einige Informationen zu diesen Tools mit Ihnen teilen.
Die Werkzeuge, die ich behandeln werde, sind:
Ich gehe davon aus, dass Sie mit Ruby-Code und dem Ruby-Ökosystem vertraut sind. Zum Beispiel sollte ich Ihnen nicht erklären müssen, was "Edelsteine" sind oder wie bestimmte Ruby-Syntax / -Konzepte funktionieren.
Wenn Sie sich nicht sicher sind, würde ich Ihnen empfehlen, bevor Sie weiterziehen, einen der anderen Ruby-Posts zu lesen, um sich auf den neuesten Stand zu bringen.
Sie sind möglicherweise nicht mit Guard vertraut, aber im Wesentlichen handelt es sich dabei um ein Befehlszeilentool, das Ruby für die Verarbeitung verschiedener Ereignisse verwendet.
Guard kann Sie beispielsweise benachrichtigen, wenn bestimmte Dateien bearbeitet wurden, und Sie können je nach ausgefallenem Dateityp oder Ereignis bestimmte Aktionen ausführen.
Dies ist als "Task Runner" bekannt. Sie haben den Satz vielleicht schon einmal gehört, da er im Moment in der Front-End / Client-Welt viel Verwendung findet (Grunt und Gulp sind zwei beliebte Beispiele).
Der Grund, warum wir Guard verwenden, ist, weil dadurch die Rückkopplungsschleife (bei TDD) viel enger wird. Es erlaubt uns, unsere Testdateien zu bearbeiten, einen fehlgeschlagenen Test anzuzeigen, unseren Code zu aktualisieren und zu speichern und sofort zu sehen, ob er erfolgreich ist oder nicht (je nachdem, was wir geschrieben haben)..
Sie könnten stattdessen etwas wie Grunt oder Gulp verwenden, aber wir bevorzugen diese Art von Task-Läufern für den Umgang mit Front-End- / Client-seitigem Material. Für Back-End / Server-Side-Code verwenden wir Rake und Guard.
RSpec ist ein Testwerkzeug für die Programmiersprache Ruby, wenn Sie es nicht bereits wissen.
Sie führen Ihre Tests (mit RSpec) über die Befehlszeile aus, und ich werde Ihnen zeigen, wie Sie diesen Prozess mithilfe von Rubys Build-Programm Rake vereinfachen können.
Schließlich verwenden wir einen weiteren Ruby-Edelstein namens Pry, ein äußerst leistungsfähiges Ruby-Debugging-Tool, das sich während der Ausführung in Ihre Anwendung einfügt, damit Sie Ihren Code überprüfen und herausfinden können, warum etwas nicht funktioniert.
Obwohl dies nicht notwendig ist, um die Verwendung von RSpec und Guard zu demonstrieren, ist es erwähnenswert, dass ich die Verwendung von TDD uneingeschränkt unterstütze, um sicherzustellen, dass jede Zeile Code, die Sie schreiben, einen Zweck hat und testbar und zuverlässig entworfen wurde.
Ich werde im Detail beschreiben, wie wir TDD mit einer einfachen Anwendung ausführen würden, so dass Sie zumindest ein Gefühl dafür bekommen, wie der Prozess funktioniert.
Ich habe ein grundlegendes Beispiel für GitHub erstellt, damit Sie nicht alles selbst eingeben müssen. Fühlen Sie sich frei, um den Code herunterzuladen.
Lassen Sie uns jetzt dieses Projekt Schritt für Schritt durchgehen.
Damit unsere Beispielanwendung funktionieren kann, sind drei primäre Dateien erforderlich. Diese sind:
Gemfile
Guardfile
Rakefile
Wir werden den Inhalt jeder Datei in Kürze durchgehen, aber als Erstes müssen wir unsere Verzeichnisstruktur einrichten.
Für unser Beispielprojekt müssen wir zwei Ordner erstellen:
lib
(dies wird unseren Anwendungscode enthalten)spez
(dies wird unseren Testcode enthalten)Dies ist keine Voraussetzung für Ihre Anwendung. Sie können den Code in unseren anderen Dateien leicht anpassen, um mit der für Sie passenden Struktur zu arbeiten.
Öffnen Sie Ihr Terminal und führen Sie den folgenden Befehl aus:
gem install Bundler
Bundler ist ein Tool, das die Installation anderer Edelsteine vereinfacht.
Wenn Sie diesen Befehl ausgeführt haben, erstellen Sie die drei oben genannten Dateien (Gemfile
, Guardfile
und Rakefile
).
Das Gemfile
ist für die Definition einer Liste von Abhängigkeiten für unsere Anwendung verantwortlich.
So sieht es aus:
Quelle "https://rubygems.org" gem 'rspec'-Gruppe: Entwicklung tun' Gem 'guard' Gem 'guard-rspec' Edelstein 'pry' ende
Sobald diese Datei gespeichert ist, führen Sie den Befehl aus Bundle installieren
.
Dadurch werden alle unsere Edelsteine installiert (einschließlich der im Verzeichnis angegebenen Edelsteine) Entwicklung
Gruppe).
Der Zweck der Entwicklung
Gruppe (das ist ein Bundler-spezifisches Feature): Wenn Sie Ihre Anwendung bereitstellen, können Sie der Produktionsumgebung anweisen, nur die Juwelen zu installieren, die für die ordnungsgemäße Funktion Ihrer Anwendung erforderlich sind.
So zum Beispiel alle Edelsteine im Inneren Entwicklung
Gruppe nicht erforderlich, damit die Anwendung ordnungsgemäß funktioniert. Sie dienen ausschließlich dazu, uns zu helfen, während wir unseren Code entwickeln und testen.
Um die entsprechenden Gems auf Ihrem Produktionsserver zu installieren, müssen Sie Folgendes ausführen:
Bundle installieren - ohne Entwicklung
Das Rakefile
erlaubt uns, unsere RSpec-Tests von der Kommandozeile aus durchzuführen.
So sieht es aus:
erfordern 'rspec / core / rake_task' RSpec :: Core :: RakeTask.new do | task | task.rspec_opts = ['--color', '--format', 'doc'] ende
Hinweis: Sie benötigen keinen Guard, um Ihre RSpec-Tests durchführen zu können. Wir verwenden Guard, um TDD einfacher zu machen.
Wenn Sie RSpec installieren, erhalten Sie Zugriff auf eine integrierte Rake-Aufgabe. Dies ist, was wir hier verwenden.
Wir erstellen eine neue Instanz von RakeTask
welche standardmäßig eine aufgerufene Aufgabe erstellt spez
das sucht nach einem Ordner namens spez
und führt alle Testdateien in diesem Ordner mit den von uns definierten Konfigurationsoptionen aus.
In diesem Fall möchten wir, dass unsere Shell-Ausgabe Farbe hat, und wir möchten die Ausgabe in die formatieren doc
Stil (Sie können das Format ändern, um verschachtelt
als Beispiel).
Sie können die Rake-Task so konfigurieren, dass sie auf beliebige Weise funktioniert und in verschiedene Verzeichnisse recherchiert, sofern dies der Fall ist. Die Standardeinstellungen funktionieren jedoch hervorragend für unsere Anwendung. Daher verwenden wir diese Einstellung.
Wenn ich die Tests in meinem Beispiel-GitHub-Repository ausführen möchte, muss ich mein Terminal öffnen und den Befehl ausführen:
Rechenspek
Dies gibt uns die folgende Ausgabe:
Rake spec / bin / ruby -Srspec ./spec/example_spec.rb --color --format doc RSpecGreeter RSpecGreeter # greet () Beendet in 0,0006 Sekunden 1 Beispiel, 0 Fehler
Wie Sie sehen, gibt es keine Fehler. Das liegt daran, dass, obwohl wir keinen Anwendungscode geschrieben haben, auch noch kein Testcode geschrieben wurde.
Der Inhalt dieser Datei teilt Guard mit, was zu tun ist, wenn wir das ausführen bewachen
Befehl:
guard 'rspec' do # watch / lib / files watch (% r ^ lib / (.+)). rb $) do | m | "spec / # m [1] _ spec.rb" end # watch / spec / files watch (% r ^ spec / (.+). rb $) do | m | "spec / # m [1]. rb" end end
Sie haben in unserem bemerkt Gemfile
Wir haben den Edelstein angegeben: guard-rspec
. Wir benötigen diesen Edelstein, damit Guard verstehen kann, wie mit Änderungen an RSpec-Dateien umgegangen wird.
Wenn wir uns den Inhalt noch einmal ansehen, können wir das sehen, wenn wir rennen Wache Rspec
Der Guard überwacht dann die angegebenen Dateien und führt die angegebenen Befehle aus, sobald Änderungen an diesen Dateien vorgenommen wurden.
Hinweis: weil wir nur eine Wachaufgabe haben, rspec
, dann wird das standardmäßig ausgeführt, wenn wir den Befehl ausgeführt haben bewachen
.
Sie können sehen, dass der Guard uns eine anbietet sehen
Übergeben Sie einen regulären Ausdruck, damit wir definieren können, welche Dateien wir an der Guard-Überwachung interessiert.
In diesem Fall fordern wir Guard auf, alle Dateien in unserem Ordner zu sehen lib
und spez
Ordner und wenn irgendwelche Änderungen an einer dieser Dateien auftreten, dann führen Sie die Testdateien in unserem aus spez
Ordner, um sicherzustellen, dass keine Änderungen, die wir vorgenommen haben, unsere Tests gebrochen haben (und anschließend unseren Code nicht beschädigt haben).
Wenn Sie alle Dateien aus dem GitHub-Repo heruntergeladen haben, können Sie den Befehl selbst ausprobieren.
Lauf bewachen
Speichern Sie dann eine der Dateien, um die Tests auszuführen.
Bevor wir uns den Test- und Anwendungscode ansehen, lassen Sie mich erklären, was unsere Anwendung tun soll. Unsere Anwendung ist eine einzelne Klasse, die eine Begrüßungsnachricht an denjenigen zurückgibt, der den Code ausführt.
Unsere Anforderungen werden absichtlich vereinfacht, da dadurch der Prozess, den wir durchführen werden, verständlicher wird.
Betrachten wir nun eine Beispielspezifikation (zum Beispiel unsere Testdatei), in der unsere Anforderungen beschrieben werden. Danach beginnen wir damit, den in der Spezifikation definierten Code schrittweise zu durchlaufen und sehen, wie wir TDD verwenden können, um uns beim Schreiben unserer Anwendung zu helfen.
Wir erstellen eine Datei mit dem Titel example_spec.rb
. Der Zweck dieser Datei ist es, unsere Spezifikationsdatei zu werden (mit anderen Worten, dies wird unser Testcode sein und die erwartete Funktionalität darstellen).
Der Grund, warum wir unseren Testcode schreiben, bevor wir unseren eigentlichen Anwendungscode schreiben, besteht darin, dass letztendlich alle von uns produzierten Anwendungscodes vorhanden sind, weil sie tatsächlich verwendet wurden.
Das ist ein wichtiger Punkt, den ich mache, und lassen Sie mich einen Moment Zeit nehmen, um es näher zu erläutern.
Wenn Sie Ihren Anwendungscode zuerst schreiben (damit Sie keine TDD ausführen), schreiben Sie normalerweise Code, der zu einem späteren Zeitpunkt überentwickelt und möglicherweise veraltet ist. Durch das Refactoring oder das Ändern von Anforderungen stellen Sie möglicherweise fest, dass einige Funktionen nicht aufgerufen werden können.
Aus diesem Grund wird TDD als die beste Methode und als bevorzugte Entwicklungsmethode betrachtet, da jede Zeile Code, die Sie produzieren, aus einem bestimmten Grund produziert wurde: eine fehlerhafte Spezifikation (Ihre tatsächliche Geschäftsanforderung) zu erhalten. Das ist eine sehr kraftvolle Sache, die man im Auge behalten muss.
Hier ist unser Testcode:
erfordern 'spec_helper' beschreiben 'RSpecGreeter' do it 'RSpecGreeter # greet ()' do greeter = RSpecGreeter.new # Gegebenes Begrüßungszeichen = greeter.greet # Beim Begrüßungsbild sollte eq ('Hallo RSpec!') # Dann beendet werden
Sie können die Code-Kommentare am Ende jeder Zeile bemerken:
Gegeben
Wann
Dann
Dies ist eine Form der BDD-Terminologie (Behavior-Driven Development). Ich habe sie für Leser aufgenommen, die mit BDD (Behavior-Driven Development) besser vertraut sind und daran interessiert waren, wie sie diese Aussagen mit TDD gleichsetzen können.
Das erste, was wir in dieser Datei tun, ist das Laden spec_helper.rb
(die sich in demselben Verzeichnis befindet wie unsere Spezifikationsdatei). Wir werden in Kürze zurückkommen und uns den Inhalt dieser Datei ansehen.
Als nächstes haben wir zwei Codeblöcke, die für RSpec spezifisch sind:
Beschreibe 'x'
es 'y' zu tun
Der Erste beschreiben
Block sollte die spezifische Klasse / das Modul, an dem wir arbeiten, und Tests bereitstellen, ausreichend beschreiben. Sie könnten sehr gut mehrere haben beschreiben
Blöcke innerhalb einer einzigen Spezifikationsdatei.
Es gibt viele verschiedene Theorien zur Verwendung beschreiben
und es
Beschreibungsblöcke. Ich persönlich bevorzuge die Einfachheit und verwende daher die Bezeichner für die Klasse / Module / Methoden, die wir testen werden. Es gibt jedoch oft Leute, die lieber vollständige Sätze für ihre Beschreibung verwenden. Weder ist richtig noch falsch, nur persönliche Vorlieben.
Das es
Block ist anders und sollte immer in einem platziert werden beschreiben
Block. Es sollte erklären Wie Wir möchten, dass unsere Anwendung funktioniert.
Auch hier könnte man einen normalen Satz verwenden, um die Anforderungen zu beschreiben, aber ich habe festgestellt, dass die Beschreibungen manchmal zu explizit sein können, wenn sie wirklich mehr sind implizit. Wenn Sie weniger explizit sind, verringert sich die Wahrscheinlichkeit, dass Änderungen an Ihrer Funktionalität vorgenommen werden. Dadurch wird Ihre Beschreibung veraltet (wenn Sie Ihre Beschreibung jedes Mal aktualisieren müssen, wenn geringfügige Funktionsänderungen auftreten, ist dies eher eine Belastung als eine Hilfe). Durch die Verwendung des Bezeichners der Methode, die wir testen (z. B. der Name der Methode, die wir ausführen) können wir dieses Problem vermeiden.
Der Inhalt der es
Block ist der Code, den wir testen werden.
Im obigen Beispiel erstellen wir eine neue Instanz der Klasse RSpecGreeter
(was es noch nicht gibt). Wir senden die Nachricht grüßen
(das auch noch nicht existiert) auf das erzeugte instanziierte Objekt (Hinweis: Diese beiden Zeilen sind an dieser Stelle Standard-Ruby-Code..
Abschließend sagen wir dem Testframework, dass wir das Ergebnis des Aufrufs von erwarten grüßen
Methode, um den Text zu sein "Hallo RSpec!
", unter Verwendung der RSpec-Syntax: eq (etwas)
.
Beachten Sie, dass die Syntax es leicht lesbar macht (auch für nicht-technische Personen). Diese sind bekannt als Behauptungen.
Es gibt viele verschiedene RSpec-Zusicherungen, und wir werden nicht auf die Details eingehen, aber schauen Sie sich die Dokumentation an, um alle Funktionen von RSpec zu sehen.
Für die Durchführung unserer Tests ist eine bestimmte Anzahl von Heizplatten erforderlich. In diesem Projekt haben wir nur eine Spezifikationsdatei, aber in einem echten Projekt haben Sie wahrscheinlich Dutzende (abhängig von der Größe Ihrer Anwendung)..
Um den Boilerplate-Code zu reduzieren, platzieren wir ihn in einer speziellen Hilfsdatei, die wir aus unseren Spezifikationsdateien laden. Diese Datei wird als Titel bezeichnet spec_helper.rb
.
Diese Datei wird ein paar Dinge tun:
neugierig sein
gem (hilft uns, unseren Code zu debuggen; wenn wir müssen).Hier ist der Code:
$ << File.join(File.dirname(FILE), '… ', 'lib') require 'pry' require 'example'
Hinweis: Die erste Zeile kann etwas kryptisch aussehen. Lassen Sie mich erklären, wie es funktioniert. Hier wollen wir das hinzufügen / lib /
Ordner zu Ruby $ LOAD_PATH
Systemvariable. Wann immer Ruby bewertet benötige 'some_file'
Es enthält eine Liste von Verzeichnissen, in denen versucht wird, diese Datei zu finden. In diesem Fall stellen wir sicher, dass wir den Code haben "Beispiel" verlangen
dass Ruby es finden kann, weil es unsere überprüft / lib /
Verzeichnis und dort wird die angegebene Datei gefunden. Dies ist ein cleverer Trick, den Sie in vielen Ruby-Edelsteinen sehen werden, aber es kann ziemlich verwirrend sein, wenn Sie ihn noch nie gesehen haben.
Unser Anwendungscode befindet sich in einer Datei mit dem Titel beispiel.rb
.
Bevor Sie mit dem Schreiben von Anwendungscode beginnen, denken Sie daran, dass wir dieses Projekt TDD durchführen. Wir werden uns daher von den Tests in unserer Spezifikationsdatei leiten lassen, was zuerst zu tun ist.
Beginnen wir mit der Annahme, dass Sie verwenden bewachen
um Ihre Tests durchzuführen (also jedes Mal, wenn wir eine Änderung vornehmen beispiel.rb
, Die Wache wird die Änderung bemerken und mit dem Ausführen fortfahren example_spec.rb
um sicherzustellen, dass unsere Tests bestanden werden.
Für uns, TDD richtig zu machen, unser beispiel.rb
Die Datei ist leer. Wenn wir also die Datei öffnen und in ihrem aktuellen Zustand "speichern", wird Guard ausgeführt und wir werden (nicht überraschend) feststellen, dass unser Test fehlschlägt:
Fehler: 1) RSpecGreeter RSpecGreeter # greet () Fehler / Fehler: greeter = RSpecGreeter.new # Gegebener NameError: nicht initialisierte Konstante RSpecGreeter # ./spec/example_spec.rb:5:inBlock (2 Ebenen) in 'Beendet in 0,00059 Sekunden 1 Beispiel, 1 Fehler Fehlgeschlagene Beispiele: rspec ./spec/example_spec.rb:4 # RSpecGreeter RSpecGreeter # greet ()
Bevor wir nun weiter gehen, möchte ich noch einmal klarstellen, dass TDD auf der Prämisse basiert, dass jede Codezeile einen Grund hat, zu existieren nicht Fahre mit dem Rennen voraus und schreibe mehr Code heraus, als du brauchst. Schreiben Sie nur die Mindestmenge an Code, die der Test zum Bestehen benötigt. Auch wenn der Code hässlich ist oder nicht die volle Funktionalität erfüllt.
Der Sinn von TDD ist es, einen Engpass zu haben Rückkopplungsschleife, auch bekannt als "rot, grün, refactor"). Was dies in der Praxis bedeutet, ist:
Sie werden gleich sehen, dass unsere Anforderungen so einfach sind, dass wir nicht umgestalten müssen. In einem realen Projekt mit viel komplexeren Anforderungen müssen Sie jedoch wahrscheinlich den dritten Schritt durchführen und den eingegebenen Code umgestalten, damit der Test bestanden wird.
Kommen wir zu unserem fehlgeschlagenen Test zurück, wie Sie im Fehler sehen können, gibt es keinen RSpecGreeter
Klasse definiert. Beheben Sie das Problem, fügen Sie den folgenden Code hinzu und speichern Sie die Datei, damit unsere Tests ausgeführt werden:
Klasse RSpecGreeter # Code wird irgendwann hier enden
Dies führt zu dem folgenden Fehler:
Fehler: 1) RSpecGreeter RSpecGreeter # greet () Fehler / Fehler: greeter = greeter.greet # When NoMethodError: undefined methodgreet 'für # # ./spec/example_spec.rb:6:in' Block (2 Ebenen) in 'Fertiggestellt 0,00036 Sekunden 1 Beispiel, 1 Fehler
Jetzt können wir sehen, dass dieser Fehler uns die Methode sagt grüßen
existiert nicht, also fügen wir sie hinzu und speichern dann erneut unsere Datei, um unsere Tests auszuführen:
class RSpecGreeter def greet # code wird irgendwann hier enden ende
OK, wir sind fast da. Der Fehler, den wir jetzt bekommen, ist:
Fehler: 1) RSpecGreeter RSpecGreeter # greet () Fehler / Fehler: greeter = greeting.should eq ('Hallo RSpec!') # Dann erwartet: "Hallo RSpec!" got: nil (im Vergleich zu ==) # ./spec/example_spec.rb:7:in 'block (2 Ebenen) in' Beendet in 0,00067 Sekunden 1 Beispiel, 1 Fehler
RSpec sagt uns, dass es erwartet hatte zu sehen Hallo RSpec!
aber stattdessen bekam es Null
(weil wir die definiert haben grüßen
Methode, aber nicht wirklich etwas innerhalb der Methode definiert und so kehrt sie zurück Null
).
Wir fügen den restlichen Code hinzu, der unseren Test bestehen würde:
Klasse RSpecGreeter def grüßt "Hallo RSpec!" Ende Ende
Da haben wir es, einen bestandenen Test:
Beendet in 0.00061 Sekunden 1 Beispiel, 0 Fehler
Wir sind hier fertig. Unser Test ist geschrieben und der Code ist bestanden.
Bisher haben wir einen Test-Driven-Entwicklungsprozess für die Erstellung unserer Anwendung angewendet und das beliebte RSpec-Testframework verwendet.
Wir werden es jetzt hier lassen. Kommen Sie zurück und kommen Sie zu Teil zwei, wo wir uns weitere RSpec-spezifische Funktionen ansehen und den Pry-Edelstein verwenden, um Ihnen beim Debuggen und Schreiben Ihres Codes zu helfen.