Tests d'interface utilisateur automatisés gérables

Il y a quelques années, j'étais très sceptique quant aux tests automatisés d'interface utilisateur. Ce scepticisme est né de quelques tentatives infructueuses. J'écrivais des tests d'interface utilisateur automatisés pour les applications de bureau ou Web et quelques semaines plus tard, je les extirpais de la base de code car le coût de leur maintenance était trop élevé. J'ai donc pensé que les tests d'interface utilisateur étaient difficiles et que, même s'ils apportaient de nombreux avantages, il était préférable de les limiter au maximum et de ne tester que les flux de travail les plus complexes d'un système via des tests d'interface utilisateur et de laisser le reste aux tests unitaires. Je me souviens d'avoir parlé de la pyramide de tests de Mike Cohn à mon équipe et que dans un système typique, plus de 70% des tests devaient être des tests unitaires, environ 5% de tests d'interface utilisateur et les tests d'intégration restants..

J'ai donc pensé que les tests d'assurance-chômage étaient difficiles et que, même s'ils apportaient de nombreux avantages, il était préférable de les limiter au minimum…

J'avais tort! Bien sûr, les tests d'interface utilisateur peuvent être difficiles. Il faut pas mal de temps pour écrire correctement les tests d’UI. Ils sont beaucoup plus lents et fragiles que les tests unitaires, car ils transcendent les limites des classes et des processus, ils touchent le navigateur, ils impliquent des éléments d'interface utilisateur (par exemple, HTML, JavaScript) qui changent constamment, ils frappent également la base de données, le système de fichiers et éventuellement les services réseau. Si l'une de ces pièces mobiles ne joue pas correctement, le test est incomplet. mais c'est aussi la beauté des tests d'interface utilisateur: ils testent votre système de bout en bout. Aucun autre test ne vous offre une couverture aussi complète ou complète. Les tests automatisés de l'interface utilisateur, s'ils sont bien effectués, pourraient constituer les meilleurs éléments de votre suite de régression..

Ainsi, au cours des derniers projets, mes tests d'interface utilisateur ont constitué plus de 80% de mes tests! Je devrais également mentionner que ces projets ont été principalement des applications CRUD avec peu de logique métier et avouons-le - la grande majorité des projets de logiciels tombent dans cette catégorie. La logique métier doit toujours être testée par unité; mais le reste de l'application peut être minutieusement testé via l'automatisation de l'interface utilisateur.


Les tests d'interface ont mal tourné

J'aimerais aborder ce que j'ai fait de mal, ce qui semble également être très courant chez les développeurs et les testeurs, à commencer par l'automatisation de l'interface utilisateur..

Alors qu'est-ce qui ne va pas et pourquoi? Beaucoup d'équipes lancent l'automatisation de l'interface utilisateur avec des enregistreurs d'écran. Si vous effectuez une automatisation Web avec Selenium, vous avez probablement utilisé Selenium IDE. À partir de la page d'accueil Selenium IDE:

Le Selenium-IDE (environnement de développement intégré) est l'outil que vous utilisez pour développer vos scénarios de test Selenium..

C’est en fait l’une des raisons pour lesquelles les tests d’interface utilisateur se transforment en une expérience horrible: vous téléchargez et lancez un enregistreur d’écran et accédez à votre site Web, puis cliquez, tapez, cliquez, tapez, cliquez sur, tapez, tapez, onglet, tapez, cliquez et affirmer. Ensuite, vous rejouez l'enregistrement et cela fonctionne. Sucré!! Ainsi, vous exportez les actions sous forme de script de test, vous les insérez dans votre code, vous les intégrez dans un test, vous exécutez le test et vous voyez le navigateur s'animer sous vos yeux et vos tests se déroulent sans à-coups. Vous êtes très excité, partagez vos découvertes avec vos collègues et montrez-les à votre supérieur hiérarchique. Ils s'énervent beaucoup et vont: "Automatisez TOUTES LES CHOSES"

Une semaine plus tard, vous avez 10 tests d’interface utilisateur automatisés et tout semble parfait. Ensuite, l'entreprise vous demande de remplacer le nom d'utilisateur par l'adresse électronique, ce qui a créé une certaine confusion parmi les utilisateurs. Ensuite, comme tout autre grand programmeur, vous exécutez votre suite de tests d'interface utilisateur. Seulement 90% de vos tests sont rompus, car vous connectez l'utilisateur avec le nom d'utilisateur. Le nom du champ a changé et il vous faut deux heures pour tout remplacer. les références à Nom d'utilisateur dans vos tests avec email et pour que les tests reviennent au vert. La même chose se produit encore et encore et à un moment donné, vous vous retrouvez à passer des heures par jour à réparer des tests cassés: des tests qui ne cassaient pas parce que quelque chose n'allait pas avec votre code; mais parce que vous avez changé un nom de champ dans votre base de données / modèle ou que vous avez légèrement restructuré votre page. Quelques semaines plus tard, vous arrêtez d'exécuter vos tests à cause de cet énorme coût de maintenance et vous en concluez que les tests d'interface utilisateur sont nul.

Vous ne devez PAS utiliser Selenium IDE ni aucun autre enregistreur d’écran pour développer vos scénarios de test. Cela dit, ce n’est pas l’écran d’écran lui-même qui mène à une suite de tests fragile; c'est le code qu'ils génèrent qui pose des problèmes de maintenabilité inhérents. De nombreux développeurs finissent toujours par avoir une suite de tests d'interface utilisateur fragile, même sans utiliser les enregistreurs d'écran, simplement parce que leurs tests présentent les mêmes attributs..

Tous les tests de cet article sont écrits sur le site Web de Mvc Music Store. Le site Web en tant que tel a des problèmes qui rendent le test de l'interface utilisateur assez difficile, aussi j'ai transféré le code et corrigé les problèmes. Vous pouvez trouver le code sur lequel j’écris ces tests sur le dépôt GitHub de cet article ici

Alors, à quoi ressemble un test fragile? Cela ressemble à quelque chose comme ça:

class BrittleTest [Test] public void Can_buy_an_Album_when_registered () var driver = Host.Instance.Application.Browser; driver.Navigate (). GoToUrl (driver.Url); driver.FindElement (By.LinkText ("Admin")). Cliquez (); driver.FindElement (By.LinkText ("Register")). Cliquez (); driver.FindElement (By.Id ("UserName")). Clear (); driver.FindElement (By.Id ("UserName")). SendKeys ("HJSimpson"); driver.FindElement (By.Id ("Password")). Clear (); driver.FindElement (By.Id ("Mot de passe")). SendKeys ("! 2345Qwert"); driver.FindElement (By.Id ("ConfirmPassword")). Clear (); driver.FindElement (By.Id ("ConfirmPassword")). SendKeys ("! 2345Qwert"); driver.FindElement (By.CssSelector ("input [type = \" submit \ "]")). Cliquez (); driver.FindElement (By.LinkText ("Disco")). Cliquez (); driver.FindElement (By.CssSelector ("img [alt = \" Le Freak \ "]")). Cliquez (); driver.FindElement (By.LinkText ("Ajouter au panier")). Cliquez (); driver.FindElement (By.LinkText ("Checkout >>")). Cliquez (); driver.FindElement (By.Id ("Prénom")). Clear (); driver.FindElement (By.Id ("Prénom")). SendKeys ("Homer"); driver.FindElement (By.Id ("Nom")). Clear (); driver.FindElement (By.Id ("Nom")). SendKeys ("Simpson"); driver.FindElement (By.Id ("Address")). Clear (); driver.FindElement (By.Id ("Address")). SendKeys ("742 Evergreen Terrace"); driver.FindElement (By.Id ("Ville")). Clear (); driver.FindElement (By.Id ("Ville")). SendKeys ("Springfield"); driver.FindElement (By.Id ("State")). Clear (); driver.FindElement (By.Id ("State")). SendKeys ("Kentucky"); driver.FindElement (By.Id ("PostalCode")). Clear (); driver.FindElement (By.Id ("PostalCode")). SendKeys ("123456"); driver.FindElement (By.Id ("Pays")). Clear (); driver.FindElement (By.Id ("Pays")). SendKeys ("États-Unis"); driver.FindElement (By.Id ("Phone")). Clear (); driver.FindElement (By.Id ("Phone")). SendKeys ("2341231241"); driver.FindElement (By.Id ("Email")). Clear (); driver.FindElement (By.Id ("Email")). SendKeys ("[email protected]"); driver.FindElement (By.Id ("PromoCode")). Clear (); driver.FindElement (By.Id ("PromoCode")). SendKeys ("FREE"); driver.FindElement (By.CssSelector ("input [type = \" submit \ "]")). Cliquez (); Assert.IsTrue (driver.PageSource.Contains ("Paiement terminé")); 

Vous pouvez trouver le Test de fragilité classe ici.

Host est une classe statique, avec une seule propriété statique: Exemple, qui, lors de l'instanciation, déclenche IIS Express sur le site Web testé et lie Firefox WebDriver à l'instance du navigateur. Une fois le test terminé, le navigateur se ferme et IIS Express se connecte automatiquement..

Ce test ouvre un navigateur Web, affiche la page d'accueil du site Web de Mvc Music Store, enregistre un nouvel utilisateur, navigue dans un album, l'ajoute au panier et vérifie.

On pourrait dire que ce test en fait trop et que c'est pourquoi il est fragile; mais la taille de ce test n'est pas la raison pour laquelle il est fragile - c'est la façon dont il est écrit qui en fait un cauchemar à entretenir.

Il existe différentes écoles de pensée sur les tests d’assurance-chômage et le montant que chaque test devrait couvrir. Certains pensent que ce test en fait trop et certains pensent qu'un test devrait couvrir un scénario réel, de bout en bout, et le considèrent comme un test parfait (à part la maintenabilité).

Alors, quel est le problème avec ce test?

  • C'est un code de procédure. L'un des principaux problèmes de ce style de codage est la lisibilité, ou son absence. Si vous souhaitez modifier le test ou s'il se rompt parce qu'une des pages impliquées a été modifiée, vous aurez du mal à déterminer ce qu'il faut changer et à tracer une ligne entre les sections de fonctionnalités; parce que tout cela est une grosse pile de code dans laquelle nous demandons au "pilote" de trouver un élément sur la page et de faire quelque chose avec. Pas de modularité.
  • Ce test en lui-même n’a peut-être pas beaucoup de duplication, mais quelques tests supplémentaires comme celui-ci et vous aurez beaucoup de sélecteur et de logique dupliqués pour interagir avec les pages Web de différents tests. Par exemple By.Id ("UserName") le sélecteur sera dupliqué dans tous les tests nécessitant une inscription, et driver.FindElement (By.Id ("UserName")). Clear () et driver.FindElement (By.Id ("UserName")). SendKeys ("") sont dupliqués partout où vous souhaitez interagir avec la zone de texte Nom d'utilisateur. Ensuite, il y a le formulaire d'inscription complet, et le formulaire de commande, etc. qui seront répétés dans tous les tests nécessitant d'interagir avec eux! Du code en double mène à des cauchemars de maintenabilité.
  • Il y a beaucoup de chaînes magiques partout, ce qui est encore un problème de maintenabilité.

Le code de test est un code!

Il existe également des modèles vous permettant d'écrire des tests d'interface utilisateur plus faciles à maintenir..

Tout comme votre code actuel, vous allez devoir maintenir vos tests. Alors donnez-leur le même traitement.

Qu'est-ce qui fait que les tests nous font perdre de la qualité? À mon avis, une mauvaise suite de tests est beaucoup plus difficile à maintenir qu'un mauvais code. Depuis des années, je produis de mauvaises pièces de code de travail en production qui ne se sont jamais cassées et je n'ai jamais eu à les toucher. Bien sûr, c’était moche et difficile à lire et à entretenir, mais cela fonctionnait et il n’y avait pas besoin de changer, le coût réel de la maintenance était donc nul. La situation n’est cependant pas tout à fait la même pour les mauvais tests: parce que les mauvais tests vont casser et que les réparer va être difficile. Je ne peux pas compter le nombre de fois où les développeurs ont évité les tests car ils pensent qu'écrire des tests est une énorme perte de temps, car sa maintenance prend trop de temps..

Le code de test est le code: appliquez-vous SRP sur votre code? Ensuite, vous devriez également l'appliquer à vos tests. Votre code est sec? Séchez ensuite vos tests. Si vous n'écrivez pas de bons tests (interface utilisateur ou autre), vous perdrez beaucoup de temps à les entretenir..

Il existe également des modèles vous permettant d'écrire des tests d'interface utilisateur plus faciles à maintenir. Ces modèles sont indépendants de la plate-forme: j'ai utilisé ces mêmes idées et modèles pour écrire des tests d'interface utilisateur pour les applications WPF et les applications Web écrites en ASP.Net et Ruby on Rails. Ainsi, quelle que soit votre technologie, vous devriez pouvoir rendre vos tests d’interface utilisateur beaucoup plus faciles à gérer en suivant quelques étapes simples..

Présentation du modèle d'objet de page

Un grand nombre des problèmes mentionnés ci-dessus sont liés à la nature procédurale du script de test et la solution est simple: Orientation objet.

Page Object est un modèle utilisé pour appliquer l'orientation d'objet aux tests d'interface utilisateur. À partir du wiki Selenium:

L'interface utilisateur de votre application Web contient des zones avec lesquelles vos tests interagissent. Un objet de page les modélise simplement en tant qu'objets dans le code de test. Cela réduit la quantité de code dupliqué et signifie que, si l'interface utilisateur change, le correctif ne doit être appliqué qu'à un seul endroit..

L'idée est que pour chaque page de votre application / site Web, vous souhaitez créer un objet de page. Les objets de page sont essentiellement l'équivalent de vos pages Web pour l'automatisation de l'interface utilisateur..

Je suis allé de l'avant et j'ai restructuré la logique et les interactions du test BrittleTest en quelques objets de page et créé un nouveau test qui les utilise au lieu d'appuyer directement sur le pilote Web. Vous pouvez trouver le nouveau test ici. Le code est copié ici pour votre référence:

public class TestWithPageObject [Test] public void Can_buy_an_Album_when_registered () var registerPage = HomePage.Initiate () .GoToAdminForAnonymousUser () .GoToRegisterPage (); registerPage.Username = "HJSimpson"; registerPage.Email = "[email protected]"; registerPage.Password = "! 2345Qwert"; registerPage.ConfirmPassword = "! 2345Qwert"; var shippingPage = registerPage .SubmitRegistration () .SelectGenreByName ("Disco") .SelectAlbumByName ("Le Freak") .AddToCart () .Checkout (); shippingPage.FirstName = "Homère"; shippingPage.LastName = "Simpson"; shippingPage.Address = "742 Evergreen Terrace"; shippingPage.City = "Springfield"; shippingPage.State = "Kentucky"; shippingPage.PostalCode = "123456"; shippingPage.Country = "États-Unis"; shippingPage.Phone = "2341231241"; shippingPage.Email = "[email protected]"; shippingPage.PromoCode = "GRATUIT"; var orderPage = shippingPage.SubmitOrder (); Assert.AreEqual (orderPage.Title, "Paiement terminé"); 

Certes, le corps du test n'a pas beaucoup diminué en taille et en fait, j'ai dû créer sept nouvelles classes pour supporter ce test. En dépit du nombre de lignes de code requis, nous venons de résoudre beaucoup de problèmes rencontrés par le test de fragilité d'origine (plus à ce sujet plus bas). Pour l'instant, approfondissons un peu le modèle d'objet de page et ce que nous avons fait ici.

Avec le modèle d'objet de page, vous créez généralement une classe d'objet de page par page Web testée dans laquelle la classe modélise et encapsule les interactions avec la page. Ainsi, une zone de texte dans votre page Web devient une propriété de chaîne sur l'objet Page et pour remplir cette zone de texte, vous devez simplement définir cette propriété de texte sur la valeur souhaitée, au lieu de:

driver.FindElement (By.Id ("Email")). Clear (); driver.FindElement (By.Id ("Email")). SendKeys ("[email protected]");

nous pouvons écrire:

registerPage.Email = "[email protected]";

registerPage est une instance de la classe RegisterPage. Une case à cocher sur la page devient une propriété booléenne sur l'objet Page et cocher / décocher la case à cocher revient simplement à définir cette propriété booléenne sur true ou sur false. De même, un lien sur la page Web devient une méthode sur l'objet de page et cliquer sur le lien permet d'appeler la méthode sur l'objet de page. Donc au lieu de:

driver.FindElement (By.LinkText ("Admin")). Cliquez ();

nous pouvons écrire:

homepage.GoToAdminForAnonymousUser ();

En fait, toute action sur notre page Web devient une méthode dans notre objet page et en réponse à cette action (c.-à-d. En appelant la méthode sur l'objet page), vous obtenez une instance d'un autre objet page qui pointe vers la page Web que vous venez de accédez à l'action en effectuant l'action (par exemple, soumission d'un formulaire ou clic sur un lien). De cette façon, vous pouvez facilement chaîner vos interactions de vue dans votre script de test:

var shippingPage = registerPage .SubmitRegistration () .SelectGenreByName ("Disco") .SelectAlbumByName ("Le Freak") .AddToCart () .Checkout ();

Ici, après avoir enregistré l’utilisateur, je suis conduit à la page d’accueil (une instance de son objet page est renvoyée par Proposez enregistrement méthode). Donc, sur l'instance HomePage que j'appelle SélectionnezGenreByName qui clique sur un lien 'Disco' sur la page qui renvoie une instance de AlbumBrowsePage, puis sur cette page, j'appelle SélectionnezAlbumByName qui clique sur l'album 'Le Freak' et retourne une instance de AlbumDetailsPage et ainsi de suite.

Je l’admets: c’est beaucoup de cours pour ce qui n’était pas un cours du tout; mais nous avons tiré de nombreux avantages de cette pratique. Tout d'abord, le code n'est plus procédural. Nous avons un modèle de test bien contenu dans lequel chaque objet fournit une encapsulation agréable des interactions avec une page. Ainsi, par exemple, si quelque chose change dans votre logique d'enregistrement, le seul endroit que vous devez changer est votre classe RegisterPage au lieu de devoir parcourir toute votre suite de tests et de modifier chaque interaction avec la vue d'enregistrement. Cette modularité permet également une belle réutilisation: vous pouvez réutiliser vos ShoppingCartPage partout où vous devez interagir avec le panier. Ainsi, dans une pratique simple consistant à passer d'un code de test procédural à un code de test orienté objet, nous avons presque éliminé trois des quatre problèmes liés au test fragile initial, à savoir le code de procédure et la duplication de logique et de sélecteur. Nous avons encore un peu de duplication que nous allons corriger sous peu.

Comment avons-nous réellement implémenté ces objets de page? Un objet de page dans sa racine n'est rien d'autre qu'un wrapper autour des interactions que vous avez avec la page. Ici, je viens d'extraire les interactions d'interface utilisateur de nos tests fragiles et de les mettre dans leurs propres objets de page. Par exemple, la logique d’enregistrement a été extraite dans sa propre classe appelée S'inscrirePage qui ressemblait à ceci:

public class RegisterPage: Page public HomePage SubmitRegistration () return NavigateTo(By.CssSelector ("input [type = 'submit']"));  chaîne publique Nom d'utilisateur set Execute (By.Name ("UserName")), e => e.Clear (); e.SendKeys (valeur););  chaîne publique Email set Execute (By.Name ("Email"), e => e.Clear (); e.SendKeys (valeur););  chaîne publique ConfirmPassword set Execute (By.Name ("ConfirmPassword"), e => e.Clear (); e.SendKeys (valeur););  chaîne publique Mot de passe set Execute (By.Name ("Mot de passe"), e => e.Clear (); e.SendKeys (valeur);); 

J'ai créé un Page superclasse qui s'occupe de quelques choses, comme Aller vers qui aide à naviguer vers une nouvelle page en prenant une action et Exécuter qui exécute des actions sur un élément. le Page la classe ressemblait à:

Classe publique Page protected RemoteWebDriver WebDriver get return Host.Instance.WebDriver;  public string Title get return WebDriver.Title;  public TPage NavigateTo(By) où TPage: Page, new () WebDriver.FindElement (by) .Click (); renvoyer Activator.CreateInstance();  public void Execute (By by, Action action) var element = WebDriver.FindElement (par); action (élément); 

dans le Test de fragilité, pour interagir avec un élément que nous avons fait FindElement une fois par action. le Exécuter méthode, en plus de l’abstraction des interactions du pilote Web, présente un avantage supplémentaire qui permet de sélectionner un élément, ce qui peut être une action coûteuse, une fois pour toutes et en effectuant plusieurs actions:

driver.FindElement (By.Id ("Password")). Clear (); driver.FindElement (By.Id ("Mot de passe")). SendKeys ("! 2345Qwert");

a été remplacé par:

Execute (By.Name ("Password"), e => e.Clear (); e.SendKeys ("! 2345Qwert");)

Jeter un second regard sur le S'inscrirePage objet de la page ci-dessus, nous avons encore un peu de duplication là-bas. Le code de test est un code et nous ne voulons pas de duplication dans notre code; alors reformulons cela. Nous pouvons extraire le code nécessaire pour remplir une zone de texte dans une méthode sur le Page classe et il suffit d'appeler cela à partir d'objets de la page. La méthode pourrait être implémentée comme:

public void SetText (string elementName, string newText) Execute (By.Name (elementName), e => e.Clear (); e.SendKeys (newText);); 

Et maintenant les propriétés sur S'inscrirePage peut être réduit à:

chaîne publique Nom d'utilisateur set SetText ("Nom d'utilisateur", valeur); 

Vous pouvez également créer une API fluide pour améliorer la lecture du setter (par exemple,. Remplir ("Nom d'utilisateur"). Avec (valeur)) mais je vous laisse ça.

Nous ne faisons rien d'extraordinaire ici. Tout simplement une refactorisation sur notre code de test, comme nous l'avons toujours fait pour notre code, "autre"!!

Vous pouvez voir le code complet pour Page et S'inscrirePage cours ici et ici.

Objet de page fortement typé

Nous avons résolu les problèmes de procédure liés au test fragile, ce qui a rendu le test plus lisible, modulaire, DRY et maintenable de manière efficace. Il y a un dernier problème que nous n'avons pas résolu: il y a encore beaucoup de chaînes magiques partout. Pas tout à fait un cauchemar, mais toujours un problème que nous pourrions résoudre. Entrez des objets de page fortement typés!

Cette approche est pratique si vous utilisez un framework MV * pour votre interface utilisateur. Dans notre cas, nous utilisons ASP.Net MVC.

Jetons un autre regard sur le S'inscrirePage:

public class RegisterPage: Page public HomePage SubmitRegistration () return NavigateTo(By.CssSelector ("input [type = 'submit']"));  chaîne publique Nom d'utilisateur set SetText ("Nom d'utilisateur", valeur);  chaîne publique Email set SetText ("Email", valeur);  chaîne publique ConfirmPassword set SetText ("ConfirmPassword", valeur);  chaîne publique Password set SetText ("Password", valeur); 

Cette page modélise la vue Enregistrer dans notre application Web (il suffit de copier le bit le plus haut ici pour votre commodité):

@model MvcMusicStore.Models.RegisterModel @ ViewBag.Title = "S'inscrire"; 

Hmmm, c'est quoi ça? EnregistrerModèle Là? C’est le modèle de vue de la page: le M dans le MVC. Voici le code (j'ai enlevé les attributs pour réduire le bruit):

Classe publique RegisterModel Chaîne publique UserName get; ensemble;  chaîne publique Email get; ensemble;  chaîne publique Mot de passe get; ensemble;  chaîne publique ConfirmPassword get; ensemble; 

Cela semble très familier, n'est-ce pas? Il a les mêmes propriétés que le S'inscrirePage classe qui n'est pas surprenant compte tenu S'inscrirePage a été créé sur la base de ce modèle de vue et de vue. Voyons si nous pouvons tirer parti des modèles de vue pour simplifier nos objets de page.

J'ai créé un nouveau Page superclasse; mais un générique. Vous pouvez voir le code ici:

Classe publique Page : Page où TViewModel: class, new () public void FillWith (TViewModel viewModel, IDictionary> propertyTypeHandling = null) // supprimé par souci de brièveté

le Page classe sous-classe l'ancien Page classe et fournit toutes ses fonctionnalités; mais il a aussi une méthode supplémentaire appelée Remplir avec qui remplit la page avec l'instance de modèle de vue fournie! Alors maintenant mon S'inscrirePage la classe ressemble à:

public class RegisterPage: Page public HomePage CreateValidUser (modèle RegisterModel) FillWith (modèle); retourner NavigateTo(By.CssSelector ("input [type = 'submit']")); 

J'ai dupliqué tous les objets de page pour montrer les deux variantes et pour rendre le code plus facile à suivre pour vous; mais en réalité, vous aurez besoin d'une classe pour chaque objet de page.

Après avoir converti mes objets de page en objets génériques, le test ressemble à ceci:

Vous devez vous enregistrer ou enregistrer des messages. Freak ") .AddAlbumToCart () .Checkout () .SubmitShippingInfo (ObjectMother.CreateShippingInfo ()," Gratuit "); Assert.AreEqual ("Paiement terminé", ordersPage.Title); 

C'est tout - le test entier! Beaucoup plus lisible, sec et maintenable, n'est-ce pas?

le ObjectMother La classe que j'utilise dans le test est un objet mère qui fournit des données de test (le code peut être trouvé ici), rien de compliqué:

Classe publique ObjectMother Ordre public statique CreateShippingInfo () var shippingInfo = nouvel Ordre FirstName = "Homer", Nom = "Simpson", Address = "742 Evergreen Terrace", City = "Springfield", State = "Kentucky", Code postal = "123456", Country = "États-Unis", Téléphone = "2341231241", E-mail = "[email protected]"; retour shippingInfo;  public statique RegisterModel CreateRegisterModel () var model = new RegisterModel UserName = "HJSimpson", Email = "[email protected]", Password = "! 2345Qwert", ConfirmPassword = "! 2345Qwert"; modèle de retour; 

Ne vous arrêtez pas à l'objet de la page

Certaines pages Web sont très grandes et complexes. Un peu plus tôt, j'ai dit que le code de test est un code et que nous devrions le traiter comme tel. Nous divisons normalement les pages Web volumineuses et complexes en composants plus petits et, dans certains cas, réutilisables (partiels). Cela nous permet de composer une page Web à partir de composants plus petits et plus faciles à gérer. Nous devrions faire la même chose pour nos tests. Pour ce faire, nous pouvons utiliser des composants de page.

Un composant de page est un peu comme un objet de page: c'est une classe qui encapsule les interactions avec certains éléments d'une page. La différence est qu’elle interagit avec une petite partie d’une page Web: elle modélise un contrôle utilisateur ou une vue partielle, si vous voulez. Un bon exemple de composant de page est une barre de menus. Une barre de menus apparaît généralement sur toutes les pages d'une application Web. Vous ne voulez pas vraiment continuer à répéter le code nécessaire pour interagir avec le menu dans chaque objet de page. Au lieu de cela, vous pouvez créer un composant de page de menu et l'utiliser à partir de vos objets de page. Vous pouvez également utiliser des composants de page pour traiter des grilles de données sur vos pages, et pour aller un peu plus loin, le composant de page en grille lui-même peut être composé de composants de page en lignes de grille. Dans le cas de Mvc Music Store, nous pourrions avoir un TopMenuComponent et un SideMenuComponent et les utiliser de notre Page d'accueil.

Comme dans votre application Web, vous pouvez également créer un, disons, LayoutPage objet de page qui modélise votre mise en page / maquette et l’utilise comme superclasse pour tous vos autres objets de page. La page de présentation serait alors composée d'éléments de page de menu afin que toutes les pages puissent accéder aux menus. J'imagine qu'une bonne règle serait d'avoir un composant de page par vue partielle, un objet de page de mise en page par mise en page et un objet de page par page Web. De cette façon, vous savez que votre code de test est aussi détaillé et bien composé que votre code.

Quelques cadres pour les tests d'interface utilisateur

Ce que j'ai montré ci-dessus était un échantillon très simple et artificiel avec quelques classes de support comme infrastructure pour les tests. En réalité, la configuration requise pour les tests d’interface utilisateur est bien plus complexe: il existe des contrôles et des interactions complexes, vous devez écrire et lire dans vos pages, vous devez gérer les latences du réseau et maîtriser AJAX et d’autres interactions Javascript. besoin de lancer différents navigateurs et ainsi de suite, ce que je n’ai pas expliqué dans cet article. Bien qu'il soit possible de coder tout cela, l'utilisation de certains frameworks peut vous faire gagner beaucoup de temps. Voici les cadres que je recommande fortement:

Cadres pour .Net:

  • Seleno est un projet open source de TestStack qui vous aide à écrire des tests d’interface utilisateur automatisés avec Selenium. Il se concentre sur l'utilisation des objets de page et des composants de page et en lisant et en écrivant des pages Web à l'aide de modèles de vue fortement typés. Si vous avez aimé ce que j'ai fait dans cet article, alors vous aimerez aussi Seleno, car la plupart du code présenté ici a été emprunté à la base de code de Seleno..
  • White est un framework open source de TestStack pour l’automatisation d’applications client enrichi basées sur les plateformes Win32, WinForms, WPF, Silverlight et SWT (Java)..

Divulgation: Je suis cofondateur et membre de l'équipe de développement de l'organisation TestStack..

Cadres pour Ruby:

  • Capybara est un framework de tests d'acceptation pour applications Web qui vous aide à tester des applications Web en simulant la manière dont un utilisateur réel interagirait avec votre application..
  • Poltergeist est un pilote pour Capybara. Il vous permet d’exécuter vos tests Capybara sur un navigateur WebKit sans tête fourni par PhantomJS..
  • page-object (je n'ai pas personnellement utilisé cette gemme) est une gemme simple permettant de créer des objets de page flexibles pour tester des applications basées sur un navigateur. L'objectif est de faciliter la création de couches d'abstraction dans vos tests afin de dissocier les tests de l'élément testé et de fournir une interface simple aux éléments d'une page. Cela fonctionne à la fois avec watir-webdriver et sélénium-webdriver.

Conclusion

Nous avons commencé avec une expérience typique de l'automatisation de l'interface utilisateur, avons expliqué pourquoi les tests d'interface utilisateur échouaient, fourni un exemple de test fragile, discuté de ses problèmes et résolu leurs problèmes à l'aide de quelques idées et modèles..

Si vous voulez prendre un point de cet article, il devrait être: Test Code Is Code. Si vous y réfléchissez, tout ce que j'ai fait dans cet article a été d'appliquer les bonnes pratiques de codage et orientées objet que vous connaissez déjà à un test d'interface utilisateur..

Il reste encore beaucoup à apprendre sur les