Test de bout en bout avec Joomla! et Cypress - Mes premiers pas et réflexions

Les tests automatisés ne sont pas un outil spécial pour les développeurs de logiciels dans les grands projets. Surtout pour les extensions, les tests automatisés sont une aide pour identifier rapidement les problèmes. Ils aident à s'assurer que les extensions fonctionnent correctement dans les nouvelles versions de Joomla.

Les développeurs de Joomla Core veulent que les développeurs de logiciels tiers testent leurs extensions, pour trouver des bogues avant qu'un utilisateur ne les trouve. Cela prend beaucoup de temps et est un travail ennuyeux. Par conséquent, cela n'est souvent pas fait. Surtout pas si cela doit être fait. manuellement par des humains pour chaque nouvelle version. Les tests automatisés permettent de répéter les étapes manuelles pour chaque version sans qu'un humain n'exécute les étapes lui-même. De cette façon, les bogues sont trouvés avant qu'un utilisateur ne les rencontre lorsqu'il accède au système en direct.

Soit dit en passant, quiconque souhaite découvrir Cypress trouvera dans ce texte un bon point de départ. Vous pouvez tester des problèmes sans avoir à tout installer et configurer vous-même. Dans le référentiel Github du projet Joomla, tout est prêt à l'emploi.

Introduction

"S'il est vrai que la qualité ne peut pas être testée, il est également évident que sans test, il est impossible de développer quoi que ce soit de qualité." - [James A. Whittaker]

Avant de rencontrer Cypress pour la première fois, je n'aurais pas pu imaginer que les obstacles qui gênaient souvent mes tests seraient quelque peu écartés. J'ai passé beaucoup de temps à tester des logiciels - et plus tôt encore plus de temps à résoudre les problèmes. qui est apparue en raison d'un manque de tests ! Maintenant, je suis convaincu que les tests qui sont :

  • au plus près dans le temps de la programmation,
  • automatiquement,
  • fréquemment - idéalement après chaque changement de programme,

    apportez plus que ce qu'ils coûtent.Et qui plus est : les tests peuvent même être amusants.

Cela vaut la peine d'apprendre les méthodes de test ! Les méthodes de test sont durables, car elles peuvent être utilisées non seulement avec n'importe quel langage de programmation, mais elles peuvent être appliquées à presque n'importe quel travail humain. Vous devriez tester presque tout ce qui est important dans la vie de temps en temps. Les méthodes de test sont indépendantes des outils logiciels spécifiques.Contrairement aux techniques de programmation ou aux langages de programmation, qui sont souvent à la mode et passés de mode, la connaissance de la façon de mettre en place de bons tests est intemporelle.

Qui doit lire ce texte ?

Quiconque pense que tester un logiciel est une perte de temps devrait jeter un œil à cet article. En particulier, j'aimerais inviter les développeurs à le lire qui ont toujours voulu écrire des tests pour leur logiciel - mais qui ne l'ont jamais fait pour une variété Cypress pourrait être un moyen de supprimer ces obstacles.

Un peu de théorie

Le triangle magique

Le triangle magique décrit la relation entre les coûts, le temps requis et la qualité réalisable. A l'origine, cette relation était reconnue et décrite dans la gestion de projet. Cependant, vous avez probablement entendu parler de cette tension dans d'autres domaines également. processus dans une entreprise.

Par exemple, on suppose généralement qu'un coût plus élevé a un impact positif sur la qualité et/ou la date d'achèvement, c'est-à-dire le temps.

 

Le triangle magique dans la gestion de projet - Si plus d'argent est investi dans le projet, cela a un impact positif sur la qualité ou le temps.

À l'inverse, une économie de coûts forcera la qualité à diminuer et/ou l'achèvement à être retardé.

Le triangle magique dans la gestion de projet - Si moins d'argent est investi dans le projet, cela a un impact négatif sur la qualité ou le temps.

Maintenant, la magie entre en jeu : nous surmontons la corrélation entre le temps, les coûts et la qualité ! Parce qu'à long terme, cela peut effectivement être surmonté.

Le lien entre le temps, les coûts et la qualité peut être surmonté à long terme.

Peut-être avez-vous vous aussi constaté dans la pratique qu'une réduction de la qualité ne se traduit pas par des économies de coûts à long terme.La dette technique que cela crée entraîne souvent même des augmentations de coûts et du temps supplémentaire.

À long terme, la corrélation entre le coût, le temps et la qualité peut en fait être surmontée.

La dette technique fait référence à l'effort supplémentaire nécessaire pour apporter des modifications et des améliorations à un logiciel mal programmé par rapport à un logiciel bien écrit. Martin Fowler distingue les types de dette technique suivants : ceux que l'on a contractés délibérément et ceux que l'on a contractés par inadvertance Il distingue également les dettes techniques prudentes et imprudentes.

Dette technique

Coûts et bénéfices

Dans la littérature, vous trouverez des statistiques dévastatrices sur les chances de succès des projets logiciels. Peu de choses ont changé dans l'image négative qui était déjà enregistrée dans une étude d'AW Feyhl dans les années 1990. Ici, dans une analyse de 162 projets dans 50 organisations , L'écart de coût par rapport à la planification initiale a été déterminé : 70 % des projets ont montré un écart de coût d'au moins 50 % ! Quelque chose ne va pas ! Vous ne pouvez pas accepter cela, n'est-ce pas ?

Une solution serait de renoncer complètement aux estimations de coûts et de suivre l'argumentation du mouvement #NoEstimates . Ce mouvement est d'avis que les estimations de coûts dans un projet logiciel sont absurdes. Un projet logiciel contient selon l'opinion de #NoEstimates toujours la production de quelque chose de nouveau. Le nouveau n'est pas comparable avec des expériences déjà existantes et donc pas prévisible.

Plus j'acquiers d'expérience, plus j'arrive à la conclusion que les vues extrêmes ne sont pas bonnes. La solution est presque toujours au milieu. Évitez également les extrêmes dans les projets logiciels et cherchez un milieu. Il n'est pas nécessaire d'avoir un 100 % plan sûr. Mais vous ne devriez pas non plus démarrer un nouveau projet naïvement. Bien que la gestion de projet logiciel et en particulier l'estimation des coûts soient un sujet important, je ne vais pas vous ennuyer plus longtemps avec cela dans ce texte. L'objectif de cet article est de montrer comment E2E les tests peuvent être intégrés dans le flux de travail pratique du développement logiciel.

Intégrez les tests de logiciels dans votre flux de travail

Vous avez décidé de tester votre logiciel. Super ! Quel est le meilleur moment pour le faire ? Examinons le coût de la correction d'un bogue dans les différentes phases du projet. Plus tôt vous trouvez un bogue, plus le coût de sa correction est faible .

Coûts relatifs du dépannage à différentes étapes du projet

Test et débogage : il y a des mots qui sont souvent mentionnés dans le même souffle et dont les significations sont donc égalisées. À y regarder de plus près, cependant, les termes ont des interprétations différentes. Test et débogage appartiennent à ces mots. Les deux termes ont en commun qu'ils détectent les dysfonctionnements. Mais il y a aussi des différences de sens.

  • Les tests détectent des dysfonctionnements inconnus au cours du développement. Trouver le dysfonctionnement est coûteux, tandis que la localisation et l'élimination de l'erreur sont peu coûteuses.
  • Les débogueurs corrigent les dysfonctionnements détectés une fois le produit terminé. La recherche du dysfonctionnement est gratuite, mais la localisation et la correction de l'erreur coûtent cher.

Conclusion : Il est plus logique de commencer à intégrer les tests le plus tôt possible. Malheureusement, cela est difficile à mettre en œuvre dans un projet open source comme Joomla avec des contributeurs majoritairement bénévoles.

Intégration Continue (IC)
Intégration continue des tests

Imaginez le scénario suivant. Une nouvelle version d'un système de gestion de contenu populaire est sur le point de sortir. Tout ce que les développeurs de l'équipe ont contribué depuis la dernière version est maintenant utilisé ensemble pour la première fois. La tension monte ! Ça marche ? Tous les tests seront-ils réussis - si le projet intègre des tests. Ou la sortie de la nouvelle version devra-t-elle être reportée à nouveau et des heures éprouvantes de correction de bogues nous attendent ? Au fait, reporter la date de sortie n'est pas non plus bon pour l'image du produit logiciel ! Aucun développeur n'aime vivre ce scénario. Il est préférable de savoir à tout moment dans quel état se trouve actuellement le projet logiciel ? Le code qui ne correspond pas à celui existant ne doit être intégré qu'après ils ont été "faits pour s'adapter".Surtout à une époque où il est de plus en plus courant qu'une faille de sécurité doive être corrigée, un projet doit toujours pouvoir créer une version ! Et c'est là que l'intégration continue entre en jeu.

Dans l'intégration continue, les éléments individuels du logiciel sont intégrés de manière permanente. Le logiciel est créé et testé en petits cycles. De cette façon, vous rencontrez des problèmes lors de l'intégration ou des tests erronés à un stade précoce et non des jours ou des semaines plus tard. Avec une intégration réussie, Le dépannage est beaucoup plus facile car les erreurs sont découvertes peu de temps après la programmation et généralement seule une petite partie du programme est affectée. Joomla intègre le nouveau code en utilisant l'intégration continue. Le nouveau code n'est intégré que lorsque tous les tests sont réussis.

Avec une intégration continue de nouveaux logiciels, le dépannage est beaucoup plus facile car les erreurs sont découvertes peu de temps après la programmation et généralement seule une petite partie du programme est affectée.

Pour vous assurer que vous avez des tests pour toutes les parties du programme disponibles à tout moment pendant l'intégration continue, vous devez développer un logiciel piloté par les tests.

Développement piloté par les tests (TDD)

Le développement piloté par les tests est une technique de programmation qui utilise le développement par petites étapes. Vous écrivez d'abord le code de test. Ce n'est qu'ensuite que vous créez le code de programme à tester. Toute modification du programme n'est effectuée qu'après que le code de test de cette modification a été ont été créés. Ainsi, vos tests échouent immédiatement après la création. La fonction requise n'est pas encore implémentée dans le programme. Ce n'est qu'alors que vous créez le code de programme réel, c'est-à-dire le code de programme qui satisfait le test.

Les tests TDD vous aident à écrire le programme correctement .

Lorsque vous entendez parler de cette technique pour la première fois, vous n'êtes peut-être pas à l'aise avec le concept. "" L'humain "" veut toujours faire quelque chose de productif en premier, après tout. Et écrire des tests ne semble pas productif à première vue. Essayez-le. Parfois vous ne devenez ami avec une nouvelle technique qu'après l'avoir connue ! Dans les projets avec une couverture de test élevée, je me sens plus à l'aise lorsque j'ajoute de nouvelles fonctionnalités.

Si vous parcourez la partie exercice à la fin du texte, vous pouvez l'essayer. Créez d'abord le test, puis écrivez le code pour Joomla Core. Ensuite, soumettez le tout ensemble sous forme de PR sur Github. Si tout le monde le faisait , Joomla aurait une couverture de test idéale.

Développement axé sur le comportement (BDD)

BDD n'est pas une autre technique de programmation ou de test, mais une sorte de meilleure pratique pour le développement de logiciels. BDD est idéalement utilisé avec TDD. En principe, Behaviour-Driven-Development signifie tester non pas l'implémentation du code du programme, mais l'exécution - c'est-à-dire le comportement du programme Un test vérifie si la spécification, c'est-à-dire l'exigence du client, est satisfaite.

Lorsque vous développez un logiciel en fonction du comportement, les tests vous aident non seulement à écrire le programme correctement, mais aussi à écrire le bon programme .

Qu'est-ce que j'entends par là : "Écrire le bon programme" ? Il arrive que les utilisateurs voient les choses différemment des développeurs. Le flux de travail de suppression d'un article dans Joomla en est un exemple. Je rencontre encore et encore des utilisateurs qui cliquent sur l'icône d'état dans le corbeille et sont surpris. L'utilisateur suppose généralement intuitivement que l'élément est désormais définitivement supprimé, mais il passe de la corbeille à l'activation. Pour le développeur, cliquer sur l'icône d'état est un changement d'état, une bascule dans toutes les autres vues. Pourquoi cela devrait-il être différent dans la corbeille ? Pour le développeur, la fonction est implémentée sans erreur. Joomla fonctionne correctement. Mais à mes yeux, la fonction n'est pas la bonne à cet endroit, car la plupart des utilisateurs la décriraient / la demanderaient de manière assez différente .

Dans le Behavior Driven Development, les exigences du logiciel sont décrites à travers des exemples appelés scénarios ou user stories.

  • une forte implication de l'utilisateur final dans le processus de développement du logiciel,
  • la documentation de toutes les phases du projet avec des user stories/exemples de cas sous forme de texte - généralement dans le langage de description dans le langage de description Gherkin,
  • test automatique de ces user stories/case studies,
  • implémentations successives.Ainsi, une description du logiciel à implémenter est accessible à tout moment.A l'aide de cette description, vous pouvez vous assurer en permanence de l'exactitude du code de programme déjà implémenté.

Le projet Joomla a introduit BDD dans un projet Google Summer of Code . On espérait que les utilisateurs sans connaissances en programmation pourraient participer plus facilement en utilisant Gherkin ). L'approche n'a pas été suivie de manière cohérente. À cette époque, Joomla utilisait Codeception comme outil de test.Avec Cypress, le développement BDD est également possible de se développer à la manière BDD.

Planification

Types d'essais
  • Tests unitaires : Un test unitaire est un test qui teste indépendamment les plus petites unités du programme.
  • Tests d'intégration : Un test d'intégration est un test qui teste l'interaction d'unités individuelles.
  • Tests E2E ou tests d'acceptation : un test d'acceptation vérifie si le programme remplit la tâche définie au début.
Stratégies

Si vous souhaitez ajouter une nouvelle fonction dans Joomla et la sécuriser avec des tests, vous pouvez procéder de deux manières.

Top-down et bottom-up sont deux approches fondamentalement différentes pour comprendre et présenter des problèmes complexes. Top-down va pas à pas de l'abstrait et du général au concret et au spécifique. Pour illustrer cela par un exemple : un système de gestion de contenu comme Joomla présente généralement les sites Web dans un navigateur. Concrètement, cependant, il existe un certain nombre de petites sous-tâches dans ce processus. L'une d'entre elles est la tâche d'afficher un texte spécifique dans un titre.

Bottom-up décrit la direction opposée : à ce stade, il convient de rappeler une fois de plus qu'un élément du développement piloté par le comportement est la création d'une description textuelle du comportement du logiciel. Cette description des critères d'acceptation aide à créer des tests - en particulier les meilleurs des tests de bout en bout ou des tests d'acceptation.

L'approche habituelle pour créer des tests aujourd'hui est par le bas. Si vous préférez le développement de logiciels axé sur le comportement, vous devez utiliser la stratégie opposée. Vous devez utiliser la stratégie descendante. Avec une stratégie descendante, un malentendu est identifié très tôt. en phase de conception.

Stratégies de test : tests descendants et tests ascendants

  • Tests descendants : lors de l'application de la stratégie descendante, on commence par les tests d'acceptation - c'est-à-dire avec la partie du système qui est la plus étroitement liée aux exigences de l'utilisateur. Pour les logiciels écrits pour des utilisateurs humains, il s'agit généralement de l'interface utilisateur . L'accent est mis sur le test de la façon dont un utilisateur interagit avec le système. Un inconvénient des tests descendants est qu'il faut passer beaucoup de temps à créer des doublons de test. Les composants qui ne sont pas encore intégrés doivent être remplacés par des espaces réservés. n'y a pas de véritable code de programme au départ, il faut donc créer artificiellement des parties manquantes, puis progressivement ces données artificielles sont remplacées par des données réellement calculées.

  • Tests ascendants : si vous suivez la stratégie ascendante, vous commencez par des tests unitaires. Au début, le développeur a en tête l'état cible. Cependant, il décompose ensuite cet objectif en composants individuels. Le problème avec le L'approche ascendante est qu'il est difficile de tester comment un composant sera utilisé par la suite dans des situations réelles. L'avantage des tests ascendants est que nous avons terminé des parties logicielles très rapidement. Cependant, ces parties doivent être utilisées avec prudence. Ils fonctionnent correctement, c'est ce que garantissent les tests unitaires, mais il n'est pas certain que le résultat final corresponde vraiment à ce que le client imagine du logiciel.

La pyramide des tests de Mike Cohn

Combien de tests doivent être mis en œuvre de quel type de test ? La pyramide des tests de Mike Cohn décrit un concept pour l'utilisation des tests logiciels automatisés. La pyramide se compose de trois niveaux, structurés en fonction de la fréquence d'utilisation et de la pertinence. ‍

Idéalement, la base de la pyramide des tests est constituée de nombreux tests unitaires rapides et faciles à gérer, ce qui permet de détecter rapidement la plupart des erreurs.

Au niveau intermédiaire se trouvent les tests d'intégration, ils fournissent des services de tests ciblés d'interfaces critiques, les temps d'exécution des tests d'intégration sont plus longs et leur maintenance est également plus complexe que celle des tests unitaires.

Le sommet de la pyramide est constitué de tests E2E lents, qui nécessitent parfois beaucoup de maintenance.Les tests E2E sont très utiles pour tester l'application en tant que système complet.

Exigences

De quel matériel avez-vous besoin pour travailler sur la partie pratique suivante ?

Quelles sont les conditions requises pour travailler activement sur la partie pratique suivante ? Pour pouvoir travailler sur le contenu de ce manuel, vous n'avez pas à répondre à de nombreuses exigences. Bien sûr, vous devez disposer d'un ordinateur. Un environnement de développement avec Git, NodeJS et Composer et un serveur Web local doivent être installés ou installables dessus.

Quelles connaissances devez-vous posséder personnellement ?

Vous devez connaître les techniques de programmation de base. Idéalement, vous avez déjà programmé une petite application web. Dans tous les cas, vous devez savoir où stocker les fichiers sur votre ordinateur de développement et comment les charger dans votre navigateur internet. sortir de nouvelles choses.

Essayez-le. Intégrez des tests dans votre prochain projet. Peut-être que votre première expérience d'un test vous évitera une session de débogage fastidieuse ou un bogue embarrassant dans le système réel. Après tout, avec un filet de sécurité de tests, vous pouvez développer des logiciels avec moins stress.

Mise en place

Configurer Cypress avec Joomla!

Dans la version développeur disponible sur Github, Joomla est Cypress ready configuré. Il existe déjà des tests que vous pouvez utiliser comme guide. Il n'est donc pas nécessaire de tout configurer vous-même pour avoir un premier aperçu. De cette façon, vous pouvez expérimenter avec Cypress , découvrez ses avantages et ses inconvénients et décidez vous-même si vous souhaitez utiliser l'outil de test.

Étapes pour configurer l'environnement local :

Clonez le référentiel à la racine de votre serveur Web local :

$ git clone https://github.com/joomla/joomla-cms.git

Accédez au dossier joomla-cms :

$ cd joomla-cms

Selon Joomla Roadmap, la prochaine version majeure 5.0 sortira en octobre 2023. Pour être à jour, j'utilise cette version de développement ici.

Passez à la branche 5.0-dev :

$ git checkout 5.0-dev

Installez tous les packages composer nécessaires :

$ composer install

Installez tous les packages npm nécessaires :

$ npm install

Pour plus d'informations et d'aide sur la configuration de votre poste de travail, consultez l'article de la documentation Joomla "Setting Up Your Workstation for Joomla Development" . Pour Cypress, il y a des informations sur cypress.io . Mais ce n'est pas nécessaire à ce stade. Joomla configure tout Il vous suffit de paramétrer vos données individuelles via le fichier de configuration joomla-cms/cypress.config.js.

Configurez vos données individuelles. Pour cela, vous pouvez utiliser le modèle joomla-cms/cypress.config.dist.jscomme orientation. Dans mon cas, ce fichier ressemble à ceci :

const { defineConfig } = require('cypress')

module.exports = defineConfig({
  fixturesFolder: 'tests/cypress/fixtures',
  videosFolder: 'tests/cypress/output/videos',
  screenshotsFolder: 'tests/cypress/output/screenshots',
  viewportHeight: 1000,
  viewportWidth: 1200,
  e2e: {
    setupNodeEvents(on, config) {},
    baseUrl: 'http://localhost/joomla-cms',
    specPattern: [
      'tests/cypress/integration/install/*.cy.{js,jsx,ts,tsx}',
      'tests/cypress/integration/administrator/**/*.cy.{js,jsx,ts,tsx}',
      'tests/cypress/integration/module/**/*.cy.{js,jsx,ts,tsx}',
      'tests/cypress/integration/site/**/*.cy.{js,jsx,ts,tsx}'
    ],
    supportFile: 'tests/cypress/support/index.js',
    scrollBehavior: 'center',
    browser: 'firefox',
    screenshotOnRunFailure: true,
    video: false
  },
  env: {
    sitename: 'Joomla CMS Test',
    name: 'admin',
    email: Cette adresse e-mail est protégée contre les robots spammeurs. Vous devez activer le JavaScript pour la visualiser.',
    username: 'admin',
    password: 'adminadminadmin',
    db_type: 'MySQLi',
    db_host: 'mysql',
    db_name: 'test_joomla',
    db_user: 'root',
    db_password: 'root',
    db_prefix: 'j4_',
  },
})

Concrètement, j'ai ajouté le répertoire tests/cypress/integration/module/**/*.cy.{js,jsx,ts,tsx}au specPattern Array, car je veux y enregistrer le test des modules plus tard. Ensuite, j'ai changé le nom d'utilisateur et les mots de passe car je veux aussi tester l'installation manuellement et mieux mémoriser ceux que j'ai auto-assignés. J'utilise un conteneur Docker comme base de données. J'ai donc changé le serveur de base de données et les données d'accès. Et enfin, j'ai dû définir l'URL racine http://localhost/joomla-cmsde mon installation Joomla.

Utiliser le cyprès

Via le navigateur Web

Appel npm run cypress:openvia CLI dans votre répertoire racine Joomla. Peu de temps après, l'application Cypress s'ouvrira. Nous avons précédemment créé le fichier joomla-cms/cypress.config.dist.js. Que cela est détecté peut être vu du fait que E2E Testing est spécifié comme configuré.

L'application Cypress s'ouvre après avoir appelé 96;npm run cypress:open96;.

Ici, vous pouvez choisir si vous souhaitez exécuter les tests E2E et quel navigateur vous souhaitez utiliser. Pour l'exemple, j'ai choisi l'option "Démarrer les tests dans Firefox".

Tests E2E dans l'application Cypress : sélectionnez le navigateur à utiliser.

Toutes les suites de tests disponibles seront listées et vous pourrez cliquer sur celle que vous souhaitez exécuter. Lorsque vous sélectionnez une suite de tests, les tests s'exécuteront et vous pourrez voir l'exécution des tests en temps réel dans le navigateur.

Suite de tests Joomla dans Firefox via l'application Cypress.

Pendant que les tests sont en cours, vous pouvez voir le script exécuté d'un côté et le résultat dans le navigateur du côté droit.Ce ne sont pas seulement des captures d'écran, mais de véritables instantanés du navigateur à ce moment-là, vous pouvez donc voir le code HTML réel Des captures d'écran et même des vidéos des tests sont également possibles.

Test d'installation de Joomla en cours.

Essayez-le.Si vous utilisez as, db_host: 'localhost',vous pouvez tester l'installation et ainsi avoir correctement configuré Joomla pour le travail sur la partie suivante de ce texte.

Si, comme moi, vous utilisez une source externe (pas lcoalhost ; j'utilise un conteneur docker) comme db_host, le test pour ce type d'installation n'est pas encore prêt. Dans ce cas, il y a une question de sécurité dans la routine d'installation, qui est pas encore pris en compte dans les tests. Dans ce cas, installez Joomla manuellement avec les informations saisies dans le fichier joomla-cms/cypress.config.js. Les tests suivants utiliseront les paramètres de ce fichier de configuration, par exemple pour se connecter à la zone d'administration de Joomla. De cette façon, le développeur de test fait pas besoin de se soucier de la saisie des données de connexion. L'utilisateur et le mot de passe correspondants sont toujours utilisés automatiquement à partir du fichier de configuration.

Sans tête

Par défaut, cypress runexécute tous les tests sans tête . La commande suivante exécute tous les tests déjà codés et enregistre les captures d'écran dans le répertoire /joomla-cms/tests/cypress/output/screenshotsen cas d'erreur. Le répertoire de sortie a été défini dans le cypress.config.jsfichier.

$ npm run cypress:run

Autres commandes CLI

Il existe d'autres commandes utiles qui ne sont pas implémentées sous forme de scripts dans le package.jsonprojet Joomla. Je les exécute via npx [docs.npmjs.com/commands/npx].

cyprès vérifier

La cypress verifycommande vérifie que Cypress est correctement installé et peut être exécuté.

$ npx cypress verify

✔  Verified Cypress! /.../.cache/Cypress/12.8.1/Cypress
infos sur le cyprès

La cypress infocommande génère des informations sur Cypress et l'environnement actuel.

$ npx cypress info
Displaying Cypress info...

Detected 2 browsers installed:

1. Chromium
  - Name: chromium
  - Channel: stable
  - Version: 113.0.5672.126
  - Executable: chromium
  - Profile: /.../snap/chromium/current

2. Firefox
  - Name: firefox
  - Channel: stable
  - Version: 113.0.1
  - Executable: firefox
  - Profile: /.../snap/firefox/current/Cypress/firefox-stable

Note: to run these browsers, pass : to the '--browser' field

Examples:
- cypress run --browser chromium
- cypress run --browser firefox

Learn More: https://on.cypress.io/launching-browsers

Proxy Settings: none detected
Environment Variables: none detected

Application Data: /.../.config/cypress/cy/development
Browser Profiles: /.../.config/cypress/cy/development/browsers
Binary Caches: /.../.cache/Cypress

Cypress Version: 12.8.1 (stable)
System Platform: linux (Ubuntu - 22.04)
System Memory: 4.08 GB free 788 MB
version cyprès

La cypress versioncommande imprime la version binaire Cypress installée, la version du package Cypress, la version d'Electron utilisée pour créer Cypress et la version du nœud groupé.

$ npx cypress version
Cypress package version: 12.8.1
Cypress binary version: 12.8.1
Electron version: 21.0.0
Bundled Node version: 16.16.0

La documentation de Cypress fournit des informations plus détaillées.

Écrire le premier propre test

Si tout a fonctionné jusqu'à présent, nous pouvons commencer à créer nos propres tests.

Obtenez un aperçu

Apprendre des tests déjà développés

Dans la version de développement du CMS Joomla, il existe déjà des tests Cypress. Ceux-ci se trouvent dans le dossier /tests/System/integration. Ceux qui aiment apprendre par l'exemple trouveront ici une introduction appropriée.

Importer du code pour les tâches répétitives

Les développeurs Joomla travaillent sur le projet NodeJs joomla-cypress , qui fournit du code de test pour les cas de test courants. Ceux-ci sont importés lors de l'installation de la version développeur du CMS en utilisant npm installvia

  • package.jsonet via le
  • fichier de support /tests/System/support/index.jsLe fichier de support est défini dans la configuration cypress.config.js.
// package.json
{
  "name": "joomla",
  "version": "5.0.0",
  "description": "Joomla CMS",
  "license": "GPL-2.0-or-later",
  "repository": {
    "type": "git",
    "url": "https://github.com/joomla/joomla-cms.git"
  },
...
  "devDependencies": {
    ...
    "joomla-cypress": "^0.0.16",
    ...
  }
}

Un exemple est le clic sur un bouton de la barre d'outils. Par exemple, Cypress.Commands.add('clickToolbarButton', clickToolbarButton)rend la commande clickToolbarButton()disponible dans les tests personnalisés et via cy.clickToolbarButton('new')un clic sur le bouton Newest simulée. Le code requis pour cela est indiqué dans le code ci-dessous.

// node_modules/joomla-cypress/src/common.js
...
const clickToolbarButton = (button, subselector = null) => {
  cy.log('**Click on a toolbar button**')
  cy.log('Button: ' + button)
  cy.log('Subselector: ' + subselector)

  switch (button.toLowerCase())
  {
    case "new":
      cy.get("#toolbar-new").click()
      break
    case "publish":
      cy.get("#status-group-children-publish").click()
      break
    case "unpublish":
      cy.get("#status-group-children-unpublish").click()
      break
    case "archive":
      cy.get("#status-group-children-archive").click();
      break
    case "check-in":
      cy.get("#status-group-children-checkin").click()
      break
    case "batch":
      cy.get("#status-group-children-batch").click()
      break
    case "rebuild":
      cy.get('#toolbar-refresh button').click()
      break
    case "trash":
      cy.get("#status-group-children-trash").click()
      break
    case "save":
      cy.get("#toolbar-apply").click()
      break
    case "save & close":
      cy.get(".button-save").contains('Save & Close').click()
      break
    case "save & new":
      cy.get("#save-group-children-save-new").click()
      break
    case "cancel":
      cy.get("#toolbar-cancel").click()
      break
    case "options":
      cy.get("#toolbar-options").click()
      break
    case "empty trash":
    case "delete":
      cy.get("#toolbar-delete").click()
      break
    case "feature":
      cy.get("#status-group-children-featured").click()
      break
    case "unfeature":
      cy.get("#status-group-children-unfeatured").click()
      break
    case "action":
      cy.get("#toolbar-status-group").click()
      break
    case "transition":
      cy.get(".button-transition.transition-" + subselector).click()
      break
  }

  cy.log('--Click on a toolbar button--')
}

Cypress.Commands.add('clickToolbarButton', clickToolbarButton)
...

Le code suivant montre un autre exemple, la connexion à la zone d'administration.

// /node_modules/joomla-cypress/src/user.js
...
const doAdministratorLogin = (user, password, useSnapshot = true) => {
  cy.log('**Do administrator login**')
  cy.log('User: ' + user)
  cy.log('Password: ' + password)

  cy.visit('administrator/index.php')
  cy.get('#mod-login-username').type(user)
  cy.get('#mod-login-password').type(password)
  cy.get('#btn-login-submit').click()
  cy.get('h1.page-title').should('contain', 'Home Dashboard')

  cy.log('--Do administrator login--')
}

Cypress.Commands.add('doAdministratorLogin', doAdministratorLogin)

...
Tâches courantes dans l'environnement individuel

Dans le répertoire, /tests/System/supportvous trouverez des tâches courantes dans l'environnement individuel. Afin qu'elles puissent être facilement réutilisées, elles sont importées via le fichier de support. /tests/System/support/index.jsUn exemple de tâche fréquemment répétée est la connexion à la zone d'administration, qui est gérée dans le fichier /tests/System/support/commands.jsen utilisant la fonction doAdministratorLogin.

Le code suivant montre également comment les informations de la cypress.config.jsconfiguration sont utilisées dans les tests. Cypress.env('username')reçoit la valeur de la usernamepropriété au sein du groupe env.

De plus, nous pouvons voir ici comment écraser les commandes. Cypress.Commands.overwrite('doAdministratorLogin' ...),écrase le code que nous venons de voir dans le package joomla-cypress. L'avantage est que l'utilisateur et le mot de passe sont automatiquement utilisés à partir de la configuration individuelle.

// /tests/System/support/commands.js
...
Cypress.Commands.overwrite('doAdministratorLogin', (originalFn, username, password, useSnapshot = true) => {
  // Ensure there are valid credentials
  const user = username ?? Cypress.env('username');
  const pw = password ?? Cypress.env('password');

  // Do normal login when no snapshot should be used
  if (!useSnapshot) {
    // Clear the session data
    Cypress.session.clearAllSavedSessions();

    // Call the normal function
    return originalFn(user, pw);
  }

  // Do login through the session
  return cy.session([user, pw, 'back'], () => originalFn(user, pw), { cacheAcrossSpecs: true });
});
...

Installez votre propre extension Joomla

Afin de voir comment tester votre propre code, nous allons installer un exemple de composant simple via le backend Joomla. Le fichier d'installation peut être téléchargé depuis Codeberg .

Installez votre propre extension Joomla.

Après l'installation, vous pouvez trouver un lien vers la vue du composant Foo dans la barre latérale gauche du backend Joomla.

Vue de l'exemple de composant dans le backend Joomla.

Nous avons maintenant un environnement de test configuré et un code à tester.

Le premier test personnel

Crochets

Lors du test du backend, vous remarquerez que vous devez démarrer chaque test avec une connexion. Nous pouvons empêcher ce code redondant en utilisant la beforeEach()fonction. Ce soi-disant crochet exécute le code que nous entrons avant que chaque test ne soit exécuté. D'où le nom beforeEach().

Cypress fournit plusieurs types de crochets , y compris beforeet afterdes crochets qui s'exécutent avant ou après les tests dans un groupe de tests, et beforeEachet afterEachdes crochets qui s'exécutent avant ou après chaque test individuel dans le groupe. Les crochets peuvent être définis globalement ou dans un describedbloc spécifique. L'exemple de code dans le fichier tests/System/integration/administrator/components/com_foos/FoosList.cy.jsentraîne l'exécution d'une connexion dans le backend avant chaque test dans le describedbloc test com_foos features.

Nous commençons maintenant par la partie pratique et créons le fichier tests/System/integration/administrator/components/com_foos/FoosList.cy.jsavec le code suivant avant d'écrire le premier test productif. Notre premier exemple devrait nous connecter avec succès au backend avant tout test ! Nous le testerons après avoir créé le premier test.

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js

describe('Test com_foos features', () => {
  beforeEach(() => {
    cy.doAdministratorLogin()
  })

})

Remarque : les hooks, qui sont implémentés dans file /tests/System/support/index.js, s'appliquent à chaque fichier de test de la combinaison de test.

Un essai réussi

Le composant que nous avons installé pour les tests contient les trois éléments Astrid, Ninaet Elmar. Tout d'abord, nous testons si ces éléments ont été créés avec succès.

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js

describe('Test com_foos features', () => {
  beforeEach(() => {
    cy.doAdministratorLogin()
  })

  it('list view shows items', function () {
    cy.visit('administrator/index.php?option=com_foos')

    cy.get('main').should('contain.text', 'Astrid')
    cy.get('main').should('contain.text', 'Nina')
    cy.get('main').should('contain.text', 'Elmar')

    cy.checkForPhpNoticesOrWarnings()
  })
})

Remarque : La fonction checkForPhpNoticesOrWarnings()que vous trouvez dans le fichier /node_modules/joomla-cypress/src/support.js.

Nous obtenons l'élément DOM mainvia la commande Cypress get

Vous devriez trouver le test que vous venez de créer FooList.cy.jsdans la liste des tests disponibles dans la barre latérale de gauche. Si ce n'est pas le cas, fermez le navigateur et exécutez npm run cypress:openà nouveau.

Test d'exécution de Joomla pour sa propre extension.

Cliquez sur le nom du test pour l'exécuter. Il devrait se terminer avec succès et vous verrez des messages verts.

La vue après que le test a été exécuté avec succès.

Un essai raté

Ajoutez la ligne cy.get('main').should('contain.text', 'Sami')au fichier de test afin que l'exécution échoue. Aucun élément ne porte ce nom. Après avoir enregistré le fichier de test, Cypress remarque la modification. Après chaque modification, Cypress réexécute automatiquement tous les tests du fichier de test.

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js
describe('Test com_foos features', () => {
  beforeEach(() => {
    cy.doAdministratorLogin()
  })

  it('list view shows items', function () {
    cy.visit('administrator/index.php?option=com_foos')

    cy.get('main').should('contain.text', 'Astrid')
    cy.get('main').should('contain.text', 'Nina')
    cy.get('main').should('contain.text', 'Elmar')
    cy.get('main').should('contain.text', 'Sami')

    cy.checkForPhpNoticesOrWarnings()
  })
})

Comme prévu, le test échoue. Il y a des messages rouges. Vous pouvez voir le code de chaque étape du test dans la barre latérale gauche. Il est donc possible de trouver la raison de l'erreur. Pour chaque étape, il y a un instantané du document HTML, afin que vous puissiez vérifier le balisage à tout moment, ce qui est utile, en particulier pendant le développement.

La vue après l'échec du test.

Exécuter un seul test dans un fichier

Notre extension de démonstration contient plusieurs mises en page. Ajoutez un test pour tester la mise en page de l'état vide. Étant donné que nous avons maintenant deux tests dans ce fichier, Cypress exécutera toujours les deux tests chaque fois que nous enregistrerons le fichier. Nous pouvons utiliser pour qu'un seul .only()test est exécuté:

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js

describe('Test com_foos features', () => {
    beforeEach(() => {
        cy.doAdministratorLogin()
    })

    it('list view shows items', function () {
        cy.visit('administrator/index.php?option=com_foos')

        cy.get('main').should('contain.text', 'Astrid')
        cy.get('main').should('contain.text', 'Nina')
        cy.get('main').should('contain.text', 'Elmar')

        cy.checkForPhpNoticesOrWarnings()
    })

    it.only('emptystate layout', function () {
        cy.visit('administrator/index.php?option=com_foos&view=foos&layout=emptystate')
        cy.get('main').should('contain.text', 'No Foo have been created yet.')
    })
})

Pendant le développement, c'est très pratique.

Attributs de test spéciaux

Maintenant, nous aimons tester l'interface de notre composant, nous le faisons dans un fichier séparé /tests/System/integration/site/components/com_foos/FooItem.cy.js.

La plupart du temps, nous utilisons une classe CSS pour obtenir un élément dans les tests Joomla. Bien que cela soit parfaitement valide et fonctionne, ce n'est pas vraiment recommandé. Pourquoi pas ? Lorsque vous utilisez des classes ou des identifiants CSS, vous liez vos tests à des éléments qui changera très probablement avec le temps. Les classes et les identifiants sont destinés à la conception, à la mise en page et parfois via JavaScript pour le contrôle, ce qui peut facilement changer. Si quelqu'un change un nom de classe ou un identifiant, vos tests ne fonctionneront plus. Pour rendre vos tests moins fragiles et plus à l'épreuve du temps, Cypress recommande de créer des attributs de données spéciaux pour vos éléments spécifiquement à des fins de test.

Je vais utiliser l' data-testattribut pour les éléments. D'abord, j'ajoute l'attribut data-test="foo-main"au code de production.

// /components/com_foos/tmpl/foo/default.php

\defined('_JEXEC') or die;
?>
<div data-test="foo-main">
Hello Foos
</div>

Ensuite, je teste le code de production en recherchant l'attribut [data-test="foo-main"].

// tests/System/integration/site/components/com_foos/FooItem.cy.js
describe('Test com_foo frontend', () => {
  it('Show frondend via query in url', function () {
    cy.visit('index.php?option=com_foos&view=foo')

    cy.get('[data-test="foo-main"]').should('contain.text', 'Hello Foos')

    cy.checkForPhpNoticesOrWarnings()
  })
})
Tester un élément de menu et quelques réflexions sur les événements, les attentes et les meilleures pratiques

Maintenant, j'aime tester la création d'un élément de menu pour notre composant. Je le fais dans un fichier séparé /tests/System/integration/administrator/components/com_foos/MenuItem.cy.js. Ce code est complexe et présente de nombreuses fonctionnalités spéciales.

Tout d'abord, j'ai défini une constante dans laquelle j'ai défini toutes les propriétés pertinentes de l'élément de menu. Cela a l'avantage qu'en cas de modification d'une propriété pertinente, je ne dois ajuster qu'à un seul endroit :

const testMenuItem = {
  'title': 'Test MenuItem',
  'menuitemtype_title': 'COM_FOOS',
  'menuitemtype_entry': 'COM_FOOS_FOO_VIEW_DEFAULT_TITLE'
}

Ensuite vous voyez tout le code du fichier MenuItem.cy.js:

// tests/System/integration/administrator/components/com_foos/MenuItem.cy.js

describe('Test menu item', () => {
  beforeEach(() => {
    cy.doAdministratorLogin(Cypress.env('username'), Cypress.env('password'))
  })

  it('creates a new menu item', function () {
    const testMenuItem = {
      'title': 'Test MenuItem',
      'menuitemtype_title': 'COM_FOOS',
      'menuitemtype_entry': 'COM_FOOS_FOO_VIEW_DEFAULT_TITLE'
    }

    cy.visit('administrator/index.php?option=com_menus&view=item&client_id=0&menutype=mainmenu&layout=edit')
    cy.checkForPhpNoticesOrWarnings()
    cy.get('h1.page-title').should('contain', 'Menus: New Item')

    cy.get('#jform_title').clear().type(testMenuItem.title)

    cy.contains('Select').click()
    cy.get('.iframe').iframe('#collapse1-heading').contains(testMenuItem.menuitemtype_title).click()
    cy.get('.iframe').iframe('#collapse1-heading').contains(testMenuItem.menuitemtype_entry).click()

    cy.intercept('index.php?option=com_menus&view=items&menutype=mainmenu').as('item_list')
    cy.clickToolbarButton('Save & Close')
    cy.wait('@item_list')
    cy.get('#system-message-container').contains('Menu item saved.').should('exist')

    // Frontend
    cy.visit('index.php')
    cy.get('.sidebar-right').contains(testMenuItem.title).click()
    cy.get('[data-test="foo-main"]').should('contain.text', 'Hello Foos')
    cy.checkForPhpNoticesOrWarnings()

    // Trash
    cy.visit('administrator/index.php?option=com_menus&view=items&menutype=mainmenu')
    cy.searchForItem(testMenuItem.title)
    cy.checkAllResults()
    cy.clickToolbarButton('Action')
    cy.intercept('index.php?option=com_menus&view=items&menutype=mainmenu').as('item_trash')
    cy.clickToolbarButton('trash')
    cy.wait('@item_trash')
    cy.get('#system-message-container').contains('Menu item trashed.').should('exist')

    // Delete
    cy.visit('administrator/index.php?option=com_menus&view=items&menutype=mainmenu')
    cy.setFilter('published', 'Trashed')
    cy.searchForItem(testMenuItem.title)
    cy.checkAllResults()
    cy.on("window:confirm", (s) => {
      return true;
    });
    cy.intercept('index.php?option=com_menus&view=items&menutype=mainmenu').as('item_delete')
    cy.clickToolbarButton('empty trash');
    cy.wait('@item_delete')
    cy.get('#system-message-container').contains('Menu item deleted.').should('exist')
  })
})
  • Dans ce code, vous pouvez voir un exemple de test de quelque chose, puis de tout supprimer - restaurant ainsi l'état initial. De cette façon, vous pouvez répéter les tests autant de fois que vous le souhaitez. Sans restaurer l'état initial, le deuxième test échouera car Joomla ne peut pas stocker deux éléments similaires.

Remarque : Un test doit être :

  • répétable.
  • Concrètement, cela signifie qu'il doit tester un problème limité et que le code pour cela ne doit pas être trop étendu.
  • indépendant des autres tests.
  • Et vous pouvez voir comment utiliser une route interceptée définie avec cy.intercept()[^docs.cypress.io/api/commands/intercept] comme alias, puis attendre la route définie comme alias avec cy.wait().

Lors de l'écriture de tests pour de telles applications, on est tenté d'utiliser des valeurs aléatoires comme cy.wait(2000);dans la cy.waitcommande. Le problème avec cette approche est que même si cela peut bien fonctionner en développement. Cependant, il n'est pas garanti de toujours fonctionner. Pourquoi ? Parce que le système sous-jacent dépend de choses difficiles à prévoir, il est donc toujours préférable de définir exactement ce que vous attendez.

  • Le code montre également comment attendre une alerte et la confirmer .
cy.on("window:confirm", (s) => {
  return true;
});
  • Enfin, le code de test contient des fonctions intégrées à Cypress et typiques de Joomla qui peuvent être réutilisées par les développeurs d'extensions. Par exemple, cy.setFilter('published', 'Trashed')ou cy.clickToolbarButton('Save & Close')sont des fonctions dans lesquelles des solutions pour des tests individuels peuvent être trouvées en général et dont les développeurs Joomla en particulier ont souvent besoin. .
Mélanger le code asynchrone et le code de synchronisation

Les commandes Cypress sont asynchrones, c'est-à-dire qu'elles ne renvoient pas de valeur, mais generatecelle-ci. Lorsque nous démarrons Cypress, il n'exécute pas les commandes immédiatement, mais les lit en série et les met en file d'attente. Si vous mélangez du code asynchrone et synchrone dans les tests, vous peut obtenir des résultats inattendus. Si vous exécutez le code suivant, vous obtiendrez une erreur par rapport aux attentes. Vous vous attendriez sûrement aussi à ce que mainText = $main.text()la valeur change mainText. Mais mainText === 'Initial'est toujours valide à la fin. Pourquoi ? Cypress exécute d'abord le code synchrone à au début et à la fin. Ce n'est qu'alors qu'il appelle la partie asynchrone à l'intérieur de then(). Cela signifie que la variable mainTextest initialisée et immédiatement après, elle est vérifiée, si elle a changé - ce qui bien sûr n'est pas le cas.

let mainText = 'Initial';
cy.visit('administrator/index.php?option=com_foos&view=foos&layout=emptystate')
cy.get("main").then(
  ($main) => (mainText = $main.text())
);

if (mainText === 'Initial') {
  throw new Error(`Der Text hat sich nicht geändert. Er lautet: ${mainText}`);
}

Le traitement de la file d'attente devient assez clair et visuel si l'on observe l'exécution du code suivant dans la console du navigateur. Le texte "Cypress Test." apparaît bien avant que le contenu de l'élément ne soit affiché, bien que les lignes de code mainsoient dans un ordre différent.

cy.get('main').then(function(e){
  console.log(e.text())
})
console.log('Cypress Test.')
Talons et espions

A stubest un moyen de simuler le comportement de la fonction dont dépendent les tests. Au lieu d'appeler la fonction réelle, le stub remplace cette fonction et renvoie un objet prédéfini. Il est généralement utilisé dans les tests unitaires, mais peut également être utilisé pour la fin -tests de bout en bout.

A spyest similaire à stub, mais pas exactement identique. Il ne modifie pas le comportement d'une fonction, mais le laisse tel quel. Il capture des informations sur la façon dont la fonction est appelée. Par exemple, pour vérifier si la fonction est appelée avec les paramètres corrects, ou pour compter la fréquence d'appel de la fonction.

L'exemple suivant montre a spyet a stuben action. Via const stub = cy.stub(), nous créons l' stubélément et déterminons à l'étape suivante qui falseest renvoyé pour le premier appel et truepour le second. À l'aide cy.on('window:confirm', stub)de nous faisons en sorte que le stubsoit utilisé pour window:confirm'. À l'étape suivante, nous créons avec cy.spy(win, 'confirm').as('winConfirmSpy')l' Spyélément , qui observe l'appel de 'window:confirm'. Maintenant, nous testons que lors du premier appel, la suppression de la catégorie est rejetée et lors du second appel, elle est confirmée. Ce faisant, le garantit stubque nous pouvons nous attendre avec certitude aux valeurs de retour livré. 'window:confirm'est encapsulé. @winConfirmSpypermet de s'assurer que la fonction a bien été appelée - et à quelle fréquence elle a été appelée.

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js
...
const stub = cy.stub()

stub.onFirstCall().returns(false)
stub.onSecondCall().returns(true)

cy.on('window:confirm', stub)

cy.window().then(win => {
  cy.spy(win, 'confirm').as('winConfirmSpy')
})

cy.intercept('index.php?option=com_categories&view=categories&extension=com_foos').as('cat_delete')
cy.clickToolbarButton('empty trash');

cy.get('@winConfirmSpy').should('be.calledOnce')
cy.get('main').should('contain.text', testFoo.category)


cy.clickToolbarButton('empty trash');
cy.wait('@cat_delete')

cy.get('@winConfirmSpy').should('be.calledTwice')

cy.get('#system-message-container').contains('Category deleted.').should('exist')
...

S'il s'agit simplement de définir une valeur fixe pour l' 'window:confirm'appel, le code suivant fera l'affaire.

cy.on("window:confirm", (s) => {
  return true;
});

conclusion

Dans cet article, vous avez vu la théorie de base et les fonctionnalités pratiques des tests E2E avec Cypress. J'ai utilisé l'installation Joomla pour montrer comment écrire différents tests pour s'assurer qu'un composant Joomla sur un site Web fonctionne comme prévu. J'ai également montré comment personnaliser Cypress. Test Runner dans le fichier cypress.json et comment utiliser les commandes Cypress personnalisées à l'aide d'exemples faciles à suivre.

J'espère que vous avez apprécié la visite de Cypress en utilisant Joomla comme exemple et que vous avez pu emporter beaucoup de connaissances et d'inspiration pour vous-même.

An online collaborative community manual for Joomla! users, developers or anyone interested in learning more about Joomla! Currently, we have more than 9,000 articles written, maintained, and translated by our Joomla! community members. 

Veuillez noter que ce site Web utilise un système de traduction automatique pour aider à la traduction dans les différentes langues. Nous nous excusons pour toute erreur ou faute de frappe pouvant apparaître dans les différents textes.