Objets de la page Ruby pour les connaisseurs de Capybara

Ce que vous allez créer

Que sont les objets de page?

Je vais d'abord vous donner le ton le plus court. Il s'agit d'un modèle de conception destiné à encapsuler les interactions entre balises et pages, spécialement pour refactoriser les spécifications de vos fonctionnalités. C'est une combinaison de deux techniques de refactorisation très courantes: Extrait classe et Extrait méthode-ce qui ne doit pas nécessairement se produire en même temps, car vous pouvez construire progressivement l'extraction de toute une classe via un nouvel objet de page.

Cette technique vous permet d’écrire des spécifications de haut niveau très expressives et DRY. D'une certaine manière, ce sont des tests d'acceptation avec un langage d'application. Vous vous demandez peut-être si les spécifications avec Capybara ne sont pas déjà de haut niveau et expressives? Bien sûr, pour les développeurs qui écrivent du code quotidiennement, les spécifications de Capybara se lisent très bien. Sont-ils secs en dehors de la boîte? Pas vraiment-en fait, certainement pas!

"La fonctionnalité ruby ​​'M crée une mission' scénario 'avec succès' sign_in_as '[email protected]'

visitez missions_path click_on 'Create Mission' fill_in 'Nom de la mission', avec: 'Project Moonraker' click_button 'Soumettre' attend (page) .to have_css 'li.mission-name', texte: 'Project Moonraker' end end "

"La fonction ruby" M marque la mission comme "complète" scénario "avec succès" sign_in_as "[email protected]"

visiter missions_path click_on 'Create Mission' fill_in 'Nom de la mission', avec: 'Octopussy' click_button 'Soumettre' au sein de "li: contient ('Octopussy')", cliquez sur "Mission terminée" et attendez (page) .à avoir_css 'ul. missions li.mission-name.completed ', texte:' Octopussy 'end end "

Lorsque vous regardez ces exemples de spécifications de fonctionnalités, où voyez-vous des opportunités pour améliorer cette lecture, et comment pouvez-vous extraire des informations pour éviter les doublons? En outre, ce niveau élevé est-il suffisant pour modéliser facilement les user stories et pour permettre aux parties prenantes non techniques de comprendre?

Dans mon esprit, il y a plusieurs façons d'améliorer cela et de rendre tout le monde heureux: des développeurs qui peuvent éviter de jouer avec les détails d'interaction avec le DOM lors de l'application de la programmation orientée objet, et d'autres membres de l'équipe non codante n'ayant aucune difficulté à sauter entre les histoires d'utilisateurs. et ces tests. Le dernier point est certes positif, mais les avantages les plus importants proviennent principalement de la robustesse de vos spécifications avec interaction DOM..

L'encapsulation est le concept clé des objets de page. Lorsque vous rédigez vos spécifications de fonctionnalité, vous bénéficiez d'une stratégie permettant d'extraire le comportement conduisant à travers un flux de test. Pour le code de qualité, vous souhaitez capturer les interactions avec des ensembles particuliers d’éléments de vos pages, en particulier si vous tombez sur des motifs répétitifs. Au fur et à mesure que votre application se développe, vous souhaitez / avez besoin d'une approche évitant la propagation de cette logique dans vos spécifications..

”Et bien n'est-ce pas exagéré? Capybara lit très bien!?

Demandez-vous: pourquoi n’auriez-vous pas tous les détails de l’implémentation HTML au même endroit tout en ayant des tests plus stables? Pourquoi les tests d'interaction de l'interface utilisateur n'auraient-ils pas la même qualité que les tests de code d'application? Voulez-vous vraiment vous arrêter là?

En raison de changements quotidiens, votre code Capybara est vulnérable lorsqu’il est répandu partout, il introduit des points de rupture possibles. Supposons qu'un concepteur souhaite modifier le texte d'un bouton. Pas trop grave, non? Mais voulez-vous vous adapter à ce changement dans un wrapper central pour cet élément de vos spécifications ou préférez-vous le faire partout? J'ai pensé ainsi!

Il existe de nombreuses refactorisations possibles pour vos spécifications d'entités, mais les objets de page offrent les abstractions les plus propres pour encapsuler le comportement face à l'utilisateur pour les pages ou les flux plus complexes. Vous n'êtes pas obligé de simuler la ou les pages entières, mais concentrez-vous sur les éléments essentiels nécessaires aux flux utilisateur. Pas besoin d'en faire trop!

Tests d'acceptation / Spécifications techniques

Avant de passer au cœur du sujet, je voudrais prendre du recul pour les débutants dans le secteur des tests et clarifier une partie du jargon qui est important dans ce contexte. Les gens plus familiers avec le TDD ne manqueront pas grand chose s'ils sautent la tête.

de quoi parle-t-on ici? Les tests d'acceptation interviennent généralement à un stade ultérieur des projets pour évaluer si vous avez créé quelque chose de précieux pour vos utilisateurs, le propriétaire du produit ou un autre intervenant. Ces tests sont généralement effectués par les clients ou vos utilisateurs. C'est en quelque sorte une vérification si les exigences sont remplies ou non. Il existe une sorte de pyramide pour toutes sortes de couches de test et les tests d'acceptation se situent au sommet. Étant donné que ce processus implique souvent des personnes non techniques, un langage de haut niveau pour l'écriture de ces tests constitue un atout précieux pour la communication entre utilisateurs..

Les caractéristiques techniques, en revanche, sont un peu plus basses dans la chaîne alimentaire testée. Beaucoup plus sophistiqués que les tests unitaires, qui se concentrent sur les détails techniques et la logique métier de vos modèles, les spécifications de fonctionnalité décrivent les flux sur et entre vos pages..

Des outils tels que Capybara vous aident à éviter de le faire manuellement, ce qui signifie que vous devez rarement ouvrir votre navigateur pour tester des éléments manuellement. Avec ce type de tests, nous souhaitons automatiser le plus possible ces tâches et tester l’interaction via le navigateur tout en écrivant des assertions sur des pages. Au fait, vous n'utilisez pas obtenir, mettre, poster ou effacer comme vous le faites avec les spécifications de demande.

Les spécifications des fonctionnalités ressemblent beaucoup aux tests d'acceptation. Parfois, les différences sont trop floues pour que l'on se soucie de la terminologie. Vous écrivez des tests qui exercent l’ensemble de votre application, ce qui implique souvent un flux d’actions utilisateur en plusieurs étapes. Ces tests d’interaction montrent si vos composants fonctionnent en harmonie lorsqu’ils sont réunis..

En Ruby Land, ils sont le protagoniste principal lorsque nous traitons avec des objets de la page. Les caractéristiques des fonctionnalités elles-mêmes sont déjà très expressives, mais elles peuvent être optimisées et nettoyées en extrayant leurs données, leur comportement et leur marquage dans une ou plusieurs classes séparées..

J'espère que la clarification de cette terminologie floue vous aidera à comprendre qu'obtenir des objets de page, c'est un peu comme effectuer des tests de niveau d'acceptation lors de la rédaction de spécifications de fonctionnalités..

Capybara

Nous devrions peut-être aussi passer très rapidement en revue cela. Cette bibliothèque se décrit elle-même comme un «cadre de test d'acceptation pour les applications Web». Vous pouvez simuler des interactions utilisateur avec vos pages via un langage spécifique à un domaine très puissant et pratique. Personnellement, RSpec associé à Capybara est la meilleure façon de rédiger vos spécifications techniques pour le moment. Il vous permet de visiter des pages, de remplir des formulaires, de cliquer sur des liens et des boutons et de rechercher des balises sur vos pages. Vous pouvez facilement combiner toutes sortes de ces commandes pour interagir avec vos pages lors de vos tests..

En gros, vous pouvez éviter d'ouvrir vous-même le navigateur pour tester ce matériel manuellement la plupart du temps, ce qui est non seulement moins élégant, mais prend également beaucoup de temps et est source d'erreurs. Sans cet outil, le processus de «vérification externe» (vous conduirez votre code de tests de haut niveau dans vos tests au niveau de l'unité) serait beaucoup plus douloureux et peut-être, par conséquent, plus négligé..

En d'autres termes, vous commencez à écrire ces tests de fonctionnalité basés sur vos user stories, puis vous passez au crible jusqu'à ce que vos tests unitaires fournissent la couverture dont vous avez besoin. Après cela, bien sûr, lorsque vos tests sont verts, le jeu recommence et vous recommencez pour continuer avec un nouveau test de fonctionnalité..

Comment?

Regardons deux exemples simples de spécifications de fonctionnalités qui permettent à M de créer des missions classifiées pouvant être complétées.

Dans le balisage, vous avez une liste de missions et une exécution réussie crée une classe supplémentaire. terminé sur le li de cette mission particulière. Des trucs simples, non? En guise de première approche, j’ai commencé avec de petites refactorisations très courantes qui extraient le comportement courant dans des méthodes..

spec / features / m_creates_a_mission_spec.rb

"ruby nécessite 'rails_helper'

la fonctionnalité 'M crée la mission' réalise le scénario 'avec succès' sign_in_as '[email protected]'

create_classified_mission_named 'Project Moonraker' agent_sees_mission 'Project Moonraker' end 

def create_classified_mission_named (nom_mission) visiter mission_path click_on 'Create Mission' fill_in 'Nom de la mission', avec: nom_mission click_button 'Submit' end

def agent_sees_mission (nom_mission) attend (page) .à avoir_css 'li.nom-nom', texte: nom_mission

def sign_in_as (email) visite chemin_racine fill_in 'Email', avec: email click_button 'Submit' end end "

spec / features / agent_completes_a_mission_spec.rb

"ruby nécessite 'rails_helper'

fonctionnalité 'M marque la mission comme complète' réaliser le scénario 'avec succès' signer @ en tant que '[email protected]'

create_classified_mission_named 'Project Moonraker' mark_mission_as_complete 'Project Moonraker' agent_sees_completed_mission 'Project Moonraker' end 

def create_classified_mission_named (nom_mission) visiter mission_path click_on 'Create Mission' fill_in 'Nom de la mission', avec: nom_mission click_button 'Submit' end

def mark_mission_as_complete (mission_name) dans "li: contient ('# mission_name')", cliquez-vous sur 'Mission terminée' end end 

def agent_sees_completed_mission (nom_mission) attend (page) .to_css 'ul.missions li.mission-nom.completed', texte: nom_mission

def sign_in_as (email) visite chemin_racine fill_in 'Email', avec: email click_button 'Submit' end end "

Bien qu’il y ait bien sûr d’autres moyens de traiter des problèmes comme sign_in_as, create_classified_mission_named et ainsi de suite, il est facile de voir à quelle vitesse ces choses peuvent commencer à être nul et à s'accumuler.

Je pense que les spécifications liées à l'assurance-chômage ne reçoivent souvent pas le traitement OO dont ils ont besoin / qu'ils méritent. Ils ont la réputation de donner trop peu pour leur argent et, bien entendu, les développeurs n’aiment pas beaucoup les moments où ils doivent beaucoup toucher aux balises de balisage. Dans mon esprit, cela rend encore plus important de SÉCHER ces spécifications et de rendre amusant leur traitement en ajoutant quelques cours de Ruby..

Faisons un petit tour de magie où je cache pour le moment l'implémentation de l'objet de page et ne vous montre que le résultat final appliqué aux spécifications de fonctionnalité ci-dessus:

spec / features / m_creates_a_mission_spec.rb

"ruby nécessite 'rails_helper'

fonction 'M crée la mission' faire le scénario 'avec succès' faire la signature '[email protected]' visitez mission_path mission_page = Pages :: Missions.new

mission_page.create_classified_mission_named 'Project Moonraker' expect (mission_page) .à avoir_mission_name 'Project Moonraker' end end "

spec / features / agent_completes_a_mission_spec.rb

"ruby nécessite 'rails_helper'

la fonctionnalité 'M marque la mission comme terminée' réalise le scénario 'avec succès' signe_in_as '[email protected]' visite missions_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) .pour avoir_completed_mission_named 'Project Moonraker' end end "

Ne lis pas trop mal, hein? En gros, vous créez des méthodes d’emballage expressives sur vos objets de page qui vous permettent de traiter avec des concepts de haut niveau, au lieu de jouer constamment avec les intestins de votre balisage. Vos méthodes extraites font ce genre de travail sale maintenant, et de cette façon la chirurgie au fusil de chasse n’est plus votre problème..

Autrement dit, vous encapsulez la plupart du code d'interaction DOM bruyant et insensé. Cependant, je dois dire que parfois, les méthodes intelligemment extraites dans vos spécifications d'entités sont suffisantes et se lisent un peu mieux, car vous pouvez éviter de traiter avec des instances Page Object. Quoi qu'il en soit, regardons l'implémentation:

spécifications / support / fonctionnalités / pages / missions.rb

"module ruby ​​Classe classe Les missions incluent Capybara :: DSL

def create_classified_mission_named (nom_mission) click_on 'Create Mission' fill_in 'nom de la mission', avec: mission_name click_button 'Submit' end def mark_mission_as_complete (mission_name) au sein de "li: contient ('# mission_name')" do click_on 'Mission completed' end end def has_mission_named? (mission_name) mission_list.has_css? 'li', texte: nom_mission et def def has_completed_mission_named? (nom_mission) liste_messions.has_css? 'li.mission-name.completed', texte: mission_name end private def mission_list find ('ul.missions') end end end "

Ce que vous voyez est un vieil objet simple Ruby Page-Object Les objets sont essentiellement des classes très simples. Normalement, vous n'instanciez pas les objets de page avec des données (lorsque le besoin s'en faisait sentir, vous pouvez bien sûr) et vous créez principalement un langage via l'API qu'un utilisateur ou un intervenant non technique d'une équipe peut utiliser. Quand vous pensez nommer vos méthodes, je pense que c'est un bon conseil que de vous poser la question suivante: comment un utilisateur pourrait-il décrire le flux ou l'action entreprise?

Je devrais peut-être ajouter que sans Capybara, la musique s'arrête assez vite.

ruby comprennent Capybara :: DSL

Vous vous demandez probablement comment fonctionnent ces matchers personnalisés:

"ruby s'attendre (page) .pour avoir_mission_nommé 'Project Moonraker' s'attendre (page) .pour avoir_completed_mission_named 'Projet Moonraker'

def has_mission_named? (mission_name)… end

def has_completed_mission_named? (mission_name)… end "

RSpec génère ces correspondeurs personnalisés en fonction de méthodes de prédicat sur vos objets de page. RSpec les convertit en supprimant le ? et changements a à avoir. Boom, les matchers à partir de zéro sans beaucoup de fuzz! Un peu de magie, je vais vous donner ça, mais le bon genre de magie, je dirais.

Depuis que nous avons garé notre Page Object à spécifications / support / fonctionnalités / pages / missions.rb, vous devez également vous assurer que les éléments suivants ne sont pas commentés dans spec / rails_helper.rb.

ruby Dir [Rails.root.join ('spec / support / ** / * .rb')]. each | f | besoin de f

Si vous rencontrez un NameError avec un constante non initialisée Pages, vous saurez quoi faire.

Si vous êtes curieux de savoir ce qui est arrivé à la sign_in_as méthode, je l'ai extrait dans un module à spec / support / sign_in_helper.rb et dit à RSpec d'inclure ce module. Cela n'a rien à voir avec les objets de page directement. Il est plus logique de stocker des fonctionnalités de test telles que se connecter de manière plus globale que via un objet de page.

spec / support / sign_in_helper.rb

module ruby ​​SignInHelper def sign_in_as (email) visiter chemin root_path fill_in 'Email', avec: email click_button 'Soumettre' end end

Et vous devez faire savoir à RSpec que vous souhaitez accéder à ce module d'assistance:

spec / spec_helper.rb

"ruby… require 'support / sign_in_helper'

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

Globalement, il est facile de voir que nous avons réussi à cacher les éléments de recherche, les liens de clic, etc. similaires à Capybara. Nous pouvons maintenant nous concentrer sur la fonctionnalité et moins sur la structure même du balisage, qui est maintenant encapsulé dans un objet de page. -la structure DOM devrait être la moindre de vos préoccupations lorsque vous testez quelque chose d'aussi haut niveau que les spécifications des fonctionnalités.

Attention!

Les éléments de configuration tels que les données d'usine appartiennent aux spécifications et non aux objets de page. En outre, les assertions sont probablement mieux placées en dehors de vos objets de page pour séparer les préoccupations..

Il existe deux perspectives différentes sur le sujet. Les avocats qui placent des assertions dans des objets de page affirment que cela aide à éviter la duplication des assertions. Vous pouvez fournir de meilleurs messages d'erreur et obtenir un meilleur style «Dites, ne demandez pas». D'autre part, les défenseurs des objets de page sans assertions affirment qu'il est préférable de ne pas mélanger les responsabilités. Fournir l'accès aux données de page et à la logique d'assertion sont deux préoccupations distinctes et conduisent à des objets de page gonflés lorsqu'ils sont mélangés. La responsabilité de l'objet de page est l'accès à l'état des pages, et la logique d'assertion appartient aux spécifications.

Types d'objets de page

Composants représentent les plus petites unités et sont plus ciblées, comme un objet de formulaire, par exemple.

Des pages combine plus de ces composants et sont des abstractions d'une page complète.

Expériences, comme vous l'avez deviné, couvrez tout le flux potentiellement de nombreuses pages différentes. Ils sont plus haut niveau. Ils se concentrent sur le flux vécu par les utilisateurs lorsqu'ils interagissent avec différentes pages. Un flux de paiement en deux étapes est un bon exemple à considérer..

Quand Pourquoi?

C'est une bonne idée d'appliquer ce modèle de conception un peu plus tard dans le cycle de vie d'un projet, lorsque vous avez accumulé un peu de complexité dans vos spécifications de fonctionnalité et que vous pouvez identifier des modèles récurrents tels que des structures DOM, des méthodes extraites ou d'autres points communs. cohérent sur vos pages.

Donc, vous ne devriez probablement pas commencer à écrire des objets de page tout de suite. Vous abordez ces modifications progressivement au fur et à mesure que la complexité et la taille de votre application / test augmentent. Les duplications et les refactorisations qui ont besoin d'un meilleur foyer via Page Objects seront plus faciles à repérer avec le temps.

Ma recommandation est de commencer par extraire les méthodes de vos spécifications de fonctionnalité localement. Une fois qu'ils atteignent la masse critique, ils apparaîtront comme des candidats évidents pour une extraction ultérieure, et la plupart d'entre eux correspondront probablement au profil de Page Objects. Commencez petit, car une optimisation prématurée laisse des traces de morsures!

Dernières pensées

Les objets de page vous offrent la possibilité d’écrire des spécifications plus claires, plus lisibles et plus expressives dans l’ensemble, car elles sont de plus haut niveau. En plus de cela, ils offrent une belle abstraction à tous ceux qui aiment écrire du code OO. Ils cachent les spécificités du DOM et vous permettent également d’avoir des méthodes privées qui font le sale boulot sans être exposées à l’API publique. Les méthodes extraites dans vos spécifications ne proposent pas le même luxe. L'API de Page Objects n'a pas besoin de partager les détails pratiques de Capybara.

Quel que soit le scénario de modification de la mise en œuvre de la conception, votre description de la manière dont votre application devrait fonctionner ne doit pas nécessairement changer lorsque vous utilisez des objets de page: vos spécifications de fonctionnalités sont davantage axées sur les interactions au niveau de l'utilisateur et ne tiennent pas vraiment compte des spécificités. des implémentations DOM. Étant donné que le changement est inévitable, les objets de page deviennent critiques lorsque les applications se développent et aident également à comprendre lorsque la taille même de l'application entraîne une complexité considérablement accrue..