


Test d'intégration réagi: une plus grande couverture, moins de tests
Pour les sites Web interactifs comme ceux construits avec React, les tests d'intégration sont un choix naturel. Ils valident comment les utilisateurs interagissent avec les applications sans les frais généraux supplémentaires de tests de bout en bout.
Cet article l'illustre avec un exercice qui commence par un site Web simple, utilise des tests unitaires et des tests d'intégration pour vérifier les comportements et montre comment les tests d'intégration peuvent atteindre une plus grande valeur avec moins de lignes de code. Cet article suppose que vous connaissez les tests dans React et JavaScript. La familiarité avec la bibliothèque des tests de plaisanterie et de réaction peut être utile, mais pas requise.
Il existe trois types de tests:
- Les tests unitaires vérifient indépendamment un morceau de code. Ils sont faciles à écrire, mais peuvent ignorer la vue d'ensemble.
- Les tests de bout en bout (E2E) utilisent un cadre automatisé tel que Cypress ou Selenium pour interagir avec votre site Web comme les utilisateurs: charger des pages, remplir des formulaires, cliquer sur les boutons, etc. Ils sont généralement écrits et s'exécutent plus lentement, mais sont très proches de l'expérience utilisateur réelle.
- Les tests d'intégration se trouvent quelque part entre les deux. Ils vérifient comment plusieurs unités d'une application fonctionnent ensemble, mais sont plus légères que les tests E2E. Par exemple, la plaisanterie est livrée avec certains services publics intégrés pour faciliter les tests d'intégration; Jest utilise JSDom en arrière-plan pour simuler les API du navigateur commun, avec moins de frais généraux que l'automatisation, et ses puissants outils de moquerie peuvent simuler des appels API externes.
Une autre chose à noter: dans les applications React, les tests unitaires et les tests d'intégration sont écrits de la même manière et les outils sont utilisés .
Test de début de réaction
J'ai créé une application React simple (disponible sur github) avec un formulaire de connexion. Je l'ai connecté à reqres.in, qui est une API pratique que j'ai trouvé à utiliser pour tester les projets frontaux.
Vous pouvez vous connecter avec succès:
... ou rencontrez un message d'erreur de l'API:
La structure du code est la suivante:
<code>LoginModule/ ├── components/ │ ├── Login.js // 渲染LoginForm、错误消息和登录确认│ └── LoginForm.js // 渲染登录表单字段和按钮├── hooks/ │ └── useLogin.js // 连接到API 并管理状态└── index.js // 将所有内容整合在一起</code>
Option 1: tests unitaires
Si vous aimez écrire des tests comme moi - peut-être porter des écouteurs et jouer de la belle musique sur Spotify - alors vous pourriez être incapable de résister aux tests d'unité d'écriture pour chaque fichier.
Même si vous n'êtes pas un passionné de test, vous pouvez travailler sur un projet qui "essaie de faire un bon travail de test" sans stratégie claire, et la méthode de test est "Je pense que chaque fichier devrait avoir son propre test?"
Cela ressemble à ceci (j'ai ajouté l'unité dans le nom du fichier de test pour plus de clarté):
<code>LoginModule/ ├── components/ │ ├── Login.js │ ├── Login.unit.test.js │ ├── LoginForm.js │ └── LoginForm.unit.test.js ├── hooks/ │ ├── useLogin.js │ └── useLogin.unit.test.js ├── index.js └── index.unit.test.js</code>
J'ai terminé l'exercice sur GitHub pour ajouter tous ces tests unitaires et créé un test: couverture: script unitaire pour générer des rapports de couverture (une fonction intégrée de la plaisanterie). Nous pouvons obtenir une couverture à 100% via quatre fichiers de test unitaires:
Une couverture à 100% est généralement écrasante, mais il est possible pour une base de code aussi simple.
Croflons-nous dans l'un des tests unitaires créés pour le crochet React Onlogin. Si vous n'êtes pas familier avec React Hooks ou comment les tester, ne vous inquiétez pas.
TEST («Flow de connexion réussie», async () => { // Simuler la plaisanterie de réponse à l'API réussie .spyon (fenêtre, 'fetch') .mockResolvedValue ({json: () => ({token: '123'})}); const {result, waitForNextupDate} = renderHook (() => usElogin ()); acte (() => { result.current.onsubmit ({ e-mail: '[e-mail protégé]', Mot de passe: «mot de passe», }); }); // définir le statut en attente attendre (result.current.state) .toequal ({ Statut: «en attente», Utilisateur: NULL, Erreur: null, }); attendre waitforNextupDate (); // Définissez le statut sur résolu et stockez l'adresse e-mail attendre (result.current.state) .toequal ({ Statut: «résolu», utilisateur: { e-mail: '[e-mail protégé]', }, Erreur: null, }); });
Ce test est intéressant à écrire (car la bibliothèque de tests React Hooks fait des crochets de test), mais il a quelques problèmes.
Premièrement, l'état interne de validation de test passe de «en attente» à «résolu»; Ces détails d'implémentation ne sont pas exposés à l'utilisateur, donc ce n'est peut-être pas une bonne chose à tester. Si nous refacteurs l'application, nous devrons mettre à jour ce test, même si rien ne change du point de vue de l'utilisateur.
De plus, en tant que test unitaire, ce n'est qu'une partie de celui-ci. Si nous voulons vérifier les autres fonctionnalités du processus de connexion, tels que la modification du texte du bouton de soumission en "chargement", nous devrons le faire dans un fichier de test différent.
Option 2: tests d'intégration
Examinons l'ajout d'une alternative au test d'intégration pour valider ce processus:
<code>LoginModule/ ├── components/ │ ├── Login.js │ └── LoginForm.js ├── hooks/ │ └── useLogin.js ├── index.js └── index.integration.test.js</code>
J'ai implémenté ce test et un test: Couverture: script d'intégration pour générer des rapports de couverture. Tout comme les tests unitaires, nous pouvons atteindre une couverture à 100%, mais cette fois, tout est dans un seul fichier et nécessite moins de lignes de code.
Voici les tests d'intégration couvrant le processus de connexion réussi:
TEST ('Connexion réussie', async () => { plaisanter .spyon (fenêtre, 'fetch') .mockResolvedValue ({json: () => ({token: '123'})}); rendre(<loginmodule></loginmodule> )); const e-mail e-mail = screen.getByRole ('textbox', {name: 'email'}); const MotwordField = screen.getByLabelText («mot de passe»); const Button = Screen.getByRole ('Button'); // Rempliez et soumettez le formulaire fireevent.change (champ de messagerie, {cible: {valeur: '[e-mail protégé]'}}); FireEvent.Change (Passwordfield, {Target: {Value: 'Password'}}); FireEvent.Click (bouton); // il définit le statut de charge attendre (bouton) .tobedisabled (); attendre (bouton) .toHaveTextContent («Chargement ...»); attendre waitfor (() => { // il cache l'élément de forme attendre (bouton) .not.tobeIntheDocument (); Attendez-vous (champ de messagerie) .not.tobeIntheDocument (); Attendez-vous (Passwordfield) .not.tobeIntheDocument (); // Il affiche le texte de réussite et l'adresse e-mail const loggEdIntext = screen.getByText ('connecté en As'); attendre (loggedIntext) .TobeIntheDocument (); const emailAddressText = screen.getByText ('[e-mail protégé]'); attendre (emailAddressText) .TobeIntheDocument (); }); });
J'aime vraiment ce test car il vérifie l'intégralité du processus de connexion du point de vue d'un utilisateur: formulaires, statut de chargement et messages de confirmation réussis. Les tests d'intégration sont excellents pour les applications React, précisément en raison de ce cas d'utilisation; L'expérience utilisateur est ce que nous voulons tester, ce qui implique presque toujours plusieurs extraits de code différents fonctionnant ensemble .
Ce test ne comprend pas les composants ou les crochets qui font fonctionner le comportement attendu, ce qui est bien. Tant que l'expérience utilisateur reste la même, nous pouvons réécrire et refacter ces détails d'implémentation sans casser le test.
Je ne creuserai pas dans l'état initial du processus de connexion et d'autres tests d'intégration pour la gestion des erreurs, mais je vous encourage à les voir sur GitHub.
Alors, qu'est-ce qui est requis pour les tests unitaires?
Plutôt que de considérer les tests unitaires par rapport aux tests d'intégration, prenons du recul et réfléchissons à la façon dont nous décidons de ce que nous devons tester en premier lieu. LoginModule doit être testé car c'est une entité que nous voulons que les utilisateurs (autres fichiers de l'application) puissent utiliser en toute confiance.
D'un autre côté, il n'est pas nécessaire de tester le crochet Onlogin, car ce ne sont que les détails d'implémentation de LoginModule. Cependant, si nos exigences changent et que Onlogin a des cas d'utilisation ailleurs, nous devrons ajouter nos propres tests (unit) pour vérifier sa fonctionnalité comme un utilitaire réutilisable. (Nous devons également déplacer le fichier car il n'est plus spécifique à LoginModule.)
Les tests unitaires ont encore de nombreux cas d'utilisation, tels que la nécessité de vérifier les sélecteurs, les crochets et les fonctions normaux réutilisables. Lorsque vous développez votre code, vous pouvez également trouver utile d'utiliser un développement axé sur les tests unitaires, même si vous déplacez cette logique jusqu'aux tests d'intégration plus tard.
De plus, les tests unitaires font un excellent travail de tests approfondis pour plusieurs entrées et cas d'utilisation. Par exemple, si mon formulaire doit afficher la validation en ligne pour divers scénarios (par exemple, un e-mail non valide, un mot de passe manquant, un mot de passe trop court), je couvrirai un cas représentatif dans le test d'intégration, puis creuserai dans le cas spécifique dans le test unitaire.
Autres avantages
Maintenant que nous sommes ici, je veux parler de certains conseils de syntaxe qui aident à garder mes tests d'intégration clairs et ordonnés.
Bloc d'attente effacer
Notre test doit tenir compte de la latence entre l'état de chargement et l'état réussi du loginmodule:
const Button = Screen.getByRole ('Button'); FireEvent.Click (bouton); attendre (bouton) .not.tobeIntheDocument (); // trop vite, le bouton est toujours là!
Nous pouvons le faire en utilisant la fonction d'assistance Waitfor de la bibliothèque de tests DOM:
const Button = Screen.getByRole ('Button'); FireEvent.Click (bouton); attendre waitfor (() => { attendre (bouton) .not.tobeIntheDocument (); // ah, c'est beaucoup mieux});
Mais que se passe-t-il si nous voulons tester d'autres projets? Il n'y a pas beaucoup de bons exemples sur Internet sur la façon de gérer cela, et dans les projets antérieurs, j'ai mis d'autres projets en dehors de WaitFor:
// bouton d'attente attend Waitfor (() => { attendre (bouton) .not.tobeIntheDocument (); }); // Testez ensuite le message de confirmation constanceText CONSTRIXText = GetByText ('connecté en tant que [e-mail protégé]'); attendre (ConfirmationText) .TobeIntheDocument ();
Cela fonctionne, mais je n'aime pas ça car cela rend la condition du bouton spéciale, même si nous pouvons facilement changer l'ordre de ces déclarations:
// attendre le message de confirmation Await WaitFor () => { const ConfirmationText = GetByText ('connecté en tant que [e-mail protégé]'); attendre (ConfirmationText) .TobeIntheDocument (); }); // Testez ensuite le bouton attendre (bouton) .not.tobeIntheDocument ();
Il me semble qu'il vaut mieux regrouper tout ce qui concerne la même mise à jour dans le rappel WaitFor:
attendre waitfor (() => { attendre (bouton) .not.tobeIntheDocument (); const ConfirmationText = Screen.getByText ('connecté en tant que [e-mail protégé]'); attendre (ConfirmationText) .TobeIntheDocument (); });
J'aime vraiment cette technique pour des affirmations simples comme celle-ci, mais dans certains cas, cela peut ralentir les tests, en attendant un échec qui se produit immédiatement à l'extérieur de l'attente. Pour cet exemple, voir "plusieurs assertions dans un seul rappel d'attente" dans l'erreur commune de la bibliothèque de tests React.
Pour les tests contenant plusieurs étapes, nous pouvons utiliser plusieurs blocs d'attente successivement:
const Button = Screen.getByRole ('Button'); const e-mail e-mail = screen.getByRole ('textbox', {name: 'email'}); // Remplissez le formulaire fireevent.change (champ de messagerie, {cible: {valeur: '[e-mail protégé]'}}); attendre waitfor (() => { // Vérifiez si le bouton est activé attendre (bouton) .not.tobedisabled (); attendre (bouton) .toHaveTextContent («soumettre»); }); // Soumettez le formulaire fireevent.click (bouton); attendre waitfor (() => { // Vérifiez si le bouton n'existe plus attendre (bouton) .not.tobeIntheDocument (); });
Si vous n'attendez qu'un seul élément apparaît, vous pouvez utiliser une requête findby à la place. Il utilise Waitfor en arrière-plan.
Les commentaires en ligne
Une autre meilleure pratique des tests consiste à écrire moins de tests plus longs; Cela vous permet de corréler les cas de test avec des processus utilisateur importants tout en gardant les tests isolés pour éviter un comportement inattendu. Je suis d'accord avec cette approche, mais elle peut poser un défi pour garder le code organisé et documenter le comportement requis. Nous avons besoin de futurs développeurs pour pouvoir revenir au test et comprendre ce qu'il fait, pourquoi il échoue, etc.
Par exemple, supposons que l'une de ces attentes commence à échouer:
it ('gère un flux de connexion réussi', async () => { // masque le début du test pour la clarté attendre (bouton) .Tobedisabled (); attendre (bouton) .toHaveTextContent («Chargement ...»); attendre waitfor (() => { attendre (bouton) .not.tobeIntheDocument (); Attendez-vous (champ de messagerie) .not.tobeIntheDocument (); Attendez-vous (Passwordfield) .not.tobeIntheDocument (); const ConfirmationText = Screen.getByText ('connecté en tant que [e-mail protégé]'); attendre (ConfirmationText) .TobeIntheDocument (); }); });
Les développeurs qui examinent ce contenu ne peuvent pas facilement déterminer ce qui est testé, et il peut être difficile de déterminer si l'échec est un bogue (ce qui signifie que nous devons corriger le code) ou un changement de comportement (ce qui signifie que nous devons corriger le test).
Ma solution préférée est d'utiliser la syntaxe de test peu connue pour chaque test et d'ajouter un commentaire de style en ligne décrivant chaque comportement clé testé:
TEST ('Connexion réussie', async () => { // masque le début du test pour la clarté // il définit le statut de chargement attendre (bouton) .tobedisabled (); attendre (bouton) .toHaveTextContent («Chargement ...»); attendre waitfor (() => { // il cache l'élément de forme attendre (bouton) .not.tobeIntheDocument (); Attendez-vous (champ de messagerie) .not.tobeIntheDocument (); Attendez-vous (Passwordfield) .not.tobeIntheDocument (); // Il affiche le texte de réussite et l'adresse e-mail constationxt xtext = screen.getByText ('connecté en tant que [e-mail protégé]'); attendre (ConfirmationText) .TobeIntheDocument (); }); });
Ces commentaires ne s'intègrent pas comme par magie à la plaisanterie, donc si vous rencontrez un échec, le nom de test défaillant correspondra aux paramètres que vous avez transmis à la balise de test, dans ce cas "Connexion réussie". Cependant, les messages d'erreur de Jest contiennent le code environnant, de sorte que ces commentaires informatiques aident toujours à identifier le comportement raté. Lorsque je ne supprime pas d'une attente, j'obtiens le message d'erreur suivant:
Pour obtenir des erreurs plus explicites, il existe un package appelé Jest-Expect-Message qui vous permet de définir des messages d'erreur pour chaque attente:
attendez-vous (bouton, 'bouton est toujours dans le document'). not.tobeIntheDocument ();
Certains développeurs préfèrent cette approche, mais je trouve cela un peu trop granulaire dans la plupart des cas, car il implique généralement plusieurs attentes.
Étapes suivantes pour l'équipe
Parfois, je souhaite que nous puissions faire des règles de linter pour les humains. Si c'est le cas, nous pouvons définir une règle de tests de préférence pour notre équipe et cela se termine.
Mais, hélas, nous devons trouver une solution plus similaire pour encourager les développeurs à choisir des tests d'intégration dans certains cas, tels que l'exemple de loginmodule que nous avons introduit plus tôt. Comme la plupart des choses, cela se résume à l'équipe discutant de votre stratégie de test, en accordant à ce qui a du sens pour le projet et - en mesure de le documenter dans ADR.
Lors de l'élaboration d'un plan de test, nous devons éviter une culture qui oblige les développeurs à rédiger des tests pour chaque fichier. Les développeurs doivent être en mesure de prendre des décisions de test éclairées en toute confiance sans se soucier de leur «entreprise». Le rapport de couverture de Jest peut aider à résoudre ce problème en fournissant une vérification de la santé mentale, même si les tests sont fusionnés au niveau de l'intégration.
Je ne me considère toujours pas comme un expert des tests d'intégration, mais faire cet exercice m'a aidé à décomposer un cas d'utilisation où les tests d'intégration offrent plus de valeur que les tests unitaires. J'espère que partager cela avec votre équipe ou faire des exercices similaires sur votre base de code vous guidera à intégrer des tests d'intégration dans votre flux de travail.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

Video Face Swap
Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

Il est sorti! Félicitations à l'équipe Vue pour l'avoir fait, je sais que ce fut un effort massif et une longue période à venir. Tous les nouveaux documents aussi.

J'ai eu quelqu'un qui écrivait avec cette question très légitime. Lea vient de bloguer sur la façon dont vous pouvez obtenir les propriétés CSS valides elles-mêmes du navigateur. C'est comme ça.

Je dirais que "Site Web" correspond mieux que "Application mobile" mais j'aime ce cadrage de Max Lynch:

Si nous devons afficher la documentation à l'utilisateur directement dans l'éditeur WordPress, quelle est la meilleure façon de le faire?

L'autre jour, j'ai repéré ce morceau particulièrement charmant sur le site Web de Corey Ginnivan où une collection de cartes se cassent les uns sur les autres pendant que vous faites défiler.

Il existe un certain nombre de ces applications de bureau où l'objectif montre votre site à différentes dimensions en même temps. Vous pouvez donc, par exemple, écrire

CSS Grid est une collection de propriétés conçues pour faciliter la mise en page qu'elle ne l'a jamais été. Comme tout, il y a un peu une courbe d'apprentissage, mais Grid est

Je vois que Google Fonts a déployé un nouveau design (tweet). Comparé à la dernière grande refonte, cela semble beaucoup plus itératif. Je peux à peine faire la différence
