Objetos de Ruby Page para conocedores de capibara

Lo que vas a crear

¿Qué son los objetos de página??

Te daré el lanzamiento corto primero. Es un patrón de diseño para encapsular el marcado y las interacciones de la página, específicamente para refactorizar sus características de características. Es una combinación de dos técnicas de refactorización muy comunes: Clase de extracto y Método de extracción-lo que no tiene que suceder al mismo tiempo porque puede ir aumentando gradualmente para extraer una clase completa a través de un nuevo objeto de página..

Esta técnica le permite escribir especificaciones de características de alto nivel que son muy expresivas y SECAS. En cierto modo, son pruebas de aceptación con lenguaje de aplicación. Podría preguntar, ¿no son las especificaciones escritas con Capybara ya de alto nivel y expresivas? Claro, para los desarrolladores que escriben código a diario, las especificaciones de Capybara leen muy bien. ¿Están SECOS fuera de la caja? No realmente-en realidad, ciertamente no!

"ruby feature 'M crea una misión' escenario 'exitosamente' do sign_in_as '[email protected]'

visita a mission_path click_on 'Create Mission' fill_in 'Mission Name', con: 'Project Moonraker' click_button 'Submit' expect (page) .to have_css 'li.mission-name', texto: 'Project Moonraker' end end end "

"ruby feature 'M marca la misión como completa' do escenario 'exitosamente' do sign_in_as '[email protected]'

visita a mission_path click_on 'Create Mission' fill_in 'Mission Name', con: 'Octopussy' click_button 'Enviar' dentro de "li: contiene ('Octopussy')" do click_on 'Mission completed' end expect (page) .to have_css 'ul. misiones li.mission-name.completed ', texto:' Octopussy 'end end "

Cuando observa estos ejemplos de especificaciones de funciones, ¿dónde ve oportunidades para mejorar la lectura y cómo podría extraer información para evitar la duplicación? Además, ¿es este nivel lo suficientemente alto como para modelar fácilmente las historias de usuario y para que las partes interesadas no técnicas lo comprendan??

En mi opinión, hay un par de maneras de mejorar esto y de hacer felices a todos los desarrolladores que pueden evitar juguetear con los detalles de la interacción con el DOM mientras aplican la POO, y otros miembros del equipo que no tienen codificación no tienen problemas para saltar entre las historias de usuario y estas pruebas. El último punto es bueno tenerlo, sin duda, pero los beneficios más importantes provienen principalmente de hacer que sus especificaciones de interacción con DOM sean más sólidas.

La encapsulación es el concepto clave con los objetos de página. Cuando escriba sus especificaciones de características, se beneficiará de una estrategia para extraer el comportamiento que está conduciendo a través de un flujo de prueba. Para el código de calidad, desea capturar las interacciones con determinados conjuntos de elementos en sus páginas, especialmente si se tropieza con patrones repetidos. A medida que su aplicación crece, usted necesita / necesita un enfoque que evite difundir esa lógica en todas sus especificaciones..

"Bueno, ¿no es eso un exceso? ¡La capibara se lee muy bien! ”Dices?

Pregúntese: ¿Por qué no tendría todos los detalles de implementación de HTML en un solo lugar mientras tiene pruebas más estables? ¿Por qué las pruebas de interacción de la interfaz de usuario no deberían tener la misma calidad que las pruebas para el código de aplicación? ¿De verdad quieres parar ahí??

Debido a los cambios cotidianos, su código de Capibara es vulnerable cuando se distribuye todo sobre él, introduce posibles puntos de interrupción. Digamos que un diseñador quiere cambiar el texto de un botón. No biggie, ¿verdad? Pero, ¿desea adaptarse a ese cambio en un envoltorio central para ese elemento en sus especificaciones, o prefiere hacerlo por todas partes? Ya me lo imaginaba!

Hay muchas refactorizaciones posibles para sus especificaciones de características, pero los objetos de página ofrecen las abstracciones más limpias para encapsular el comportamiento de cara al usuario para páginas o flujos más complejos. Sin embargo, no tiene que simular la (s) página (s) completa (s), centrándose en los bits esenciales que son necesarios para los flujos de usuarios. No hay necesidad de exagerar!

Pruebas de aceptación / Especificaciones de características

Antes de pasar al tema central, me gustaría dar un paso atrás para las personas nuevas en todo el negocio de las pruebas y aclarar algunas de las palabras que son importantes en este contexto. Las personas más familiarizadas con TDD no se perderán mucho si saltan adelante.

¿De qué estamos hablando aqui? Las pruebas de aceptación generalmente se presentan en una etapa posterior de los proyectos para evaluar si ha estado construyendo algo de valor para sus usuarios, el propietario del producto u otra parte interesada. Estas pruebas suelen ser realizadas por los clientes o sus usuarios. Es una especie de verificación si los requisitos se cumplen o no. Hay algo como una pirámide para todo tipo de capas de prueba, y las pruebas de aceptación están cerca de la parte superior. Debido a que este proceso a menudo incluye personas no técnicas, un lenguaje de alto nivel para escribir estas pruebas es un activo valioso para la comunicación de ida y vuelta.

Las especificaciones de características, por otro lado, son un poco más bajas en la cadena de alimentos de prueba. Mucho más alto nivel que las pruebas unitarias, que se centran en los detalles técnicos y la lógica empresarial de sus modelos, las especificaciones de las características describen los flujos en y entre sus páginas.

Herramientas como Capybara lo ayudan a evitar hacer esto manualmente, lo que significa que rara vez tiene que abrir su navegador para probar cosas manualmente. Con este tipo de pruebas, nos gusta automatizar estas tareas tanto como sea posible y probar la interacción a través del navegador mientras se escriben afirmaciones contra páginas. Por cierto, no usas obtener, poner, enviar o borrar como lo haces con las especificaciones de solicitud.

Las características de las características son muy similares a las pruebas de aceptación; a veces siento que las diferencias son demasiado borrosas como para preocuparse realmente por la terminología. Escribes pruebas que ejercitan toda tu aplicación, que a menudo involucra un flujo de múltiples pasos de acciones del usuario. Estas pruebas de interacción muestran si sus componentes funcionan en armonía cuando se juntan.

En Ruby land, son los principales protagonistas cuando tratamos con Page Objects. Las especificaciones de las características en sí ya son muy expresivas, pero pueden optimizarse y limpiarse extrayendo sus datos, comportamiento y marcas en una clase o clases separadas.

Espero que aclarar esta terminología borrosa te ayude a ver que tener objetos de página es como hacer pruebas de nivel de aceptación mientras escribes especificaciones de funciones..

Capibara

Tal vez deberíamos repasar esto muy rápido también. Esta biblioteca se describe a sí misma como un "marco de prueba de aceptación para aplicaciones web". Puede simular las interacciones del usuario con sus páginas a través de un lenguaje específico de dominio muy potente y conveniente. En mi opinión personal, RSpec emparejado con Capybara ofrece la mejor manera de escribir las especificaciones de sus características en este momento. Le permite visitar páginas, completar formularios, hacer clic en enlaces y botones, buscar marcas en sus páginas y puede combinar fácilmente todo tipo de estos comandos para interactuar con sus páginas a través de sus pruebas..

Básicamente, puedes evitar abrir el navegador por ti mismo para probar esto manualmente la mayor parte del tiempo, lo que no solo es menos elegante, sino también mucho más lento y propenso a errores. Sin esta herramienta, el proceso de "prueba externa" (el que conduzca su código de pruebas de alto nivel a sus pruebas de nivel de unidad) sería mucho más doloroso y, por lo tanto, más desatendido.

En otras palabras, comienza a escribir estas pruebas de características que se basan en sus historias de usuario, y desde allí baja por el agujero del conejo hasta que sus pruebas de unidad proporcionan la cobertura que necesitan las especificaciones de sus características. Después de eso, cuando las pruebas están en verde, el juego comienza de nuevo y usted vuelve a subir para continuar con una nueva prueba de características..

Cómo?

Veamos dos ejemplos simples de especificaciones de características que permiten a M crear misiones clasificadas que luego pueden completarse.

En el marcado, tienes una lista de misiones y una finalización exitosa crea una clase adicional terminado sobre el li de esa misión particular. Cosas directas, ¿verdad? Como primer enfoque, comencé con refactorizaciones pequeñas y muy comunes que extraen el comportamiento común en métodos..

spec / features / m_creates_a_mission_spec.rb

"ruby require 'rails_helper'

la característica 'M crea la misión' escenario 'exitosamente' do sign_in_as '[email protected]'

create_classified_mission_named 'Project Moonraker' agent_sees_mission 'Project Moonraker' final 

def create_classified_mission_named (mission_name) visitaissions_path click_on 'Create Mission' fill_in 'Mission Name', con: mission_name click_button 'Submit' end

def agent_sees_mission (mission_name) expect (page) .to have_css 'li.mission-name', texto: mission_name end

def sign_in_as (email) visita root_path fill_in 'Email', con: email click_button 'Submit' end end end "

spec / features / agent_completes_a_mission_spec.rb

"ruby require 'rails_helper'

la característica 'M marca la misión como completada' hacer el escenario 'con éxito' hacer sign_in_as '[email protected]'

create_classified_mission_named 'Project Moonraker' mark_mission_as_complete 'Project Moonraker' agent_sees_completed_mission 'Project Moonraker' final 

def create_classified_mission_named (mission_name) visitaissions_path click_on 'Create Mission' fill_in 'Mission Name', con: mission_name click_button 'Submit' end

def mark_mission_as_complete (mission_name) dentro de "li: contiene ('# mission_name')" do click_on 'Mission completed' end end 

def agent_sees_completed_mission (mission_name) expect (page) .to have_css 'ul.missions li.mission-name.completed', texto: mission_name end

def sign_in_as (email) visita root_path fill_in 'Email', con: email click_button 'Submit' end end end "

Aunque hay otras formas, por supuesto, para lidiar con cosas como sign_in_as, create_classified_mission_named y así sucesivamente, es fácil ver qué tan rápido estas cosas pueden comenzar a apestar y acumularse.

Las especificaciones relacionadas con la interfaz de usuario a menudo no reciben el tratamiento OO que necesitan / merecen, creo. Tienen la reputación de ofrecer muy pocos beneficios, y por supuesto, a los desarrolladores no les gustan mucho los momentos en los que tienen que tocar mucho las cosas de marcado. En mi opinión, eso hace que sea aún más importante SECAR estas especificaciones y hacer que sea divertido lidiar con ellas lanzando un par de clases de Ruby.

Hagamos un pequeño truco de magia donde oculto la implementación del objeto de página por ahora y solo te muestro el resultado final aplicado a las características de las características anteriores:

spec / features / m_creates_a_mission_spec.rb

"ruby require 'rails_helper'

la característica 'M crea la misión' escenario 'exitosamente' do sign_in_as '[email protected]' visitaissions_path mission_page = Páginas :: Misiones.nuevo

mission_page.create_classified_mission_named 'Project Moonraker' expect (mission_page) .to have_mission_named 'Project Moonraker' end end "

spec / features / agent_completes_a_mission_spec.rb

"ruby require 'rails_helper'

la función 'M marca la misión como completada' hacer el escenario 'con éxito' hacer sign_in_as '[email protected]' visitarissions_path mission_page = Pages :: Missions.new

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

No lee tan mal, ¿eh? Básicamente, creas métodos de envoltura expresivos en tus objetos de página que te permiten lidiar con conceptos de alto nivel, en lugar de jugar con los intestinos de tu marca todo el tiempo. Sus métodos extraídos realizan este tipo de trabajo sucio ahora, y de esa manera la cirugía con escopeta ya no es su problema.

Dicho de otra manera, usted encapsula la mayoría de los ruidosos, el código interactivo de DOM de down-in-the-weeds. Sin embargo, debo decir que a veces los métodos extraídos de manera inteligente en las especificaciones de sus funciones son suficientes y se leen un poco mejor, ya que puede evitar tratar con instancias de objetos de página. De todos modos, echemos un vistazo a la implementación:

especificaciones / soporte / características / páginas / misiones.rb

"módulo ruby ​​Páginas clase misiones incluyen Capybara :: DSL

def create_classified_mission_named (mission_name) click_on 'Create Mission' fill_in 'Mission name', con: mission_name click_button 'Enviar' end def mark_mission_as_complete (mission_name) dentro de "li: contiene ('# mission_name')" do click_on 'Mission completed' end end def has_mission_named? (mission_name) mission_list.has_css? 'li', texto: mission_name end def has_completed_mission_named? (mission_name) mission_list.has_css? 'li.mission-name.completed', texto: mission_name end private def mission_list find ('ul.missions') end end end end end "

Lo que ves es un objeto de Ruby antiguo y simple. Los objetos de la página son, en esencia, clases muy simples. Normalmente, no crea una instancia de los objetos de la página con datos (cuando es necesario, por supuesto, puede) y crea principalmente un lenguaje a través de la API que un usuario o un interesado no técnico en un equipo podría usar. Cuando piense en nombrar sus métodos, creo que es un buen consejo preguntarse: ¿Cómo describiría un usuario el flujo o la acción tomada??

Tal vez debería agregar que sin incluir Capibara, la música se detiene bastante rápido.

ruby include Capybara :: DSL

Probablemente te preguntes cómo funcionan estos emparejadores personalizados:

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

def has_mission_named? (mission_name) ... end

def has_completed_mission_named? (mission_name) ... end "

RSpec genera estos emparejadores personalizados basados ​​en métodos de predicado en sus objetos de página. RSpec los convierte eliminando el ? y cambios tiene a tener. ¡Boom, matchers desde cero sin mucho fuzz! Un poco de magia, te daré eso, pero el buen tipo de magia, diría.

Desde que estacionamos nuestro objeto de página en especificaciones / soporte / características / páginas / misiones.rb, También debe asegurarse de que no se comente lo siguiente en spec / rails_helper.rb.

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

Si te topas con un NameError con un Páginas constantes sin inicializar, sabrás que hacer.

Si tienes curiosidad por lo que le pasó a la sign_in_as Método, lo extraje en un módulo en spec / support / sign_in_helper.rb y le dijo a RSpec que incluya ese módulo. Esto no tiene nada que ver con los objetos de página directamente, simplemente tiene más sentido almacenar funciones de prueba como registrarse de una manera más accesible a nivel mundial que a través de un objeto de página.

spec / support / sign_in_helper.rb

ruby module SignInHelper def sign_in_as (email) visita root_path fill_in 'Email', con: email click_button 'Submit' end end

Y debe informar a RSpec que desea acceder a este módulo auxiliar:

spec / spec_helper.rb

"ruby ... require 'support / sign_in_helper'

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

En general, es fácil ver que logramos ocultar los elementos específicos de búsqueda de capibara, hacer clic en los enlaces, etc. Ahora podemos centrarnos en la funcionalidad y menos en la estructura real del marcado, que ahora está encapsulado en un objeto de página. -la estructura DOM debería ser la menor de tus preocupaciones cuando pruebas algo tan alto como las características de las características.

Atención!

Los elementos de configuración, como los datos de fábrica, pertenecen a las especificaciones y no a los objetos de página. Además, las afirmaciones probablemente estén mejor ubicadas fuera de los objetos de página para lograr una separación de preocupaciones.

Hay dos perspectivas diferentes sobre el tema. Los defensores de poner aserciones en Page Objects dicen que ayuda a evitar la duplicación de aserciones. Puede proporcionar mejores mensajes de error y lograr un mejor estilo de "Decir, no preguntar". Por otro lado, los defensores de los objetos de página sin afirmación argumentan que es mejor no mezclar responsabilidades. Proporcionar acceso a los datos de la página y la lógica de aserción son dos preocupaciones separadas y conducen a Objetos de página hinchados cuando se mezclan. La responsabilidad del objeto de la página es el acceso al estado de las páginas, y la lógica de aserción pertenece a las especificaciones.

Tipos de objetos de página

Componentes representan las unidades más pequeñas y están más enfocadas, como un objeto de formulario, por ejemplo.

Páginas Combina más de estos componentes y son abstracciones de una página completa..

Experiencias, como ya ha adivinado, abarque todo el flujo en potencialmente muchas páginas diferentes. Son más de alto nivel. Se centran en el flujo que el usuario experimenta mientras interactúan con varias páginas. Un flujo de pago que tiene un par de pasos es un buen ejemplo para pensar sobre esto.

Cuando por qué?

Es una buena idea aplicar este patrón de diseño un poco más adelante en el ciclo de vida de un proyecto, cuando haya acumulado un poco de complejidad en las especificaciones de sus características y cuando pueda identificar patrones repetitivos como estructuras DOM, métodos extraídos u otros elementos comunes que sean consistente en tus páginas.

Así que probablemente no deberías comenzar a escribir objetos de página de inmediato. Se aproxima a estas refactorizaciones gradualmente cuando la complejidad y el tamaño de su aplicación / pruebas aumentan. Las duplicaciones y refactorizaciones que necesitan un mejor hogar a través de los objetos de página serán más fáciles de detectar con el tiempo.

Mi recomendación es comenzar con los métodos de extracción en las especificaciones de sus características a nivel local. Una vez que alcancen la masa crítica, se verán como candidatos obvios para una mayor extracción, y la mayoría de ellos probablemente se ajustarán al perfil de los objetos de página. Comience con poco, porque la optimización prematura deja marcas de mordidas desagradables!

Pensamientos finales

Los objetos de página le brindan la oportunidad de escribir especificaciones más claras que leen mejor y en general son mucho más expresivas porque son más de alto nivel. Además de eso, ofrecen una buena abstracción para todos los que les gusta escribir código OO. Ocultan los detalles específicos del DOM y también le permiten tener métodos privados que hacen el trabajo sucio mientras no están expuestos a la API pública. Los métodos extraídos en las características de sus características no ofrecen el mismo lujo. La API de los objetos de página no necesita compartir los detalles básicos de Capybara.

Para todas las situaciones en las que cambian las implementaciones de diseño, sus descripciones de cómo debería funcionar su aplicación no tienen por qué cambiar cuando usa Objetos de página: las especificaciones de sus características están más enfocadas en las interacciones de nivel de usuario y no les importan mucho los detalles de las implementaciones de DOM. Dado que el cambio es inevitable, los Page Objects se vuelven críticos cuando las aplicaciones crecen y también ayudan a comprender cuando el tamaño de la aplicación significa una complejidad drásticamente mayor.