Probar la funcionalidad del usuario del sitio web con los objetos de página de Capybara

Los objetos de página se pueden usar como un poderoso método de abstracción (aislamiento) de sus pruebas de la implementación técnica. Es importante recordar que estos (Objetos de página) se pueden usar para aumentar la estabilidad de las pruebas y mantener el principio de DRY (no se repita), encapsulando la funcionalidad (sitio web) en métodos simples.

En otras palabras


Page Object es una instancia de una clase que abstrae (aísla) la interfaz de usuario del entorno de prueba, presenta métodos para interactuar con la interfaz de usuario y extrae la información necesaria.

Terminología


El término Objeto de página es un concepto demasiado general. En mi experiencia, Page Object incluye los siguientes 3 tipos:

  • Los objetos componentes representan componentes o widgets específicos en la interfaz de usuario. Por ejemplo: tablas, menús, artículos y otros bloques que contienen un grupo de componentes.
  • Objeto de página describe un área específica o interfaz de usuario en una aplicación web. Puede consistir en varios Objetos componentes y puede contener métodos convenientes para interactuar con la abstracción contenida dentro de este objeto.
  • La experiencia se utiliza para agrupar funcionalidades complejas, cuya prueba requiere varios pasos o interacción con varias páginas. Desde mi propia experiencia, utilicé este concepto para abstraer comportamientos complejos en una página (probar páginas educativas, crear un nuevo usuario, etc.)

Ejemplos


Considere la simple prueba RSpec Capybara , que crea blogs y no utiliza objetos de página:

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 

Echemos un vistazo más de cerca al código; tiene varios problemas. Existen las siguientes acciones: transición a la página correspondiente, interacción con la página y verificación del contenido. Parte del código está duplicado, pero esto puede solucionarse siguiendo el principio DRY .

Es importante comprender que este código es difícil de mantener si hay cambios en la aplicación bajo prueba. Por ejemplo, las clases de elementos, los nombres y los identificadores pueden cambiar, lo que requiere actualizaciones periódicas del código de prueba.

Además, en este código no existe un 'contexto semántico', es difícil entender qué líneas de código están agrupadas lógicamente.

Introducción a los objetos de página


Como se discutió en la sección de terminología, los objetos de página se pueden usar para representar abstracciones a nivel de presentación.

Tomando el ejemplo anterior y usando Page Object para crear nuevos blogs y ver blogs, podemos borrar el código del ejemplo anterior.

Una vez eliminada la información específica sobre la implementación técnica, el resultado final (código) debe ser legible y no debe contener información específica sobre la interfaz de usuario (id, clases css, etc.).

 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 

Crear objetos de página

El primer paso para crear objetos de página es crear una estructura de clase de página básica :

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

Conectar (habilitar) Capybara :: DSL para permitir que las instancias de Objetos de página utilicen los métodos disponibles en Capybara

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

Además, usé
incluye RSpec :: Matchers
en los ejemplos anteriores para usar la biblioteca de expectativas RSpec.

No viole el acuerdo, los Objetos de página no deben incluir esperar (expectativas) . Sin embargo, donde sea apropiado, prefiero este enfoque para confiar en los mecanismos incorporados de Capybara para manejar las condiciones.

Por ejemplo, el siguiente código de Carpincho esperará la presencia de 'foo' dentro de Page Objects (en este caso, self ):

 expect(self).to have_content 'foo' 

Sin embargo, en el siguiente código:

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

Son posibles errores inesperados (puede ocurrir una prueba flotante), ya que page_object.content se verifica de inmediato para verificar el cumplimiento de la condición y es posible que aún no se haya declarado. Para obtener más ejemplos, recomendaría leer las pruebas de integración asincrónica confiables de escribir de thinkbot con Capybara .

Método de creación


Podemos abstraer (describir) el lugar (región) del que queremos obtener datos, en el marco de un método:

 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 importante elegir los nombres semánticamente correctos para los métodos para sus Objetos de página

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

En general, es importante seguir el principio de métodos funcionalmente integrados y, cuando sea posible, adherirse al principio de responsabilidad única (Principio de responsabilidad única).

Objetos componentes


En nuestro ejemplo, usamos la clase NewBlog, pero no hay implementación para crear.

Como interactuamos con el formulario, también podríamos introducir una clase para representar este componente:

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

Donde se puede ocultar la implementación de métodos para BlogForm:

 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 

Todos juntos


Usando las clases anteriores, ahora puede consultar e instanciar los Objetos de página de su página como parte de la descripción del objeto.

 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 

Nota: intencionalmente creé un objeto de página manualmente en la parte superior del archivo de objeto. En algunas pruebas de RSpec, puede ser conveniente descargar automáticamente todos los archivos de soporte y proporcionarles acceso a ellos en archivos de objetos, sin embargo, esto puede generar cargas de trabajo excesivas cuando se utilizan grandes piezas de código. En particular, esto conducirá a un inicio lento y posibles dependencias cíclicas no intencionadas.

Llamar a objetos de página


Ahora, en cada escenario, tendremos acceso a las instancias de new_blog_page y 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 

Convenciones de nomenclatura / métodos de predicado


Como con la mayoría de las cosas en Rails / Ruby, hay convenciones que pueden parecer insignificantes (no vinculantes) completamente de un vistazo.

En nuestras pruebas, interactuamos con el objeto de página usando have_loaded y have_blog :

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

Sin embargo, ¿los nombres de los métodos de nuestro objeto de página realmente se han cargado? y has_blog? :

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

Esta es una distinción sutil que necesita atención. Para obtener más información sobre esta convención, recomendaría leer el siguiente enlace de comparadores de predicados .

Git, el código fuente usado en los ejemplos
El original

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


All Articles