Wie teste ich?

In einer kürzlich auf Google+ geführten Diskussion kommentierte ein Freund von mir: "Test-Driven Development (TDD) und Verhaltens-Driven Development (BDD) ist Ivory Tower BS."Das hat mich dazu veranlasst, über mein erstes Projekt nachzudenken, wie ich mich damals genauso gefühlt habe und wie ich mich jetzt dabei fühle. Seit diesem ersten Projekt habe ich einen TDD / BDD-Rhythmus entwickelt, der nicht nur für mich, sondern auch für mich funktioniert auch für den Kunden.

Ruby on Rails wird mit einer Testsuite ausgeliefert, die als Test Unit bezeichnet wird. Viele Entwickler bevorzugen jedoch RSpec, Cucumber oder eine Kombination aus beiden. Persönlich bevorzuge ich letzteres, indem ich eine Kombination aus beidem benutze.


RSpec

Von der RSpec-Site:

RSpec ist ein Testwerkzeug für die Programmiersprache Ruby. Geboren unter dem Namen Behavior-Driven Development, ist es darauf angelegt, Test-Driven Development zu einer produktiven und angenehmen Erfahrung zu machen.

RSpec bietet ein leistungsfähiges DSL, das für Geräte- und Integrationstests nützlich ist. Während ich RSpec zum Schreiben von Integrationstests verwendet habe, ziehe ich es vor, es nur in einer Einheitstestkapazität zu verwenden. Deshalb werde ich darauf eingehen, wie ich RSpec ausschließlich für Komponententests verwende. Ich empfehle, das RSpec-Buch von David Chelimsky und anderen zu lesen, um eine umfassende und umfassende RSpec-Berichterstattung zu erhalten.


Gurke

Ich habe festgestellt, dass die Vorteile von TDD / BDD die Nachteile bei weitem überwiegen.

Cucumber ist ein Integrations- und Akzeptanztest-Framework, das Ruby, Java, .NET, Flex und eine Vielzahl anderer Web-Sprachen und Frameworks unterstützt. Seine wahre Stärke kommt von seinem DSL. Es ist nicht nur auf Englisch verfügbar, sondern wurde bereits in über 40 gesprochene Sprachen übersetzt.

Mit einem von Menschen lesbaren Abnahmetest können Sie den Kunden eine Funktion abmelden lassen, bevor Sie eine einzige Codezeile schreiben. Wie bei RSpec werde ich Gurke nur in der Funktion behandeln, in der ich sie verwende. Den kompletten Überblick über Cucumber finden Sie im The Cucumber Book.


Die Einrichtung

Beginnen wir zunächst mit einem neuen Projekt und weisen Sie Rails an, die Test Unit zu überspringen. Geben Sie Folgendes in ein Terminal ein:

 Schienen neues how_i_test -T

Innerhalb des Gemfile, hinzufügen:

 Quelle "https: //rubygems.org" Gruppe: test do gem 'capybara' gem 'Gurkenschienen', erforderlich: falscher Edelstein 'Datenbank-Reiniger' gem 'factory_girl_rails' gem 'shoulda' Endgruppe: Entwicklung,: test do edelstein 'rspec-schienen' ende

Ich verwende hauptsächlich RSpec, um sicherzustellen, dass meine Modelle und ihre Methoden in Schach bleiben.

Hier haben wir Gurke und Freunde in die Gruppe gesteckt Prüfung Block. Dadurch wird sichergestellt, dass sie nur in der Testumgebung von Rails ordnungsgemäß geladen werden. Beachten Sie, wie wir auch RSpec in den laden Entwicklung und Prüfung Blöcke, so dass es in beiden Umgebungen verfügbar ist. Es gibt ein paar andere Edelsteine. das werde ich unten kurz beschreiben. Vergiss nicht zu laufen Bundle installieren um sie zu installieren.

  • Capybara: simuliert Browser-Interaktionen.
  • Datenbankbereiniger: Bereinigt die Datenbank zwischen den Testläufen.
  • Fabrik-Mädchen-Schienen: Geräteaustausch.
  • Shoulda: Hilfsmethoden und Matcher für RSpec.

Wir müssen die Generatoren dieser Edelsteine ​​betreiben, um sie einzurichten. Dies können Sie mit den folgenden Terminalbefehlen tun:

 schienen g rspec: install create .rspec create spec create spec / spec_helper.rb schienen g gurke: install create config / cucumber.yml create script / cucumber chmod script / cucumber create features / step_definitions erstellen features / support erstellen features / support / env. rb existiert lib / task erstellt lib / task / cucumber.rake gsub config / database.yml gsub config / database.yml erzwingt config / database.yml

An diesem Punkt könnten wir mit dem Schreiben von Spezifikationen und Kerben beginnen, um unsere Anwendung zu testen, aber wir können ein paar Dinge einrichten, um das Testen zu erleichtern. Beginnen wir im application.rb Datei.

 Modul HowITest Klasse Anwendung < Rails::Application config.generators do |g| g.view_specs false g.helper_specs false g.test_framework :rspec, :fixture => true g.fixture_replacement: factory_girl,: dir => 'spec / factories' end… end end

In der Application-Klasse überschreiben wir einige Standardgeneratoren von Rails. Für die ersten beiden überspringen wir die Ansichten und die Generierungsspezifikationen für Helfer.

Diese Tests sind nicht erforderlich, da wir RSpec nur für Komponententests verwenden.

Die dritte Zeile informiert Rails darüber, dass wir beabsichtigen, RSpec als Test-Framework der Wahl zu verwenden, und es sollte auch bei der Generierung von Modellen Fixtures generieren. Die letzte Zeile stellt sicher, dass factory_girl für unsere Geräte verwendet wird, von denen in der erstellt wird spec / fabriken Verzeichnis.


Unser erstes Feature

Um es einfach zu halten, schreiben wir eine einfache Funktion zum Anmelden in unserer Anwendung. Der Kürze halber werde ich die eigentliche Implementierung überspringen und bei der Testsuite bleiben. Hier ist der Inhalt von features / signing_in.feature:

 Feature: Anmelden Um die Anwendung zu nutzen Als registrierter Benutzer möchte ich mich über ein Formular anmelden. Szenario: Anmeldung über das Formular. Es gibt einen registrierten Benutzer mit der E-Mail "[email protected]". Und ich bin auf dem Schild in page Wenn ich richtige Anmeldeinformationen eingebe und ich auf die Anmeldeschaltfläche drücke Dann sollte die Flash-Nachricht "Erfolgreich angemeldet" sein.

Wenn wir das im Terminal mit ausführen gurke features / signing_in.feature, Wir sehen eine Menge Ausgabe, die mit unseren undefinierten Schritten endet:

 Bei / ^ gibt es einen registrierten Benutzer mit der Email "(. *?)" $ / Do | arg1 | ausstehend # drücken Sie den Regexx oben mit dem Code aus, den Sie wünschen, dass Sie das Ende hätten. Gegeben / ^ Ich bin auf der Anmeldeseite $ / do ausstehend # drücken Sie den Regexx oben aus, und geben Sie den Code ein, den Sie am Ende wünschten. Wann / ^ Ich gebe die korrekten Anmeldeinformationen ein $ / mache ausstehende # drücke den Regexx mit dem Code aus, den du wünschst, du hättest enden Wenn / ^ Ich drücke den Anmeldebutton $ / mache ausstehende # drücke den Regexx mit dem Code, den du wünschst, du hättest enden Dann / ^ sollte die Flash-Nachricht " (. *?) "$ / do | arg1 | ausstehend # drücken Sie den Regexx oben mit dem Code aus, den Sie am Ende wünschen

Der nächste Schritt ist zu definieren, was von diesen Schritten erwartet wird. Wir drücken das in aus features / stepdefinitions / signin_steps.rb, Verwenden von normalem Ruby mit Capybara- und CSS-Selektoren.

 Bei / ^ gibt es einen registrierten Benutzer mit der Email "(. *?)" $ / Do | email | @user = FactoryGirl.create (: Benutzer, E-Mail: E-Mail) Ende Gegeben / ^ Ich bin auf der Anmeldeseite $ / do visit sign_in_path Ende. Wenn / ^ ich die korrekten Anmeldeinformationen eingebe $ / do "E-Mail" ausfüllen, mit: @user .email fillin "Password", mit: @ user.password end Wenn / ^ drücke ich den Anmelden-Button $ / do click_button "Sign in" end Dann / ^ sollte die Flash-Nachricht "(. *?)" $ / do sein | text | innerhalb (". flash") tun page.hould_content text end end

In jedem der Gegeben, Wann, und Dann Blöcke verwenden wir die Capybara-DSL, um zu definieren, was wir von jedem Block erwarten (außer im ersten Block). Im ersten gegebenen Block weisen Sie factory_girl an, einen im erstellten Benutzer zu erstellen Nutzer Instanzvariable zur späteren Verwendung. Wenn du läufst gurke features / signing_in.feature Wieder sollten Sie etwas Ähnliches wie das Folgende sehen:

 Szenario: Anmeldung über das Formular # features / signing_in.feature: 6 Angenommen, es gibt einen registrierten Benutzer mit der E-Mail-Adresse "[email protected]" # features / step_definitions / signing \ _in \ _steps.rb: 1 Fabrik nicht registriert: Benutzer ( ArgumentError) ./features/step_definitions/signing\_in\_steps.rb:2:in '/ ^ es gibt einen registrierten Benutzer mit E-Mail "(. *?)" $ /' Features / signing_in.feature: 7: in 'Given' Es gibt einen registrierten Benutzer mit der E-Mail "[email protected]" '

Wir können aus der Fehlermeldung sehen, dass unser Beispiel in Zeile 1 mit einem fehlschlägt ArgumentError der Benutzerfabrik wird nicht registriert. Wir könnten diese Fabrik selbst erstellen, aber ein Teil der Magie, die wir zuvor eingerichtet haben, wird Rails dazu bringen, das für uns zu tun. Wenn wir unser Benutzermodell erstellen, erhalten wir das Anwenderwerk kostenlos.

 Rails Modell E-Mail des Modellbenutzers: Zeichenfolge Kennwort: Zeichenfolge invoke active_record create db / migrate / 20121218044026 \ _create \ _users.rb create app / models / user.rb aufrufen .rb

Wie Sie sehen, ruft der Modellgenerator factory_girl auf und erstellt die folgende Datei:

ruby spec / factories / users.rb FactoryGirl.define do factory: Benutzer senden E-Mail "MyString" Passwort "MyString" Ende Ende

Ich werde hier nicht tief in die Tiefe von factory_girl eingehen, aber Sie können mehr in ihrem Einführungshandbuch lesen. Vergiss nicht zu laufen rake db: migrieren und Rechen db: test: vorbereiten um das neue Schema zu laden. Dies sollte den ersten Schritt unserer Funktion zum Bestehen bringen und Ihnen die Einführung von Gurken für Ihre Integrationstests erleichtern. Bei jedem Durchlauf Ihrer Features führt Cucumber Sie zu den Teilen, die fehlen, um sie passieren zu lassen.


Modellprüfung mit RSpec und Shoulda

Ich verwende hauptsächlich RSpec, um sicherzustellen, dass meine Modelle und ihre Methoden in Schach bleiben. Ich verwende es oft auch für einige Tests von Controllern auf hohem Niveau, aber das geht detaillierter aus, als dies in diesem Handbuch möglich ist. Wir werden dasselbe Benutzermodell verwenden, das wir zuvor mit unserer Anmeldefunktion eingerichtet haben. Wenn wir auf den Ausgang des Modellgenerators zurückblicken, sehen wir, dass wir auch bekommen haben user_spec.rb kostenlos. Wenn wir rennen rspec spec / models / user_spec.rb Wir sollten die folgende Ausgabe sehen.

 Ausstehend: Benutzer fügt einige Beispiele zu /Users/janders/workspace/how\_i\_test/spec/models/user_spec.rb hinzu (oder löscht sie)

Und wenn wir diese Datei öffnen, sehen wir:

 erfordern 'spechelper' beschreiben Benutzer tun ausstehend "fügen Sie einige Beispiele hinzu (oder löschen Sie # DATEI")

Die ausstehende Zeile gibt uns die Ausgabe, die wir im Terminal gesehen haben. Wir werden die ActiveRecord- und ActiveModel-Matcher von Shoulda nutzen, um sicherzustellen, dass unser Benutzermodell unserer Geschäftslogik entspricht.

 erfordern 'spechelper' beschreiben Benutzer do context "#fields" do it sollte antworten (: email) es sollte antworten (: passwort) es sollte antworten (: vorname) es sollte antworten (: nachname) ende context "#validations" do it sollte validate_presence_of (: email) es soll validate_presence_of (: password) es soll validate_uniqueness_of (: email) beenden end kontext "#associations" do it sollte have_many (: task) beenden Beschreibe "#methods" do let! (: user) FactoryGirl.create (: user) Es sollte "name" den Benutzernamen "do user.name.should eql" "Testy McTesterson" end end end angeben

Wir haben einige Kontextblöcke innerhalb unseres ersten eingerichtet beschreiben blockieren, um Dinge wie Felder, Validierungen und Assoziationen zu testen. Zwar gibt es keine funktionalen Unterschiede zwischen a beschreiben und ein Kontext Block gibt es einen kontextuellen. Wir gebrauchen beschreiben Blöcke, um den Status von dem festzulegen, was wir testen, und Kontext Blöcke, um diese Tests zu gruppieren. Dies macht unsere Tests auf lange Sicht lesbarer und wartbarer.

Der Erste beschreiben erlaubt es uns, mit dem zu testen Nutzer Modell in einem unveränderten Zustand.

Wir verwenden diesen nicht geänderten Status, um mit der Datenbank zu testen, wobei die Shoulda-Matcher nach Typ gruppieren. Der nächste beschreiben Block baut einen Benutzer von unserem zuvor erstellten auf Nutzer Fabrik. Einrichten des Benutzers mit der Lassen Die Methode innerhalb dieses Blocks ermöglicht es uns, eine Instanz unseres Benutzermodells anhand bekannter Attribute zu testen.

Nun, wenn wir rennen rspec spec / models / user_spec.rb, Wir sehen, dass alle unsere neuen Tests fehlschlagen.

 Fehler: 1) Name der User # -Methoden sollte den Benutzernamen zurückgeben Failure / Error: user.name.should eql "Testy McTesterson" NoMethodError: undefined method name 'für # # ./spec/models/user_spec.rb:26:inBlock (3 Ebenen) in '2) User # -Validierungen Fehler / Fehler: muss validate_uniqueness_of (: email) Zu erwartende Fehler "wurde bereits genommen", wenn die E-Mail auf "willkürlich" gesetzt istString ", keine Fehler # ./spec/models/userspec.rb: 15: in 'block (3 stufen) in '3) User # validations Fehler / Fehler: es sollte validate_presence_of (: password) Erwartete Fehler "kann nicht leer sein", wenn das Kennwort auf null gesetzt ist, keine Fehler # ./spec/models/user_spec.rb : 14: in 'block (3 stufen) in '4) User # validations Fehler / Fehler: es sollte validate_presence_of (: email) Erwartete Fehler "kann nicht leer sein", wenn E-Mail auf null gesetzt ist, keine Fehler # ./spec/models/user_spec.rb : 13: in 'block (3 stufen) in '5) User-#-Assoziationen Fehler / Fehler: Es sollteviele (: Aufgaben) Erwarteter Benutzer hat einenViele Assoziationen, die als Tasks bezeichnet werden (keine Assoziationen, die als Tasks bezeichnet werden) # ./spec/models/user_spec.rb:19:in 'Block (3 Ebenen) in '6) Benutzer # -Felder Fehler / Fehler: Es sollte antwortenhaltenname) erwartet # zu antworten: zuletztname # ./spec/models/userspec.rb: 9: in 'block (3 stufen) in '7) Benutzer # -Felder Fehler / Fehler: Es sollte antwortenbis (: zuerstname) erwartet # zu antworten: zuerstname # ./spec/models/userspec.rb: 8: in Block (3 Ebenen) in '

Wenn jeder dieser Tests fehlschlägt, haben wir den Rahmen, den wir benötigen, um Migrationen, Methoden, Assoziationen und Validierungen zu unseren Modellen hinzuzufügen. Wenn sich unsere Anwendung weiterentwickelt, Modelle erweitern und unser Schema geändert wird, bieten wir mit dieser Teststufe Schutz für die Einführung bahnbrechender Änderungen.


Fazit

Während wir nicht zu viele Themen ausführlich behandelt haben, sollten Sie jetzt ein grundlegendes Verständnis für die Integration und das Testen von Einheiten mit Cucumber und RSpec besitzen. TDD / BDD ist eines der Dinge, die Entwickler entweder zu tun scheinen oder nicht, aber ich habe festgestellt, dass die Vorteile von TDD / BDD die Nachteile bei mehr als einer Gelegenheit bei weitem überwiegen.