Testen der Benutzerfunktionalität von Websites mit Capybara-Seitenobjekten

Seitenobjekte können als leistungsstarke Methode zur Abstraktion (Isolierung) Ihrer Tests von der technischen Implementierung verwendet werden. Es ist wichtig zu bedenken, dass sie (Seitenobjekte) verwendet werden können, um die Stabilität von Tests zu erhöhen und das Prinzip von DRY beizubehalten (wiederholen Sie sich nicht) - indem Sie die Funktionalität (Website) in einfachen Methoden einkapseln.

Mit anderen Worten


Das Seitenobjekt ist eine Instanz einer Klasse, die die Benutzeroberfläche von der Testumgebung abstrahiert (isoliert), Methoden für die Interaktion mit der Benutzeroberfläche vorstellt und die erforderlichen Informationen extrahiert.

Terminologie


Der Begriff Seitenobjekt ist ein zu allgemeines Konzept. Nach meiner Erfahrung umfasst Page Object die folgenden 3 Typen:

  • Komponentenobjekte repräsentieren bestimmte Komponenten oder Widgets in der Benutzeroberfläche. Zum Beispiel: Tabellen, Menüs, Artikel und andere Blöcke, die eine Gruppe von Komponenten enthalten.
  • Das Seitenobjekt beschreibt einen bestimmten Bereich oder eine bestimmte Benutzeroberfläche in einer Webanwendung. Es kann aus mehreren Komponentenobjekten bestehen und praktische Methoden für die Interaktion mit der in diesem Objekt enthaltenen Abstraktion enthalten.
  • Erfahrung wird verwendet, um komplexe Funktionen zu gruppieren, deren Testen mehrere Schritte oder die Interaktion mit mehreren Seiten erfordert. Aus eigener Erfahrung habe ich dieses Konzept verwendet, um komplexes Verhalten auf einer Seite zu abstrahieren (Testen von Lernseiten, Erstellen eines neuen Benutzers usw.)

Beispiele


Betrachten Sie den einfachen RSpec Capybara- Test, der Blogs erstellt und keine Seitenobjekte verwendet:

require 'feature_helper' feature 'Blog management', type: :feature do scenario 'Successfully creating a new blog' do visit '/' click_on 'Form Examples' expect(page).to have_content('Create Blog') fill_in 'blog_title', with: 'My Blog Title' fill_in 'blog_text', with: 'My new blog text' click_on 'Save Blog' expect(page).to have_selector('.blog--show') expect(page).to have_content('My Blog Title') expect(page).to have_content('My new blog text') end scenario 'Entering no data' do visit '/' click_on 'Form Examples' expect(page).to have_content('Create Blog') click_on 'Save Blog' expect(page).to have_content('4 errors stopped this form being submitted') expect(page).to have_content("Title can't be blank") expect(page).to have_content("Text can't be blank") expect(page).to have_content('Title is too short') expect(page).to have_content('Text is too short') end end 

Schauen wir uns den Code genauer an, er hat mehrere Probleme. Es gibt folgende Aktionen: Wechseln zur entsprechenden Seite, Interaktion mit der Seite und Überprüfen des Inhalts. Ein Teil des Codes ist dupliziert, dies kann jedoch durch Einhaltung des DRY- Prinzips behoben werden.

Es ist wichtig zu verstehen, dass dieser Code schwer zu pflegen ist, wenn sich Änderungen in der zu testenden Anwendung ergeben. Beispielsweise können sich Elementklassen, Namen und Bezeichner ändern, was regelmäßige Aktualisierungen des Testcodes erfordert.

Auch in diesem Code gibt es keinen 'semantischen Kontext', es ist schwer zu verstehen, welche Codezeilen logisch gruppiert sind.

Einführung in Seitenobjekte


Wie im Abschnitt zur Terminologie erläutert, können Seitenobjekte zur Darstellung von Abstraktionen auf Präsentationsebene verwendet werden.

Wenn Sie das vorherige Beispiel verwenden und mit Page Object neue Blogs erstellen und Blogs anzeigen, können Sie den Code des vorherigen Beispiels löschen.

Nachdem bestimmte Informationen zur technischen Implementierung entfernt wurden, sollte das Endergebnis (Code) lesbar sein und keine spezifischen Informationen zur Benutzeroberfläche (ID, CSS-Klassen usw.) enthalten.

 require 'feature_helper' require_relative '../pages/new_blog' require_relative '../pages/view_blog' feature 'Blog management', type: :feature do let(:new_blog_page) { ::Pages::NewBlog.new } let(:view_blog_page) { ::Pages::ViewBlog.new } before :each do new_blog_page.visit_location end scenario 'Successfully creating a new blog' do new_blog_page.create title: 'My Blog Title', text: 'My new blog text' expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text' end scenario 'Entering no data' do new_blog_page.create title: '', text: '' expect(view_blog_page).to_not have_loaded expect(new_blog_page).to have_errors "Title can't be blank", "Text can't be blank", "Title is too short", "Text is too short" end end 

Seitenobjekte erstellen

Der erste Schritt beim Erstellen von Seitenobjekten besteht darin, eine grundlegende Seitenklassenstruktur zu erstellen:

 module Pages class NewBlog include RSpec::Matchers include Capybara::DSL # ... end end 

Verbinden (Aktivieren) von Capybara :: DSL, damit Instanzen von Seitenobjekten die in Capybara verfügbaren Methoden verwenden können

 has_css? '.foo' has_content? 'hello world' find('.foo').click 

Außerdem habe ich verwendet
RSpec :: Matchers einschließen
In den obigen Beispielen wird die Erwartungs- RSpec-Bibliothek verwendet.

Verstoßen Sie nicht gegen die Vereinbarung. Seitenobjekte sollten keine Erwartungen (Erwartungen) enthalten . Gegebenenfalls bevorzuge ich diesen Ansatz jedoch, um mich auf die in Capybara integrierten Mechanismen für die Handhabung von Bedingungen zu stützen.

Der folgende Capybara- Code erwartet beispielsweise das Vorhandensein von 'foo' in Seitenobjekten (in diesem Fall self ):

 expect(self).to have_content 'foo' 

Im folgenden Code jedoch:

 expect(page_object.content).to match 'foo' 

Unerwartete Fehler sind möglich (möglicherweise tritt ein schwebender Test auf), da page_object.content sofort auf Übereinstimmung mit der Bedingung überprüft wird und möglicherweise noch nicht deklariert wurde. Für weitere Beispiele würde ich empfehlen, die zuverlässigen asynchronen Integrationstests von Thoughtbot mit Capybara zu lesen.

Methodenerstellung


Wir können den Ort (die Region), von dem wir Daten erhalten möchten, im Rahmen einer Methode abstrahieren (beschreiben):

 def visit_location visit '/blogs/new' # It can be beneficial to assert something positive about the page # before progressing with your tests at this point # # This can be useful to ensures that the page has loaded successfully, and any # asynchronous JavaScript has been loaded and retrieved etc. # # This is required to avoid potential race conditions. expect(self).to have_loaded end def has_loaded? self.has_selector? 'h1', text: 'Create Blog' end 

Es ist wichtig, die semantisch korrekten Namen für die Methoden für Ihre Seitenobjekte auszuwählen

 def create(title:, text:) # ... end def has_errors?(*errors) # ... end def has_error?(error) # ... end 

Im Allgemeinen ist es wichtig, das Prinzip der funktional integrierten Methoden zu befolgen und nach Möglichkeit das Prinzip der Einzelverantwortung (Prinzip der Einzelverantwortung) einzuhalten.

Komponentenobjekte


In unserem Beispiel verwenden wir die NewBlog-Klasse, es muss jedoch keine Implementierung erstellt werden.

Da wir mit dem Formular interagieren, können wir zusätzlich eine Klasse einführen, um diese Komponente darzustellen:

 # ... def create(title:, text:) blog_form.new.create title: title, text: text end # ... private def blog_form ::Components::BlogForm end 

Wo die Implementierung von Methoden für BlogForm ausgeblendet werden kann:

 module Components class BlogForm include RSpec::Matchers include Capybara::DSL def create(title:, text:) within blog_form do fill_in 'blog_title', with: title fill_in 'blog_text', with: text click_on 'Save Blog' end end private def blog_form find('.blog--new') end end end 

Alle zusammen


Mit den oben genannten Klassen können Sie jetzt die Seitenobjekte Ihrer Seite als Teil der Beschreibung des Objekts abfragen und instanziieren.

 require 'feature_helper' require_relative '../pages/new_blog' require_relative '../pages/view_blog' feature 'Blog management', type: :feature do let(:new_blog_page) { ::Pages::NewBlog.new } let(:view_blog_page) { ::Pages::ViewBlog.new } # ... end 

Hinweis: Ich habe absichtlich ein Seitenobjekt manuell oben in der Objektdatei erstellt. In einigen RSpec-Tests kann es praktisch sein, alle Unterstützungsdateien automatisch herunterzuladen und in Objektdateien darauf zuzugreifen. Dies kann jedoch zu übermäßiger Arbeitsbelastung führen, wenn große Codeteile verwendet werden. Dies führt insbesondere zu einem langsamen Start und möglichen unbeabsichtigten zyklischen Abhängigkeiten.

Seitenobjekte aufrufen


Jetzt haben wir in jedem Szenario Zugriff auf Instanzen von new_blog_page und view_blog_page :

 scenario 'Successfully creating a new blog' do new_blog_page.create title: 'My Blog Title', text: 'My new blog text' expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text' end 

Namenskonventionen / Prädikatmethoden


Wie bei den meisten Dingen in Rails / Ruby gibt es Konventionen, die auf den ersten Blick unbedeutend (nicht bindend) erscheinen.

In unseren Tests haben wir mit have_loaded und have_blog mit dem Seitenobjekt interagiert :

 expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text' 

Die Methodennamen unseres Seitenobjekts wurden jedoch tatsächlich geladen. und has_blog? ::

 def has_loaded? # ... end def has_blog?(title:, text:) # ... end 

Dies ist eine subtile Unterscheidung, die Aufmerksamkeit erfordert. Für weitere Informationen zu dieser Konvention würde ich empfehlen, den folgenden Link zu Prädikat-Matchern zu lesen.

Git, der in den Beispielen verwendete Quellcode
Das Original

Source: https://habr.com/ru/post/de466527/


All Articles