Ruby Page Objekte für Capybara-Kenner

Was Sie erstellen werden

Was sind Seitenobjekte??

Ich werde dir zuerst die kurze Tonhöhe geben. Es ist ein Entwurfsmuster, um Markup- und Seiteninteraktionen zu kapseln, und zwar speziell, um die Feature-Spezifikationen zu überarbeiten. Es ist eine Kombination aus zwei sehr verbreiteten Refactoring-Techniken: Klasse extrahieren und Methode extrahieren-Dies muss nicht gleichzeitig geschehen, da Sie schrittweise das Extrahieren einer ganzen Klasse über ein neues Seitenobjekt aufbauen können.

Mit dieser Technik können Sie Feature-Spezifikationen auf hoher Ebene schreiben, die sehr ausdrucksstark und DRY sind. In gewisser Weise handelt es sich um Abnahmetests mit der Anwendungssprache. Sie fragen sich vielleicht, sind die mit Capybara geschriebenen Daten nicht bereits auf hohem Niveau und ausdrucksstark? Klar, für Entwickler, die täglich Code schreiben, lesen die Capybara-Spezifikationen gut. Sind sie trocken aus der Box? Nicht wirklich, schon gar nicht!

"Rubin-Feature" M erstellt eine Mission "führt Szenario erfolgreich aus" do sign_in_as "[email protected]"

besuche missions_path click_on 'Mission erstellen' fill_in 'Mission Name', mit: 'Project Moonraker' click_button 'Submit' erwarten (Seite) .to have_css 'li.mission-name', Text: 'Project Moonraker' end end "

"Rubin-Feature 'M markiert Mission als abgeschlossen' Szenario erfolgreich abgeschlossen 'do sign_in_as' [email protected] '

besuche missions_path click_on 'Mission erstellen' fill_in 'Mission Name', mit: 'Octopussy' click_button 'Submit' innerhalb von 'li: contains (' Octopussy ')' do click_on 'Mission beendet' Ende erwartet (page) .to have_css 'ul. Missionen li.mission-name.completed ', Text:' Octopussy 'Ende Ende "

Wenn Sie sich diese Beispiele für Feature-Spezifikationen ansehen, wo sehen Sie Möglichkeiten, diese Lesbarkeit zu verbessern, und wie können Sie Informationen extrahieren, um Doppelarbeit zu vermeiden? Ist dieses Niveau auch für die einfache Modellierung von User Storys und für nicht-technische Stakeholder ausreichend?

Meines Erachtens gibt es mehrere Möglichkeiten, dies zu verbessern und alle Entwickler glücklich zu machen, die es vermeiden, sich mit den Details der Interaktion mit dem DOM zu beschäftigen, während sie OOP anwenden, und andere nicht-codierende Teammitglieder, die problemlos zwischen den Benutzergeschichten springen und diese Tests. Der letzte Punkt ist sicherlich schön zu haben, aber die wichtigsten Vorteile ergeben sich meistens aus der Verbesserung der DOM-Interaktionsspezifikationen.

Die Kapselung ist das Schlüsselkonzept bei Page Objects. Wenn Sie Ihre Feature-Spezifikationen schreiben, profitieren Sie von einer Strategie, um das Verhalten zu ermitteln, das einen Testablauf durchläuft. Für Qualitätscode möchten Sie die Interaktionen mit bestimmten Elementgruppen auf Ihren Seiten erfassen - insbesondere, wenn Sie beim Wiederholen von Mustern stolpern. Wenn Ihre Anwendung wächst, benötigen / benötigen Sie einen Ansatz, der verhindert, dass sich diese Logik über Ihre Spezifikationen verteilt.

”Ist das nicht übertrieben? Capybara liest sich gut! “, Sagst du?

Fragen Sie sich: Warum sollten Sie nicht alle HTML-Implementierungsdetails an einem Ort haben und stabilere Tests durchführen? Warum sollten Interaktionstests für Benutzeroberflächen nicht dieselbe Qualität haben wie Tests für Anwendungscode? Willst du wirklich dort aufhören??

Aufgrund der alltäglichen Änderungen ist Ihr Capybara-Code anfällig, wenn er sich über die gesamte Breite ausdehnt. Dies führt zu möglichen Haltepunkten. Angenommen, ein Designer möchte den Text einer Schaltfläche ändern. Kein Biggie, richtig? Möchten Sie sich jedoch an diese Änderung in einem zentralen Wrapper für dieses Element in Ihren Spezifikationen anpassen, oder ziehen Sie es vor, dies überall zu tun? Ich dachte auch!

Für Ihre Feature-Spezifikationen sind viele Umgestaltungen möglich, aber Seitenobjekte bieten die saubersten Abstraktionen zum Verkapseln des Verhaltens von Benutzern für Seiten oder komplexere Flows. Sie müssen nicht die gesamte Seite (n) simulieren, konzentrieren Sie sich jedoch auf die wesentlichen Elemente, die für Benutzerabläufe erforderlich sind. Keine Notwendigkeit, es zu übertreiben!

Abnahmetests / Feature-Spezifikationen

Bevor wir uns dem Kern der Sache zuwenden, möchte ich einen Schritt zurückgehen, um neue Leute für das gesamte Testgeschäft zu gewinnen und einige der in diesem Zusammenhang wichtigen Ausdrücke zu klären. Menschen, die sich mit TDD besser auskennen, werden nicht viel vermissen, wenn sie vorausgehen.

Worüber reden wir hier? Akzeptanztests werden in der Regel zu einem späteren Zeitpunkt in Projekten durchgeführt, um zu prüfen, ob Sie für Ihre Benutzer, Produktbesitzer oder andere Interessengruppen etwas Wertvolles aufgebaut haben. Diese Tests werden normalerweise von Kunden oder Benutzern ausgeführt. Es ist eine Art Überprüfung, ob die Anforderungen erfüllt werden oder nicht. Es gibt so etwas wie eine Pyramide für alle Arten von Testschichten, und Abnahmetests befinden sich ganz oben. Da dieser Prozess häufig nicht-technische Personen umfasst, ist eine Hochsprache für das Schreiben dieser Tests ein wertvolles Gut für die Kommunikation zwischen beiden Seiten.

Auf der anderen Seite sind die Funktionsspezifikationen in der Test-Nahrungskette etwas niedriger. Viel mehr als nur Unit-Tests, die sich auf die technischen Details und die Geschäftslogik Ihrer Modelle konzentrieren, beschreiben Feature-Spezifikationen den Datenfluss auf und zwischen Ihren Seiten.

Tools wie Capybara helfen Ihnen dabei, dies manuell zu vermeiden, was bedeutet, dass Sie Ihren Browser selten öffnen müssen, um Dinge manuell zu testen. Mit diesen Tests möchten wir diese Aufgaben möglichst weitgehend automatisieren und die Interaktion über den Browser testen, während Assertions gegen Seiten geschrieben werden. Übrigens verwenden Sie nicht erhalten, stellen, Post oder löschen wie Sie mit Anforderungsspezifikationen tun.

Feature-Spezifikationen ähneln Abnahmetests - manchmal sind die Unterschiede zu verschwommen, um sich wirklich um die Terminologie zu kümmern. Sie schreiben Tests, die Ihre gesamte Anwendung ausführen, was häufig einen mehrstufigen Ablauf von Benutzeraktionen beinhaltet. Diese Interaktionstests zeigen, ob Ihre Komponenten beim Zusammenfügen harmonisch zusammenarbeiten.

In Ruby Land sind sie der Hauptdarsteller, wenn es um Seitenobjekte geht. Feature-Spezifikationen selbst sind bereits sehr ausdrucksstark, können jedoch optimiert und bereinigt werden, indem ihre Daten, ihr Verhalten und ihr Markup in eine oder mehrere separate Klassen extrahiert werden.

Ich hoffe, dass das Aufräumen dieser verschwommenen Terminologie Ihnen helfen wird, zu erkennen, dass das Verwenden von Seitenobjekten ein bisschen wie das Durchführen von Tests beim Schreiben von Feature-Spezifikationen ist.

Capybara

Vielleicht sollten wir das auch sehr schnell besprechen. Diese Bibliothek beschreibt sich selbst als "Akzeptanztest-Framework für Webanwendungen". Sie können Benutzerinteraktionen mit Ihren Seiten über eine sehr leistungsfähige und komfortable domänenspezifische Sprache simulieren. Meiner Meinung nach bietet RSpec in Kombination mit Capybara die beste Möglichkeit, derzeit Ihre Feature-Spezifikationen zu schreiben. Sie können Seiten besuchen, Formulare ausfüllen, auf Links und Schaltflächen klicken und nach Markierungen auf Ihren Seiten suchen. Sie können alle Arten dieser Befehle kombinieren, um mit Ihren Seiten durch Ihre Tests zu interagieren.

Sie können es grundsätzlich vermeiden, den Browser selbst zu öffnen, um dieses Zeug meistens manuell zu testen - was nicht nur weniger elegant, sondern auch viel zeitaufwändiger und fehleranfälliger ist. Ohne dieses Tool wäre der Prozess des "Outside-in-Testing" - Sie fahren Ihren Code von Tests auf hoher Ebene in Ihre Tests auf Einheitenebene - sehr viel schmerzhafter und möglicherweise deshalb vernachlässigt.

Mit anderen Worten, Sie fangen an, diese Feature-Tests zu schreiben, die auf Ihren Benutzergeschichten basieren, und von dort aus gehen Sie durch das Kaninchenloch, bis Ihre Unit-Tests die Abdeckung bieten, die für Ihre Feature-Spezifikationen erforderlich ist. Danach, wenn Ihre Tests natürlich grün sind, beginnt das Spiel von neuem und Sie gehen zurück, um mit einem neuen Funktionstest fortzufahren.

Wie?

Schauen wir uns zwei einfache Beispiele für Feature-Spezifikationen an, mit denen M klassifizierte Missionen erstellen kann, die dann abgeschlossen werden können.

Im Markup haben Sie eine Liste von Missionen, und bei erfolgreichem Abschluss wird eine zusätzliche Klasse erstellt abgeschlossen auf der li dieser besonderen Mission. Unkompliziertes Zeug, richtig? Als ersten Ansatz habe ich mit kleinen, sehr häufigen Refactorings begonnen, die gemeinsames Verhalten in Methoden extrahieren.

spec / features / m_creates_a_mission_spec.rb

"Rubin erfordert 'schienen_helfer'

Funktion "M erstellt Mission" führt Szenario erfolgreich aus "do sign_in_as" [email protected] "

create_classified_mission_named 'Project Moonraker' agent_sees_mission 'Project Moonraker' end 

def create_classified_mission_named (mission_name) visit missions_path click_on 'Create Mission' fill_in 'Mission Name', mit: mission_name click_button 'Submit' end

def agent_sees_mission (missionsname) erwarten (seite) .zur_css 'li.mission_name', Text: missionsname ende

def sign_in_as (email) besuchen root_path fill_in 'Email', mit: email click_button 'Submit' end end "

spec / features / agent_completes_a_mission_spec.rb

"Rubin erfordert 'schienen_helfer'

Feature 'M markiert Mission als abgeschlossen' do Szenario 'erfolgreich abgeschlossen' do sign_in_as '[email protected]'

create_classified_mission_named 'Project Moonraker' mark_mission_as_complete 'Project Moonraker' agent_sees_completed_mission 'Project Moonraker' beendet 

def create_classified_mission_named (mission_name) visit missions_path click_on 'Create Mission' fill_in 'Mission Name', mit: mission_name click_button 'Submit' end

def mark_mission_as_complete (mission_name) innerhalb von "li: contains ('# mission_name')" do click_on 'Mission beendet' Ende 

def agent_sees_completed_mission (mission_name) erwarten (Seite) .to_css 'ul.missions li.mission_name.completed', Text: mission_name end

def sign_in_as (email) besuchen root_path fill_in 'Email', mit: email click_button 'Submit' end end "

Obwohl es natürlich auch andere Möglichkeiten gibt, mit Sachen umzugehen sign_in_as, create_classified_mission_named und so weiter, es ist leicht zu sehen, wie schnell diese Dinge anfangen zu saugen und sich zu stauen.

UI-bezogene Spezifikationen erhalten oft nicht die OO-Behandlung, die sie brauchen / verdienen, denke ich. Sie haben den Ruf, zu wenig Knall für das Geld zu geben, und natürlich lieben es die Entwickler nicht, wenn sie viel Markup-Sachen anfassen müssen. In meinen Augen ist es umso wichtiger, diese Spezifikationen zu TROCKNEN und es macht Spaß, mit ihnen umzugehen, indem sie ein paar Ruby-Klassen werfen.

Lassen Sie uns einen kleinen Zaubertrick machen, bei dem ich die Implementierung des Seitenobjekts fürs Erste verstecke und Ihnen nur das Endergebnis zeige, das auf die obigen Feature-Spezifikationen angewendet wurde:

spec / features / m_creates_a_mission_spec.rb

"Rubin erfordert 'schienen_helfer'

Feature "M erstellt Mission" führt Szenario durch "do sign_in_as" [email protected] "besucht missions_path mission_page = Pages :: Missions.new

mission_page.create_classified_mission_named 'Project Moonraker' erwarten (mission_page) .to_mission_named 'Project Moonraker' end end "

spec / features / agent_completes_a_mission_spec.rb

"Rubin erfordert 'schienen_helfer'

Feature 'M kennzeichnet Mission als abgeschlossen' do Szenario 'erfolgreich' do sign_in_as '[email protected]' besuchen missions_path mission_page = Seiten :: Missions.new

mission_page.create_classified_mission_named 'Project Moonraker' mission_page.mark_mission_as_complete 'Project Moonraker' erwarten (mission_page) .to have_completed_mission_named 'Project Moonraker' end end "

Liest nicht schlecht, hm? Grundsätzlich erstellen Sie auf Ihren Seitenobjekten ausdrucksstarke Wrapper-Methoden, mit denen Sie mit übergeordneten Konzepten umgehen können, anstatt ständig mit dem Darm Ihres Markups herumzuspielen. Ihre extrahierten Methoden erledigen diese Drecksarbeit jetzt, und auf diese Weise ist die Operation mit der Flinte nicht mehr Ihr Problem.

Anders ausgedrückt: Sie kapseln den größten Teil des interaktionsgestörten DOM-Codes aus. Ich muss jedoch sagen, dass manchmal intelligent extrahierte Methoden in Ihren Feature-Spezifikationen ausreichend sind und etwas besser gelesen werden, da Sie den Umgang mit Page Object-Instanzen vermeiden können. Schauen wir uns doch mal die Implementierung an:

specs / support / features / pages / missions.rb

"Ruby-Modul Pages-Klasse-Missionen enthalten Capybara :: DSL

def create_classified_mission_named (mission_name) click_on 'Create Mission' fill_in 'Mission name', mit: mission_name click_button 'Submit' end def mark_mission_as_complete (mission_name) innerhalb von "li: enthält ('# mission_name')" do click_on 'Mission beendet' beenden end def has_mission_named? (mission_name) mission_list.has_css? 'li', Text: mission_name end def has_completed_mission_named? (mission_name) mission_list.has_css? "li.mission-name.completed", Text: mission_name end private def mission_list find ('ul.missions') end end end "

Was Sie sehen, ist ein einfaches altes Ruby-Objekt. Page-Objekte sind im Wesentlichen sehr einfache Klassen. Normalerweise instanziieren Sie Seitenobjekte nicht mit Daten (wenn dies erforderlich ist, natürlich), und erstellen Sie meistens eine Sprache über die API, die ein Benutzer oder ein nicht technischer Stakeholder in einem Team verwenden kann. Wenn Sie über die Benennung Ihrer Methoden nachdenken, ist es ratsam, sich die Frage zu stellen: Wie würde ein Benutzer den Fluss oder die durchgeführte Aktion beschreiben??

Ich sollte vielleicht hinzufügen, dass ohne Capybara die Musik ziemlich schnell stoppt.

Ruby gehören Capybara :: DSL

Sie fragen sich wahrscheinlich, wie diese benutzerdefinierten Matcher funktionieren:

"ruby erwarten (page) .to have_mission_named 'Project Moonraker' erwarten (page) .to have_completed_mission_named 'Project Moonraker'

def has_mission_named? (mission_name)… ende

def has_completed_mission_named? (mission_name)… end "

RSpec generiert diese benutzerdefinierten Matcher basierend auf Prädikatmethoden für Ihre Seitenobjekte. RSpec konvertiert sie durch Entfernen von ? und ändert sich hat zu haben. Boom, Matchers von Grund auf ohne viel Fuzz! Ein bisschen Magie, das gebe ich dir, aber die gute Art von Zauberei würde ich sagen.

Da haben wir unser Page-Objekt bei geparkt specs / support / features / pages / missions.rb, Sie müssen auch sicherstellen, dass das Folgende nicht auskommentiert wird spec / schienen_helper.rb.

ruby Dir [Rails.root.join ('spec / support / ** / *. rb')]. jeweils | f | erfordern f

Wenn Sie auf ein treffen NameFehler mit einem nicht initialisierte konstante Pages, Du wirst wissen, was zu tun ist.

Wenn Sie neugierig sind, was mit dem passiert ist sign_in_as Methode, ich habe es in ein Modul bei extrahiert spec / support / sign_in_helper.rb und wies RSpec an, dieses Modul aufzunehmen. Das hat nichts mit Seitenobjekten direkt zu tun - es macht einfach mehr Sinn, Testfunktionen wie zu speichern Einloggen global zugänglicher als über ein Seitenobjekt.

spec / support / sign_in_helper.rb

ruby module SignInHelper def sign_in_as (email) Besuchen Sie root_path fill_in 'Email', mit: email click_button 'Submit' end end

Und Sie müssen RSpec mitteilen, dass Sie auf dieses Hilfsmodul zugreifen möchten:

spec / spec_helper.rb

"Ruby ... erfordert" support / sign_in_helper "

RSpec.config do | config | config.include SignInHelper… end "

Insgesamt ist es leicht zu erkennen, dass es uns gelungen ist, die Capybara-Besonderheiten wie das Finden von Elementen, das Klicken auf Links usw. zu verbergen. Wir können uns jetzt auf die Funktionalität und weniger auf die tatsächliche Struktur des Markups konzentrieren, das jetzt in einem Seitenobjekt gekapselt ist -Die DOM-Struktur sollte das geringste Ihrer Bedenken sein, wenn Sie etwas testen, das so hoch ist wie die Feature-Spezifikationen.

Beachtung!

Einrichtungsgegenstände wie Werksdaten gehören zu den Spezifikationen und nicht zu Seitenobjekten. Assertionen werden wahrscheinlich auch außerhalb Ihrer Seitenobjekte platziert, um eine Trennung der Bedenken zu erreichen.

Zum Thema gibt es zwei unterschiedliche Perspektiven. Befürworter von Assertions in Page Objects behaupten, dass es dabei hilft, Duplizierungen zu vermeiden. Sie können bessere Fehlermeldungen bereitstellen und einen besseren "Tell, Don't Ask" -Stil erzielen. Befürworter von durchsetzungsfreien Seitenobjekten argumentieren dagegen, dass es besser ist, Verantwortlichkeiten nicht zu vermischen. Das Bereitstellen des Zugriffs auf Seitendaten und die Assertionslogik sind zwei getrennte Anliegen und führen beim Mischen zu aufgeblähten Seitenobjekten. Die Verantwortung von Page Object ist der Zugriff auf den Status von Seiten. Die Assertionslogik gehört zu den Spezifikationen.

Arten von Seitenobjekten

Komponenten repräsentieren die kleinsten Einheiten und sind beispielsweise fokussierter wie ein Formularobjekt.

Seiten kombinieren Sie mehr dieser Komponenten und sind Abstraktionen einer ganzen Seite.

Erfahrungen, Wie Sie bis jetzt erraten haben, erstreckt sich der gesamte Fluss über potenziell viele verschiedene Seiten. Sie sind eher auf hohem Niveau. Sie konzentrieren sich auf den Fluss, den der Benutzer während der Interaktion mit verschiedenen Seiten erlebt. Ein Checkout-Vorgang mit mehreren Schritten ist ein gutes Beispiel, um darüber nachzudenken.

Wann warum?

Es ist eine gute Idee, dieses Entwurfsmuster ein wenig später im Lebenszyklus eines Projekts anzuwenden - wenn Sie in Ihren Feature-Spezifikationen ein wenig Komplexität aufgebaut haben und sich wiederholende Muster wie DOM-Strukturen, extrahierte Methoden oder andere Gemeinsamkeiten erkennen lassen konsistent auf Ihren Seiten.

Sie sollten also wahrscheinlich nicht sofort mit dem Schreiben von Seitenobjekten beginnen. Sie nähern sich allmählich diesen Refactorings, wenn die Komplexität und Größe Ihrer Anwendung / Tests zunehmen. Duplikationen und Refactorings, die durch Page Objects ein besseres Zuhause benötigen, werden im Laufe der Zeit leichter zu erkennen sein.

Meine Empfehlung ist, mit dem Extrahieren von Methoden in Ihren Feature-Spezifikationen lokal zu beginnen. Sobald sie die kritische Masse erreicht haben, sehen sie als offensichtliche Kandidaten für die weitere Extraktion aus, und die meisten von ihnen werden wahrscheinlich in das Profil für Seitenobjekte passen. Fangen Sie klein an, weil eine vorzeitige Optimierung unangenehme Bissspuren hinterlässt!

Abschließende Gedanken

Page Objects bieten Ihnen die Möglichkeit, klarere Spezifikationen zu schreiben, die besser lesen und insgesamt viel ausdrucksvoller sind, da sie auf höherer Ebene liegen. Außerdem bieten sie eine schöne Abstraktion für alle, die gerne OO-Code schreiben. Sie verbergen die Besonderheiten des DOM und ermöglichen es Ihnen auch, über private Methoden zu verfügen, die die Dirty-Arbeit erledigen, ohne der öffentlichen API ausgesetzt zu sein. Extrahierte Methoden in Ihren Feature-Spezifikationen bieten nicht denselben Luxus. Die API von Page Objects muss nicht die winzigen Capybara-Details gemeinsam nutzen.

Bei allen Szenarien, in denen sich die Designimplementierungen ändern, muss sich die Beschreibung der Funktionsweise Ihrer App bei Verwendung von Seitenobjekten nicht ändern. Ihre Feature-Spezifikationen konzentrieren sich mehr auf Interaktionen auf Benutzerebene und interessieren sich nicht so sehr für die Besonderheiten der DOM-Implementierungen. Da Änderungen unvermeidlich sind, werden Seitenobjekte immer wichtiger, wenn Anwendungen wachsen, und sie helfen auch beim Verständnis, wenn die schiere Größe der Anwendung eine drastisch erhöhte Komplexität bedeutet.