Comment écrire des tests unitaires maintenables et non cassants pour une interface graphique?

16

J'ai essayé d'écrire des tests unitaires d'interface utilisateur pour mes applications GUI et je suis confronté au problème que, même si elles fonctionnent bien lorsque je les écris au départ, elles se révèlent fragiles et se cassent chaque fois que la conception change (c'est-à-dire, assez souvent). J'ai du mal à trouver un ensemble de directives qui m'amèneraient à avoir des tests unitaires maintenables pour l'interface graphique.

Pour l'instant, une chose que j'ai découverte est que les tests disant "ce composant devrait montrer ses données d'entrée quelque part" sont bons (et c'est très facile avec HTML). Les tests vérifiant un état spécifique d'une partie spécifique du composant sont généralement fragiles. Les tests qui vont comme cliquer-cliquer-cliquer-attendre, qui essaient de suivre le comportement de l'utilisateur et la logique métier sous-jacente (qui est la partie la plus importante) se révèlent généralement fragiles. Comment écrire de bons tests?


Pour être plus précis, j'aimerais connaître certains modèles sur ce que je pourrais tester dans mon interface utilisateur, pas exactement comment le tester. Les conventions de dénomination et les identifiants fixes sont bons, mais ne résolvent pas le problème principal, qui est que les interfaces graphiques changent beaucoup. Je voudrais tester les comportements les moins susceptibles de changer. Comment trouver la bonne chose à tester?

mik01aj
la source
1
@MichaelDurrant: Vous avez modifié la question concernant les tests d'interface utilisateur en général, alors que je posais à l'origine des questions sur les tests unitaires. Je trouve les tests d'intégration plus difficiles à maintenir et je préfère les tests unitaires à ceux-ci, dans la mesure du possible.
mik01aj
2
Je pense que cela fait partie du problème cependant, fondamentalement, vous ne pouvez pas vraiment tester une interface par des tests unitaires seuls, car leur raison d'être est de s'interfacer avec quelque chose. Les interfaces graphiques ne sont pas différentes à cet égard.
jk.
m01, vous pouvez le modifier en arrière. Je pense que les tests d'interface utilisateur sont généralement des tests intégrés. Les tests ont tendance à s'appuyer sur les données de graines et de fixtures présentes et sur l'interface utilisateur travaillant avec eux. Si vous avez de vrais tests d'interface utilisateur qui ne reposent sur aucune autre donnée, c'est excellent. Cependant, j'ai trouvé que c'était relativement rare.
Michael Durrant
2
lié mais pas un doublon car il s'agit de tests gui: programmers.stackexchange.com/questions/109703/…
k3b

Réponses:

3

Un problème commun avec les tests GUI ... La raison principale pour laquelle ces tests sont considérés comme fragiles est qu'ils ne peuvent pas survivre à un changement dans l'interface graphique qui n'est pas un changement dans les exigences . Vous devez vous efforcer de structurer votre code de test de manière à ce qu'un changement dans l'interface graphique soit isolé à un seul endroit dans les tests.

À titre d'exemple, considérons un test libellé comme suit:

Lorsque l'utilisateur entre «999» dans le champ du numéro de téléphone et clique sur le bouton Enregistrer, la fenêtre contextuelle du message d'erreur doit indiquer «Numéro de téléphone non valide».

Beaucoup d'espace ici pour que ce test se brise lorsque vous retravaillez l'interface, même si l'exigence de validation reste.

Maintenant, mettons cela dans un petit libellé alternatif:

Lorsque l'utilisateur entre «999» comme numéro de téléphone et enregistre la page de profil, il devrait afficher une erreur indiquant «numéro de téléphone non valide»

Le test est le même, les exigences sont les mêmes, mais ce type de test survivra à une refonte de votre interface utilisateur. Vous devrez évidemment changer de code, mais le code sera isolé. Même si vous avez dix ou vingt tests de ce type pour votre page de profil et que vous déplacez votre logique de validation affichant les messages d'erreur de javascript-alerts à jquery-popups par exemple, il vous suffit de modifier la seule partie de test qui vérifie les messages d'erreur.

JDT
la source
4

C'est un problème commun. Je ferais attention à:

  • Comment vous nommez les éléments

    Utilisez id ou classe css pour identifier les éléments. Privilégiez l'utilisation de l'ID CSS lorsque l'objet est unique. Tenez compte du cadre que vous utilisez, par exemple avec Ruby on Rails, l' nameattribut est attribué automatiquement et peut (de manière non intuitive) être meilleur que d'utiliser l'ID ou la classe CSS

  • Comment vous identifiez les éléments.

    Évitez les identificateurs de position comme table/tr/td/tden faveur de formes telles que td[id="main_vehicle"ou td[class='alternates']. Envisagez d'utiliser des attributs de données lorsque cela est approprié. Encore mieux, essayez d'éviter les balises de mise en page telles que <td>tout à fait.Pour ce qui précède, vous pouvez soit ajouter une étendue et l'utiliser, par exemple, <span id="main_vehicle">soit un sélecteur générique tel que *[id="main_vehicle"]*pourrait maintenant être un div, une étendue, un td, etc.

  • Utiliser des attributs de données spécifiques au test qui ne sont utilisés que pour qa et les tests.

  • Évitez la qualification inutile des éléments. Vous pourriez vous retrouver en utilisant les éléments suivants:

    body.main div#vehicles > form#vehicle input#primary_vehicle_name

    Cependant, cela nécessite que le champ de saisie reste dans un formulaire avec un identifiant exact de véhicule et sur une page avec un corps qui a une classe de principal et un div avec un identifiant de véhicules qui a un enfant immédiat d'un formulaire avec un identifiant de véhicule. Tout changement à l'une de ces structures et le test échoue. Dans ce cas, vous pourriez constater que

    input#primary_vehicle_name

    est suffisant pour identifier l'élément de manière unique.

  • Évitez les tests qui font référence à du texte visible. Le texte de la page qui est présenté à l'utilisateur change généralement au fil du temps au fur et à mesure que le site est mis à jour et mis à jour, utilisez donc des identifiants tels que css id et css class ou des attributs de données. Des éléments tels que form, inputet selectutilisés dans les formes sont aussi bonnes parties d'éléments d' identification, généralement en combinaison avec id ou de classe, par exemple , li.vehicleou input#first-vehicle vous pouvez également ajouter vos propres identifiants, par exemple <div data-vehicle='dodge'>. De cette façon, vous pouvez éviter d'utiliser les ID d'élément ou les classes, qui sont susceptibles d'être modifiés par les développeurs et les concepteurs. Au fil du temps, j'ai trouvé qu'il valait mieux travailler avec les développeurs et les concepteurs et s'entendre sur les noms et les étendues. C'est difficile.

  • Comment les données fixes sont maintenues.

    Semblable à l'identification d'éléments réels, essayez d'éviter que le sélecteur en ligne codé en dur n'identifie les valeurs en faveur des objets de page - de petits morceaux de texte qui sont conservés dans des variables ou des méthodes et peuvent ainsi être réutilisés et également gérés de manière centralisée. Exemples de variables javascript suivant ce modèle pour les valeurs codées en dur:

    storedVars["eqv_auto_year"] = "2015";
    storedVars["eqv_auto_make_1"] = "ALFA ROMEO";
    storedVars["eqv_auto_make_2"] = "HONDA";`  
    

    Plus d'informations sur les objets de la page sur le sélénium wiki et les documents sur le sélénium

  • Communication avec les développeurs.

    Indépendamment de l'approche technique en termes de «développeurs apportent des modifications et cassent l'automatisation de l'assurance qualité» qui est un problème de workflow. Vous devez vous assurer que: tout le monde est la même équipe; le développeur exécute les mêmes tests intégrés; les normes sont convenues et suivies par les deux groupes; la définition de done comprend l'exécution et éventuellement la mise à jour des tests d'interface utilisateur; Les développeurs et les testeurs s'appuient sur des plans de test et assistent tous deux au toilettage des tickets (si vous faites Agile) et parlent des tests d'interface utilisateur dans le cadre du toilettage. Vous devez vous assurer que l'approche et la stratégie que vous utilisez pour nommer sont coordonnées avec les développeurs d'applications. Si vous n'obtenez pas sur la même page, vous aimerez affronter la dénomination des objets. Quelques exemples de méthodes d'objet de page que j'ai récemment créées pour un projet ruby:

    def css_email_opt_in_true
      'auto_policy[email_opt_in][value=1]'
    end 
    
    def css_phone_opt_in
      '*[name="auto_policy[phone_opt_in]"]'
    end 
    
    def css_phone_opt_in_true
      'input[name=phone_opt_in][value=true]'
    end 
    
    def css_credit_rating
      'auto_policy[credit_rating]'
    end
    

    Voici les mêmes objets de page que les variables javascript:

    storedVars["css_email_opt_in"] = "css=*[name='auto_policy[email_opt_in]']";
    storedVars["css_phone_opt_in"]="css=*[name='auto_policy[phone_opt_in]']";
    storedVars["css_phone_opt_in_true"]="css=input[name='phone_opt_in'][value=true]";
    storedVars["css_credit_rating"]="css=select[name='auto_policy[credit_rating]']";
    
Michael Durrant
la source
2
Ce sont des conseils utiles, et je suis en fait déjà la plupart d'entre eux. Mon problème était que j'écrivais des tests pour un bouton, puis ce bouton était supprimé pendant que la même action était gérée ailleurs. Ou le bouton reste là, mais l'étiquette change et le bouton exécute également une action supplémentaire.
mik01aj
1
Ahh, c'est bon à savoir. Ouais pour ce genre de choses, je me concentrerais sur le workflow et l'interaction organisationnelle qa-développeur
Michael Durrant
1
Je publie également des informations pour les autres qui trouvent votre question et peuvent ne pas avoir tous les éléments en place que vous avez ou ne les connaissez peut-être même pas.
Michael Durrant
1
J'ai développé mon dernier point bulllet en fonction de vos commentaires.
Michael Durrant
1
Et j'ai mis à jour les parties sur la dénomination et l'identification des éléments et sur la non-utilisation du texte visible.
Michael Durrant
3

La raison pour laquelle les gens ont développé des choses comme MVC, MVP et le présentateur, et des modèles de conception similaires, était de séparer la logique métier de l'interface utilisateur.

De toute évidence, la partie vue ne peut être testée qu'en démarrant le programme et en vérifiant ce qu'elle montre - en d'autres termes, elle ne peut être testée que dans des tests d'acceptation.

D'un autre côté, le test de la logique métier peut être effectué dans des tests unitaires. Et c'est la réponse à votre question. Testez tout dans le modèle, et si vous le pouvez et le souhaitez, vous pouvez également tester le code du contrôleur.


Les interfaces graphiques changent beaucoup

Cela ne peut se produire que lorsque vous avez des exigences changeantes. Lorsqu'une exigence change, il n'y a aucun moyen de la contourner, sauf pour modifier le code. Si vous parvenez à créer une bonne conception et une bonne architecture, le changement ne se propagera pas à de nombreux endroits.

BЈовић
la source
2
Je pense que c'est un bon point. Peut-être que je fais mal MVC :) Btw, je crois que les exigences changeantes sont inévitables lorsque vous développez une plate-forme Web pour de nombreux utilisateurs. Vous ne savez pas comment vos utilisateurs se comporteront jusqu'à ce qu'ils commencent à utiliser l'interface graphique.
mik01aj
2

Les tests d'interaction GUI ne doivent pas être plus ou moins fragiles que tout autre type de test. C'est; si votre application change d'une manière ou d'une autre, les tests doivent être mis à jour pour refléter cela.

A titre de comparaison:

Test de l'unité

Original : validateEmail()devrait jeter unInvalidData exception. Ce qui est correctement couvert dans votre test unitaire.

Changement : validateEmail()devrait lever une InvalidEmailexception. Maintenant, votre test est incorrect, vous le mettez à jour et tout redevient vert.

Test GUI

Original : la saisie d'un e-mail non valide entraînera une boîte d'erreur contextuelle contenant "Données non valides saisies". Détecté correctement par vos tests.

Modification : la saisie d'un e-mail non valide entraînera une erreur en ligne contenant "E-mail non valide entré". Maintenant, votre test est incorrect, vous le mettez à jour et tout redevient vert.


N'oubliez pas que vous testez les entrées et les sorties - un comportement bien défini. Que ce soit un test GUI ou un test unitaire ou un test d'intégration, etc.

Jess Telford
la source