diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index d47f21ef..00000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.all-contributorsrc b/.all-contributorsrc index 8ee1279f..db0972e7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -322,10 +322,166 @@ "contributions": [ "content" ] + }, + { + "login": "otavionetoca", + "name": "Otavio Araujo", + "avatar_url": "https://avatars.githubusercontent.com/u/11263232?v=4", + "profile": "https://github.com/otavionetoca", + "contributions": [ + "test", + "content" + ] + }, + { + "login": "contributorpw", + "name": "Alex Ivanov", + "avatar_url": "https://avatars.githubusercontent.com/u/5027939?v=4", + "profile": "https://contributor.pw", + "contributions": [ + "content" + ] + }, + { + "login": "YeeJone", + "name": "Yiqiao Xu", + "avatar_url": "https://avatars.githubusercontent.com/u/20400822?v=4", + "profile": "https://github.com/YeeJone", + "contributions": [ + "content" + ] + }, + { + "login": "yubinTW", + "name": "YuBin, Hsu", + "avatar_url": "https://avatars.githubusercontent.com/u/31545456?v=4", + "profile": "https://github.com/yubinTW", + "contributions": [ + "translation", + "code" + ] + }, + { + "login": "TREER00T", + "name": "Ali Azmoodeh", + "avatar_url": "https://avatars.githubusercontent.com/u/76606342?v=4", + "profile": "https://github.com/TREER00T", + "contributions": [ + "content" + ] + }, + { + "login": "Saimon398", + "name": "Alex Popov", + "avatar_url": "https://avatars.githubusercontent.com/u/71539667?v=4", + "profile": "https://github.com/Saimon398", + "contributions": [ + "content" + ] + }, + { + "login": "Shramkoweb", + "name": "Serhii Shramko", + "avatar_url": "https://avatars.githubusercontent.com/u/42001531?v=4", + "profile": "http://shramko.dev", + "contributions": [ + "content" + ] + }, + { + "login": "yugoccp", + "name": "Yugo Sakamoto", + "avatar_url": "https://avatars.githubusercontent.com/u/1724114?v=4", + "profile": "https://github.com/yugoccp", + "contributions": [ + "content" + ] + }, + { + "login": "Fdawgs", + "name": "Frazer Smith", + "avatar_url": "https://avatars.githubusercontent.com/u/43814140?v=4", + "profile": "https://yeovilhospital.co.uk/", + "contributions": [ + "content" + ] + }, + { + "login": "wralith", + "name": "Wralith", + "avatar_url": "https://avatars.githubusercontent.com/u/75392169?v=4", + "profile": "https://github.com/wralith", + "contributions": [ + "content" + ] + }, + { + "login": "saseungmin", + "name": "Harang", + "avatar_url": "https://avatars.githubusercontent.com/u/60910665?v=4", + "profile": "https://haranglog.tistory.com", + "contributions": [ + "content" + ] + }, + { + "login": "rcanelav", + "name": "rcanelav", + "avatar_url": "https://avatars.githubusercontent.com/u/64812826?v=4", + "profile": "https://github.com/rcanelav", + "contributions": [ + "content" + ] + }, + { + "login": "drewrwilson", + "name": "Drew Wilson", + "avatar_url": "https://avatars.githubusercontent.com/u/4324656?v=4", + "profile": "https://github.com/drewrwilson", + "contributions": [ + "content" + ] + }, + { + "login": "XtLee", + "name": "XtLee", + "avatar_url": "https://avatars.githubusercontent.com/u/30145777?v=4", + "profile": "https://github.com/XtLee", + "contributions": [ + "content" + ] + }, + { + "login": "smonn", + "name": "Simon Ingeson", + "avatar_url": "https://avatars.githubusercontent.com/u/44818?v=4", + "profile": "https://www.smonn.se", + "contributions": [ + "content" + ] + }, + { + "login": "elfacu0", + "name": "elfacu0", + "avatar_url": "https://avatars.githubusercontent.com/u/30785449?v=4", + "profile": "https://github.com/elfacu0", + "contributions": [ + "content" + ] + }, + { + "login": "jorbelca", + "name": "jorbelca", + "avatar_url": "https://avatars.githubusercontent.com/u/76847923?v=4", + "profile": "https://github.com/jorbelca", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", "projectOwner": "goldbergyoni", "repoType": "github", - "repoHost": "https://github.com" + "repoHost": "https://github.com", + "commitConvention": "angular" } diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e43b0f98 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/.operations/questions-answers.md b/.operations/questions-answers.md index 67b42d7e..2f71c638 100644 --- a/.operations/questions-answers.md +++ b/.operations/questions-answers.md @@ -2,9 +2,9 @@ ## Q: How do I start a new translation? -**Answer:** +**Answer:** -Welcome aboard! Having a Brazilian Portuguese translation would be awesome 🔥🌈👌 . I'll be glad to collaborate with you on this and help wherever I can +Welcome aboard! Having a Brazilian Portuguese translation would be awesome 🔥🌈👌💚 . I'll be glad to collaborate with you on this and help wherever I can Before you start with this, I've prepared some basic workflow guidelines: diff --git a/assets/header.pptx b/assets/header.pptx index 925889c9..57b1eb07 100644 Binary files a/assets/header.pptx and b/assets/header.pptx differ diff --git a/assets/uml.png b/assets/uml.png new file mode 100644 index 00000000..088a9bd4 Binary files /dev/null and b/assets/uml.png differ diff --git a/assets/zh-CN/bp-1-3-parts.jpg b/assets/zh-CN/bp-1-3-parts.jpg new file mode 100644 index 00000000..7a907611 Binary files /dev/null and b/assets/zh-CN/bp-1-3-parts.jpg differ diff --git a/assets/zh-CN/bp-12-rich-testing.jpg b/assets/zh-CN/bp-12-rich-testing.jpg new file mode 100644 index 00000000..d7eb34fe Binary files /dev/null and b/assets/zh-CN/bp-12-rich-testing.jpg differ diff --git a/assets/zh-CN/bp-13-component-test-yoni-goldberg.png b/assets/zh-CN/bp-13-component-test-yoni-goldberg.png new file mode 100644 index 00000000..e943ddc2 Binary files /dev/null and b/assets/zh-CN/bp-13-component-test-yoni-goldberg.png differ diff --git a/assets/zh-CN/bp-14-testing-best-practices-contract-flow.png b/assets/zh-CN/bp-14-testing-best-practices-contract-flow.png new file mode 100644 index 00000000..74a4ebaa Binary files /dev/null and b/assets/zh-CN/bp-14-testing-best-practices-contract-flow.png differ diff --git a/assets/zh-CN/bp-20-yoni-goldberg-mutation-testing.jpg b/assets/zh-CN/bp-20-yoni-goldberg-mutation-testing.jpg new file mode 100644 index 00000000..bd2c6589 Binary files /dev/null and b/assets/zh-CN/bp-20-yoni-goldberg-mutation-testing.jpg differ diff --git a/assets/zh-CN/headspace.png b/assets/zh-CN/headspace.png new file mode 100644 index 00000000..25225b18 Binary files /dev/null and b/assets/zh-CN/headspace.png differ diff --git a/assets/~$header.pptx b/assets/~$header.pptx deleted file mode 100644 index 1a939757..00000000 Binary files a/assets/~$header.pptx and /dev/null differ diff --git a/index.js b/index.js index e6be5312..e3c1c215 100644 --- a/index.js +++ b/index.js @@ -1 +1,2 @@ -// This is a book for now, but code examples are coming soon \ No newline at end of file +// This is a book for now, but code examples are coming soon +// See https://github.com/testjavascript/nodejs-integration-tests-best-practices to see an example app diff --git a/package.json b/package.json index dde3fe4b..daf57f48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "javascript-testing-best-practices", - "version": "1.0.0", + "version": "2.0.0", "description": "📗🌐 🚢 Comprehensive and exhaustive JavaScript & Node.js testing best practices (April 2020) https://testjavascript.com/", "main": "index.js", "scripts": { diff --git a/readme-es.md b/readme-es.md index 2ec1dcfc..a2b4ebda 100644 --- a/readme-es.md +++ b/readme-es.md @@ -6,7 +6,7 @@
-## 📗 45+ buenas practicas: Súper comprensiva y exhaustiva +## 📗 46+ buenas practicas: Súper comprensiva y exhaustiva Esta es una guía completa para JavaScript y Node.js de la A a la Z. Resume y selecciona docenas de los mejores post de blogs, libros, y herramientas ofrecidas en el mercado @@ -34,6 +34,8 @@ Empieza por comprender las técnicas de testing ubicuas que son la base de cualq - 🇰🇷[Coreano](readme.kr.md) - cortesía de [Rain Byun](https://github.com/ragubyun) - 🇵🇱[Polaco](readme-pl.md) - cortesía de [Michal Biesiada](https://github.com/mbiesiad) - 🇪🇸[Español](readme-es.md) - cortesía de [Miguel G. Sanguino](https://github.com/sanguino) +- 🇧🇷[Portugués-BR](readme-pt-br.md) - cortesía de [Iago Angelim Costa Cavalcante](https://github.com/iagocavalcante), [Douglas Mariano Valero](https://github.com/DouglasMV) y [koooge](https://github.com/koooge) +- 🇺🇦[Ukrainian](readme-ua.md) - cortesía de [Serhii Shramko](https://github.com/Shramkoweb) - ¿Quieres traducir a tu propio lenguaje? por favor abre una issue 💜

@@ -799,7 +801,7 @@ Los test de componente se centran en la 'unidad' de microservicios, funcionan co

-## ⚪ ️2.3 Asegúrate de que las nuevas versiones no rompan el API en uso +## ⚪ ️2.3 Asegúrate de que las nuevas versiones no rompan el API usando tests de contrato :white_check_mark: **Haz:** Pongamos que tu microservicio tiene múltiples consumidores, y tenemos en ejecución diferentes versiones del servicio por compatibilidad (para que todos estén contentos). Luego cambias un campo y "¡boom!", uno de los consumidores que necesita ese campo se cabrea. Este es el Catch-22 del mundo de la integración: es muy difícil para el lado del servidor considerar todas las expectativas de todos los consumidores. Por otro lado, los consumidores no pueden realizar ningún test porque el servidor controla las fechas de release. [Los contratos dirigidos por el consumidor y el framework PACT] (https://docs.pact.io/) nacieron para regularizar este proceso con un enfoque muy disruptivo: no es el servidor quien define los test de sí mismo, sino que son los consumidores quienes definen los test de ¡el servidor! PACT puede registrar las expectativas del consumidor y dejarlas en una ubicación compartida, "broker", para que el servidor pueda cogerlas y cumplir con las expectativas y ejecutar cada construcción utilizando la librería PACT para detectar contratos incumplidos — una expectativa de consumidor no cumplida. Al hacerlo, todos los desajustes de la API cliente-servidor se detectan muy pronto durante la construcción / CI y pueden ahorrarte mucha frustración @@ -892,7 +894,7 @@ Crédito: { const { getAllByTestId } = render(); // Afirmar - Mezcla de UI y datos en las aserciones - expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); + expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); }); ``` @@ -1047,8 +1049,8 @@ test("When flagging to show only VIP, should display only VIP members", () => { // the markup code (part of React component)

    - {value} - + {value} +

    ``` @@ -1283,7 +1285,7 @@ export default function ProductsList() { fetchProducts(); }, []); - return products ?
    {products}
    :
    No products
    ; + return products ?
    {products}
    :
    No products
    ; } // test @@ -1975,7 +1977,12 @@ Se encargó de revisar, mejorar, lintear y pulir todos los textos

    Morgan

    🖋 -
    Lukas Bischof

    ⚠️ 🖋 +
    Lukas Bischof

    ⚠️ 🖋 +
    JuanMa Ruiz

    🖋 +
    Luís Ângelo Rodrigues Jr.

    🖋 +
    José Fernández

    🖋 +
    Alejandro Gutierrez Barcenilla

    🖋 +
    Jason

    🖋 diff --git a/readme-fr.md b/readme-fr.md new file mode 100644 index 00000000..e03b122f --- /dev/null +++ b/readme-fr.md @@ -0,0 +1,1967 @@ + + +
    + +# 👇 Comment ce guide peut faire passer vos compétences de test au niveau supérieur + +
    + +## 📗 46+ bonnes pratiques : complet et exhaustif + +Ceci est un guide complet pour Javascript & Node.js de A à Z. Il résume et organise pour vous les meilleurs articles de blogs, livres et outils du marché + +## 🚢 Avancé : va bien au-delà des bases + +Embarque pour un voyage qui va bien au-delà des bases et aborde des sujets avancés tels que les tests en production, les tests de mutations, les tests basés sur les propriétés et de nombreux autres outils stratégiques et professionnels. Si vous lisez chaque mot de ce guide, vos compétences de tests seront probablement bien au-dessus la moyenne. + +## 🌐 Full-stack: front, backend, CI ... + +Commence par comprendre les pratiques de tests omniprésentes qui sont à la base de tout niveau d'application. Ensuite, plonge dans ton domaine de prédilection : frontend/UI, backend, CI ou peut-être tous ça à la fois ? + +
    + +### Écrit par Yoni Goldberg + +- Un consultant JavaScript & Node.js +- 📗 [Les tests Node.js & JavaScript de A à Z](https://www.testjavascript.com) - Mon cours en ligne complet avec plus de [10 heures de video](https://www.testjavascript.com), 14 types de tests et plus de 40 bonnes pratiques +- [Suis-moi sur Twitter ](https://twitter.com/goldbergyoni/) + +
    + +### Traductions - Lis dans la langue de ton choix +- 🇬🇧[Anglais](readme.md) +- 🇨🇳[Chinois](readme-zh-CN.md) - Traduit par [Yves yao](https://github.com/yvesyao) +- 🇰🇷[Coréen](readme.kr.md) - Traduit par [Rain Byun](https://github.com/ragubyun) +- 🇵🇱[Polonais](readme-pl.md) - Traduit par [Michal Biesiada](https://github.com/mbiesiad) +- 🇪🇸[Espagnol](readme-es.md) - Traduit par [Miguel G. Sanguino](https://github.com/sanguino) +- 🇧🇷[Portugais brésilien](readme-pt-br.md) - Traduit par [Iago Angelim Costa Cavalcante](https://github.com/iagocavalcante) , [Douglas Mariano Valero](https://github.com/DouglasMV) et [koooge](https://github.com/koooge) +- 🇺🇦[Ukrainian](readme-ua.md) - Traduit par [Serhii Shramko](https://github.com/Shramkoweb) +- Envie de traduire dans ta propre langue ? Ouvres une issue 💜 + +

    + +## `Table des matières` + +#### [`Section 0: La règle d'or`](#section-0️⃣-the-golden-rule) + +Un seul conseil qui inspire tout les autres (1 point spécial) + +#### [`Section 1: Anatomie d'un test`](#section-1-the-test-anatomy-1) + +La base - structurer des tests propre (12 points) + +#### [`Section 2: Backend`](#section-2️⃣-backend-testing) + +Écrire efficacement des tests backend et de microservices (8 points) + +#### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) + +Écrire des tests pour l'interface utilisateur, y compris des tests de composants et des tests E2E (11 points) + +#### [`Section 4: Mesurer l'efficacité des tests`](#section-4️⃣-measuring-test-effectiveness) + +Surveiller le surveillant - mesurer la qualité des tests (4 points) + +#### [`Section 5: Intégration continue`](#section-5️⃣-ci-and-other-quality-measures) + +Lignes directrices pour l'intégration continue dans le monde du JS (9 points) + +

    + +# Section 0️⃣: La règle d'or + +
    + +## ⚪️ 0 La règle d'or: Concevoir des tests minimalistes + +:white_check_mark: **À faire:** +Le code des tests n'est pas comme le code de production - conçoit le pour être simple, court, sans abstraction, agréable à utiliser et minimaliste. En regardant le code d'un test, on doit pouvoir comprendre son but instantanément. + +Nos esprits sont déjà occupés avec le code de production, on n'a pas "d'espace" pour de la complexité additionnelle. Si on essaye d'insérer un autre code compliqué dans nos pauvres cerveaux, l'équipe va être ralentie ce qui est en contradiction avec la raison pour laquelle on fait des tests. +En pratique, c'est là que de nombreuses équipes abandonnent tout simplement les tests. + +Les tests sont une opportunité pour autre chose - un assistant amical et souriant, un avec qui il est agréable de travailler et qui nous apporte beaucoup pour peu d'investissement. La science nous dit que l'on a deux systèmes cérébraux : le premier est utilisé pour les activités qui ne demandent pas d'effort comme conduire une voiture sur une route vide ; le deuxième sert aux opérations complexes et conscientes comme résoudre une équation mathématique. Conçois tes tests pour le premier système, lire un test doit _sembler_ aussi simple que de modifier un fichier HTML, et pas comme résoudre 2X(17 x 24). + +On peut y arriver en sélectionnant des techniques, des outils et des cibles de tests qui sont rentables et offrent un bon retour sur investissement. Test seulement ce qui doit être testé, essaye de conserver de la souplesse, et parfois, il vaut même mieux supprimer quelques tests et échanger la fiabilité contre de l'agilité et de la simplicité. + +![alt text](/assets/headspace.png "On a pas de place disponible pour une complexité supplémentaire") + +La plupart des conseils ci-dessous sont des dérivés de ce principe. + +### Prêt à commencer ? + +

    + +# Section 1: Anatomie d'un test + +
    + +## ⚪ ️ 1.1 Chaque nom devrait contenir 3 parties + +:white_check_mark: **À faire:** Un rapport de test devrait indiquer si la version actuelle de l'application correspond aux attentes pour des personnes qui ne sont pas forcément familières avec la base de code : le testeur, le dev ops qui déploie et toi dans 2 ans. Dans ce but, les noms des tests doivent expliciter les attentes et inclure 3 parties : + +(1) Qu'est-ce qui est testé ? Par exemple, la méthode ProductService.addNewProduct + +(2) Dans quelle circonstance et scénario ? Par exemple, aucun prix n'est passé à la méthode + +(3) Quel est le résultat attendu ? Par exemple, le produit n'est pas approuvé + +
    + +❌ **Autrement:** Un déploiement a échoué, un test appelé "Add product" à échoué. Est-ce que cela indique exactement ce qui ne fonctionne plus correctement ? + +
    + +**👇 Note:** Chaque point contient des exemples de codes et parfois une image d'illustration. Cliques pour agrandir. +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Un nom de test constitué de 3 parties + +![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Using Mocha to illustrate the idea") + +```javascript +//1. unit under test +describe('Products Service', function() { + describe('Add new product', function() { + //2. scenario and 3. expectation + it('When no price is specified, then the product status is pending approval', ()=> { + const newProduct = new ProductService().add(...); + expect(newProduct.status).to.equal('pendingApproval'); + }); + }); +}); + +``` + +
    + +### :clap: Bien faire les choses, exemple: Un nom de test constitué de 3 parties + +![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") + +
    + +
    +
    © Credits & read-more + 1. Roy Osherove - Naming standards for unit tests +
    + +

    + +## ⚪ ️ 1.2 Structurer les tests avec le pattern AAA + +:white_check_mark: **À faire:** Structure tes tests avec 3 sections séparées: Organiser, Agir & Vérifier (Arrange, Act & Assert: AAA). Suivre cette structure garantit que le lecteur n'utilise pas de "CPU" de cerveau pour comprendre le plan du test : + +1er A - Organiser (Arrange): Tout le code permettant de configurer le système selon le scénario qui doit être simulé. Cela peut inclure d'instancier le constructeur de l'élément testé, ajouter des entrées en DB, mocking/stubbing des objets et autres codes de préparation + +2ème A - Agir (Act): Exécute l'élément testé. En général 1 seule ligne de code + +3éme A - Vérifier (Assert): Vérifier que les valeurs reçues correspondent aux attentes. En général 1 seule ligne de code + +
    + +❌ **Autrement:** Non seulement, vous avez passé des heures à comprendre le code principal, mais en plus ce qui devait être la partie la plus simple de la journée (tester) vous tord le cerveau. + + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Un test structuré avec le pattern AAA + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +describe("Customer classifier", () => { + test("When customer spent more than 500$, should be classified as premium", () => { + //Arrange + const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; + const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); + + //Act + const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); + + //Assert + expect(receivedClassification).toMatch("premium"); + }); +}); +``` + +
    + +### :thumbsdown: Exemple d'anti pattern: Pas de séparation, un bloc, plus dur à interpréter + +```javascript +test("Should be classified as premium", () => { + const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; + const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); + const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); + expect(receivedClassification).toMatch("premium"); +}); +``` + +
    + +

    + +## ⚪ ️1.3 Décrire les attentes dans un language produit: Utiliser des assertions de type BDD + +:white_check_mark: **À faire:** Coder tes tests dans un langage déclaratif permet au lecteur de comprendre immédiatement sans effectuer un seul cycle de "CPU" de cerveau. Lorsque tu écris du code impératif remplis de logique conditionnelles, le lecteur est forcé d'utiliser plus de cycles de "CPU" de cerveau. Dans ce cas, code les attentes dans un langage similaire au langage humain, dans un style déclaratif de type BDD avec `expect` ou `should` et sans utiliser de code custom. Si Chai et Jest n'incluent pas les assertions nécessaires et qu'elles reviennent régulièrement, considère [d'étendre Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) ou d'écrire un [plugin Chai custom](https://www.chaijs.com/guide/plugins/) + +
    + +❌ **Autrement:** L'équipe écrira moins de tests et décorera ceux qui sont ennuyeux avec .skip() + +
    + +
    Exemple de code
    + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +### :thumbsdown: Exemple d'anti pattern: Le lecteur doit parcourir un long code impératif juste pour comprendre l'histoire du test + +```javascript +test("When asking for an admin, ensure only ordered admins in results", () => { + //assuming we've added here two admins "admin1", "admin2" and "user1" + const allAdmins = getUsers({ adminOnly: true }); + + let admin1Found, + adming2Found = false; + + allAdmins.forEach(aSingleUser => { + if (aSingleUser === "user1") { + assert.notEqual(aSingleUser, "user1", "A user was found and not admin"); + } + if (aSingleUser === "admin1") { + admin1Found = true; + } + if (aSingleUser === "admin2") { + admin2Found = true; + } + }); + + if (!admin1Found || !admin2Found) { + throw new Error("Not all admins were returned"); + } +}); +``` + +
    + +### :clap: Bien faire les choses, exemple: Parcourir le test déclaratif suivant est un jeu d'enfant + +```javascript +it("When asking for an admin, ensure only ordered admins in results", () => { + //assuming we've added here two admins + const allAdmins = getUsers({ adminOnly: true }); + + expect(allAdmins) + .to.include.ordered.members(["admin1", "admin2"]) + .but.not.include.ordered.members(["user1"]); +}); +``` + +
    + +

    + +## ⚪ ️ 1.4 S'en tenir aux tests des boites noires : Ne tester que les méthodes publiques + +:white_check_mark: **À faire:** Tester les composants internes apporte beaucoup de complexité pour presque rien. Si ton code/API délivre les bon résultats, est-ce que tu dois vraiment passer les 3 prochaines heures à tester COMMENT il fonctionne et maintenir ces tests ? À chaque fois qu'un comportement publique est testé, l'implémentation privée est aussi testé implicitement, et test tests n'échoueront que si il y a un certain problème (par exemple: mauvais retour). Cette approche est aussi appelée `behavioral testing` (test de comportement). De l'autre côté, si tu dois tester les éléments internes (approche de la boîte blanche) - l'objectif passe de planifier le résultat du composant à des détails de bases, et votre test peut échouer à cause de refactoring mineurs alors que le résultat est toujours bon - cela augmente la charge de maintenance. +
    + +❌ **Autrement:** Tes tests se comportent comme [l'enfant qui criait au loup](https://fr.wikipedia.org/wiki/L%27Enfant_qui_criait_au_loup): crier des faux positifs (par exemple, un test échoue parce qu'un nom de variable privé a été changé). Sans surprise, les gens vont rapidement ignorer les notifications, jusqu'à ce qu'un jour, un vrai bug soit ignoré + +
    +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: Un cas qui test une méthode interne sans raison valable + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") + +```javascript +class ProductService { + //this method is only used internally + //Change this name will make the tests fail + calculateVATAdd(priceWithoutVAT) { + return { finalPrice: priceWithoutVAT * 1.2 }; + //Change the result format or key name above will make the tests fail + } + //public method + getPrice(productId) { + const desiredProduct = DB.getProduct(productId); + finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice; + return finalPrice; + } +} + +it("White-box test: When the internal methods get 0 vat, it return 0 response", async () => { + //There's no requirement to allow users to calculate the VAT, only show the final price. Nevertheless we falsely insist here to test the class internals + expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0); +}); +``` + +
    + +

    + +## ⚪ ️ ️1.5 Choisir les bons "test doubles": Éviter les mocks en faveur des stubs et spies + +:white_check_mark: **À faire:** Les "test doubles" sont un mal nécessaire parce qu'ils sont couplés aux composants internes mais apportent néanmoins beaucoup de valeur ([Retrouve ici un rappel à propos des "test doubles": mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). + +Avant d'utiliser des "test doubles", pose toi une question très simple: Est-ce que je l'utilise pour tester une fonctionnalité qui apparaît, ou peut apparaître, dans le document de spécification ? Si non, ça sent le test de boite blanche. + +Par exemple, si tu veux tester que ton application se comporte correctement quand le service de paiement est coupé, tu peux faire un stub du service de paiement et déclencher une réponse de type 'No Response' pour vérifier que l'unité testée retourne la bonne valeur. Cela vérifie le comportement/réponse de notre application suivant un certain scénario. Tu peux aussi utiliser un spy pour vérifier qu'un email a bien été envoyé quand ce service était coupé - il s'agit encore une fois d'un test de comportement qui pourrait apparaître dans les spécifications ("Envoyer un email si le paiement n'as pas pu être enregistré"). +D'un autre côté, si tu mock le service de paiement pour vérifier qu'il a bien été appelé avec le bon type Javascript, alors ton test est orienté sur des comportements internes qui n'ont rien à voir avec les fonctionnalités de l'application et changeront probablement fréquemment. +
    + +❌ **Autrement:** Chaque refactoring du code implique de chercher l'ensemble des mock dans le code afin de les mettre à jour. Les tests deviennent une corvée plutôt qu'un ami aidant. +
    + +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: Les mocks se concentrent sur des composants internes + +![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg "Examples with Sinon") + +```javascript +it("When a valid product is about to be deleted, ensure data access DAL was called once, with the right product and right config", async () => { + //Assume we already added a product + const dataAccessMock = sinon.mock(DAL); + //hmmm BAD: testing the internals is actually our main goal here, not just a side-effect + dataAccessMock + .expects("deleteProduct") + .once() + .withArgs(DBConfig, theProductWeJustAdded, true, false); + new ProductService().deletePrice(theProductWeJustAdded); + dataAccessMock.verify(); +}); +``` + +
    + +### :clap: Bien faire les choses, exemple : Les spies se concentrent sur les fonctionnalités requises mais touchent les composants internes par effet de bord + +```javascript +it("When a valid product is about to be deleted, ensure an email is sent", async () => { + //Assume we already added here a product + const spy = sinon.spy(Emailer.prototype, "sendEmail"); + new ProductService().deletePrice(theProductWeJustAdded); + //hmmm OK: we deal with internals? Yes, but as a side effect of testing the requirements (sending an email) + expect(spy.calledOnce).to.be.true; +}); +``` + +
    + +

    + +## 📗 Envie d'apprendre ces bonnes pratiques en vidéo ? + +### Va voir mon cours en ligne [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +

    + +## ⚪ ️1.6 Utiliser des données réalistes + +:white_check_mark: **À faire:** Souvent les bugs de production sont révélés par des entrées très spécifiques et surprenantes. Plus les entrées de tests seront réalistes, plus il y a de chance de détecter les bugs tôt. Utilise une librairie dédiée comme [Faker](https://www.npmjs.com/package/faker) pour générer des pseudo-vrais données qui ressemble aux données de production. Par exemple, ce type de librairie peut générer de façon réaliste des numéros de téléphone, noms d'utilisateur, cartes de crédit, nom de société et même du 'Lorem ipsum'. Tu peux aussi créer des tests (en plus des tests unitaires, par à leur place) qui utilise des fausses données randomisées pour pousser test tests, ou même importer de vraies données depuis ton environnement de production. Envie de passer au niveau supérieur ? Regarde le prochain point (property-based testing). +
    + +❌ **Autrement:** Tout vos tests de développement vont montrer du vert à tort avec des entrées tels que "Foo", mais en production ils passeront au rouge lorsqu'un hacker passera une chaine de caractère tel que “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +
    + +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: Une suite de test qui passe à cause de données non réalistes +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +const addProduct = (name, price) => { + const productNameRegexNoSpace = /^\S*$/; //no white-space allowed + + if (!productNameRegexNoSpace.test(name)) return false; //this path never reached due to dull input + + //some logic here + return true; +}; + +test("Wrong: When adding new product with valid properties, get successful confirmation", async () => { + //The string "Foo" which is used in all tests never triggers a false result + const addProductResult = addProduct("Foo", 5); + expect(addProductResult).toBe(true); + //Positive-false: the operation succeeded because we never tried with long + //product name including spaces +}); +``` + +
    + +### :clap: Bien faire les choses, exemple : Données réalistes randomisés + +```javascript +it("Better: When adding new valid product, get successful confirmation", async () => { + const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); + //Generated random input: {'Sleek Cotton Computer', 85481} + expect(addProductResult).to.be.true; + //Test failed, the random input triggered some path we never planned for. + //We discovered a bug early! +}); +``` + +
    + +

    + +## ⚪ ️ 1.7 Tester plusieurs combinaisons d'input avec le Property-based testing + +:white_check_mark: **À faire:** En règle général, on choisit quelques valeurs d'entrées pour chaque test. Même lorsque le format des inputs est réaliste (voir le point 'Utiliser des données réalistes'), on couvre seulement quelques combinaisons d'entrées. En revanche, en production, une API appelée avec 5 paramètres peut être invoquée avec des milliers de permutations différentes, l'une d'entre elle peut faire échouer notre processus ([voir le Fuzz testing](https://fr.wikipedia.org/wiki/Fuzzing)). Et si tu pouvais écrire un seul test qui envoie 1000 permutations d'entrées automatiquement et détecte pour lequel d'entre eux notre processus ne retourne pas la bonne valeur ? Le Property-based testing c'est une méthode qui fait exactement ça : En testant toutes les combinaisons d'entrées possible on augmente les chance de détecter un bug. Par exemple, prenons une méthode : addNewProduct(id, name, isDiscount), la librairie appellera cette méthode avec plusieurs combinaisons de (number, string, boolean) tel que (1, “iPhone”, false), (2, “Galaxy”, true). Tu peux utiliser le property-based testing avec ta librairie de test préféré (Mocha, Jest ...etc) à l'aide de librairie tel que [js-verify](https://github.com/jsverify/jsverify) ou [testcheck](https://github.com/leebyron/testcheck-js) (meilleure documentation). MAJ: Nicolas Dubien à suggéré dans les commentaire de [regarder fast-check](https://github.com/dubzzz/fast-check#readme) qui semble offrir des fonctionnalitées supplémentaire et être activement maintenue. +
    + +❌ **Autrement:** Inconsciemment, tu choisis des entrées de test qui ne couvrent que les cas qui fonctionnent correctement. Malheureusement, cela réduit l'efficacité tests et leur capacité a détecter des bugs. +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Tester plusieurs permutations d'entrées avec "fast-check" + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +import fc from "fast-check"; + +describe("Product service", () => { + describe("Adding new", () => { + //this will run 100 times with different random properties + it("Add new product with random yet valid properties, always successful", () => + fc.assert( + fc.property(fc.integer(), fc.string(), (id, name) => { + expect(addNewProduct(id, name).status).toEqual("approved"); + }) + )); + }); +}); +``` + +
    + +

    + +## ⚪ ️ 1.8 Si besoin, n'utiliser que des snapshots courts et inline + +:white_check_mark: **Do:** Quand il y a un besoin pour du [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), utilise seulement des snapshots courts (3-7 lignes) qui sont inclut dans le test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) et pas dans des fichiers externes. Respecter cette règle permettra à vos tests de rester auto-explicatif et moins fragile. + +D'un autre côté, les tutoriels et outils 'classique' encouragent à stocker de gros fichiers (résultats d'API JSON, markup d'un composant) sur un emplacement externe et de s'assurer à chaque fois que le test est lancé, de comparer le résultat reçu avec la version sauvegardée. Cela peut, par exemple, implicitement coupler notre test à 1000 lignes avec 3000 valeurs que le lecteur du test ne verra jamais et auquel il ne pensera pas. Pourquoi est-ce que c'est mauvais ? En faisant ça, il y a 1000 raisons pour votre test d'échouer - Il suffit qu'une seule ligne change pour que le snapshot soit invalide, et cela arrivera probablement souvent. À quelle fréquence ? Pour chaque espace, commentaire ou changement mineur dans le HTML/CSS. De plus, le nom du test ne donnera pas la moindre indication à propos de l'erreur vu qu'il vérifie simplement que les 1000 lignes n'ont pas changé. Cela encourage aussi celui qui écrit les tests à accepter comme valeur de succès un long document qu'il ne pourra pas inspecter et vérifier. Tous ces points sont des symptômes d'un test obscure qui n'est pas ciblé et cherche à en faire trop. + +Il faut noter qu'il y a quelques cas ou de long snapshots externes sont acceptable - Pour valider un schéma et pas des données ou concernant des documents qui ne changent presque jamais +
    + +❌ **Sinon:** Un test UI échoue. Le code semble bon, l'écran rend parfaitement les pixels, que s'est-il passé ? Ton test de snapshot a trouvé une différence entre le document original et le document actuel - un simple espace a été ajouté dans le markdown... + +
    + +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: Coupler nos tests à 2000 lignes de code qu'on ne voit pas + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +it("TestJavaScript.com is renderd correctly", () => { + //Arrange + + //Act + const receivedPage = renderer + .create( Test JavaScript ) + .toJSON(); + + //Assert + expect(receivedPage).toMatchSnapshot(); + //We now implicitly maintain a 2000 lines long document + //every additional line break or comment - will break this test +}); +``` + +
    + +### :clap: Bien faire les choses, exemple: Les attentes sont claires et spécifiques + +```javascript +it("When visiting TestJavaScript.com home page, a menu is displayed", () => { + //Arrange + + //Act + const receivedPage = renderer + .create( Test JavaScript ) + .toJSON(); + + //Assert + + const menu = receivedPage.content.menu; + expect(menu).toMatchInlineSnapshot(` + +`); +}); +``` + +
    + +

    + +## ⚪ ️1.9 Éviter les fixtures et seeds globals, ajouter les données par test + +:white_check_mark: **À faire:** En suivant la règle d'or (point 0), chaque test doit ajouter et agir sur son propre jeu d'entrée en base de données pour éviter d'être couplés et faciliter le raisonnement à propos de la logique du test. En réalité, cette règle est souvent violée par les testeurs qui remplissent la base de données avant de lancer les tests ([aussi connu sous le nom ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) afin d'améliorer les performances. Même si la performance est effectivement une inquiétude valide, elle peut être atténuée (voir "Component testing"), en revanche, la complexité des tests est une peine bien plus douloureuse qui devrait régir les autres considérations la plupart du temps. +En pratique, chaque cas testé doit explicitement ajouter les entrées en base de données dont il a besoin et n'agir que sur ces entrées. Si la performance devient une inquiétude critique - un compromis peut se trouver sous la forme de seeds pour les jeux de tests qui ne modifient pas les données (queries). + +
    + +❌ **Autrement:** Certains tests échoue, le déploiement est annulé, l'équipe va dépenser un temps précieux maintenant, est-ce qu'on a un bug ? Investiguons, oh non - il semble que deux tests modifiaient les même données + +
    + +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: les tests ne sont pas indépendants et reposent sur un hook global pour des données globales en DB + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +before(async () => { + //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + await DB.AddSeedDataFromJson('seed.json'); +}); +it("When updating site name, get successful confirmation", async () => { + //I know that site name "portal" exists - I saw it in the seed files + const siteToUpdate = await SiteService.getSiteByName("Portal"); + const updateNameResult = await SiteService.changeName(siteToUpdate, "newName"); + expect(updateNameResult).to.be(true); +}); +it("When querying by site name, get the right site", async () => { + //I know that site name "portal" exists - I saw it in the seed files + const siteToCheck = await SiteService.getSiteByName("Portal"); + expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ +}); + +``` + +
    + +### :clap: Bien faire les choses, exemple: On peut rester dans le test, chaque test agis sur ses propres données + +```javascript +it("When updating site name, get successful confirmation", async () => { + //test is adding a fresh new records and acting on the records only + const siteUnderTest = await SiteService.addSite({ + name: "siteForUpdateTest" + }); + + const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); + + expect(updateNameResult).to.be(true); +}); +``` + +
    + +
    + +## ⚪ ️ 1.10 Ne pas catcher les erreurs, les prévoir + +:white_check_mark: **À faire:** Lorsqu'on essaye de détecter que certaines entrées déclenchent une erreur, il peut sembler être une bonne idée d'utiliser try-catch-finally et de vérifier qu'on est passé dans le catch. Le résultat est un test étrange et verbeux (exemple plus bas) qui cache l'intention simple du test et le résultat attendu. + +Une alternative plus élégante est d'utiliser l'assertion Chai dédiée : expect(method).to.throw (ou en Jest: expect(method).toThrow()). Il est également obligatoire de vérifier que l'exception contient une propriété qui indique le type d'erreur, sinon, en recevant un message d'erreur générique, l'application ne sera pas capable de faire beaucoup plus que de montrer un message décevant à l'utilisateur. +
    + +❌ **Autrement:** Il sera compliqué de déduire du rapport de test ce qui s'est mal passé + +
    + +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: Un long test qui essaye de vérifier la présence d'une erreur avec try-catch + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +it("When no product name, it throws error 400", async () => { + let errorWeExceptFor = null; + try { + const result = await addNewProduct({}); + } catch (error) { + expect(error.code).to.equal("InvalidInput"); + errorWeExceptFor = error; + } + expect(errorWeExceptFor).not.to.be.null; + //if this assertion fails, the tests results/reports will only show + //that some value is null, there won't be a word about a missing Exception +}); +``` + +
    + +### :clap: Bien faire les choses, exemple: Un attente lisible qui peut être comprise simplement, peut être même par un QA ou PM technique + +```javascript +it("When no product name, it throws error 400", async () => { + await expect(addNewProduct({})) + .to.eventually.throw(AppError) + .with.property("code", "InvalidInput"); +}); +``` + +
    + +

    + +## ⚪ ️ 1.11 Taguer tes tests + +:white_check_mark: **À faire:** Des tests différents doivent être lancés dans différents scénarios. Les tests de fumée (quick smoke), IO-less, doivent tourner à chaque fois qu'un développeur sauvegarde ou commit un fichier, les tests complets end-to-end sont en général lancés quand une nouvelle pull-request est soumise, etc. +On peut réaliser ça en taggant les tests avec des mots clefs comme #cold #api #sanity pour pouvoir utiliser grep et sélectionner les tests qui nous interesse. Par exemple, voilà comment invoquer uniquement le groupe de test 'sanity' avec Mocha : mocha - grep 'sanity' +
    + +❌ **Autrement:** Lancer tous les tests, y compris ceux qui exécutent des dizaines de requêtes DB, à chaque fois qu'un développeur fait un petit changement peut être extrêmement lent et pousser les développeurs a ne pas utiliser les tests. +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Taguer des tests avec '#cold-test' permet à celui qui les lance de n'executer que les tests rapide (cold = tests rapides qui ne font pas d'opérations IO et peuvent être executés souvent, meme pendant que le développeur code) + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +//this test is fast (no DB) and we're tagging it correspondigly +//now the user/CI can run it frequently +describe("Order service", function() { + describe("Add new order #cold-test #sanity", function() { + test("Scenario - no currency was supplied. Expectation - Use the default currency #sanity", function() { + //code logic here + }); + }); +}); +``` + +
    + +

    + +## ⚪ ️ 1.12 Catégoriser tes tests sous au moins 2 niveaux + +:white_check_mark: **À faire:** Applique une structure à ta suite de tests pour qu'un visiteur occasionnel puisse facilement comprendre les attentes (Les tests sont la meilleure documentation) et les différents scénarios testés. Une méthode fréquence pour ça est de placer au moins 2 blocs 'describe' au-dessus de vos tests : Le premier est pour le nom de l'unité testé et le deuxième pour un niveau supplémentaire de catégorisation comme le scénario ou une catégorie (voir l'exemple de code plus bas). Cette organisation améliorera grandement vos rapports de tests: Le lecteur comprendra simplement les catégories de tests, examinera la section voulue et verra les corrélations entre les tests qui échouent. De plus, ce sera bien plus simple pour un développeur de naviguer dans le code d'une suite avec de nombreux tests. Il y a plusieurs structures alternatives pour les suites de tests que tu peux envisager comme [given-when-then](https://github.com/searls/jasmine-given) et [RITE](https://github.com/ericelliott/riteway) + +
    + +❌ **Autrement:** En regardant un rapport avec une longue liste de tests a plat, le lecteur devra lire un long texte pour comprendre les scénarios majeurs et comprendre les liens entre les tests qui échouent. Imagine le cas suivant : Quand 7/100 tests échouent, regarder une liste à plat nécessitera d'aller lire le contenu des tests qui échouent pour comprendre le lien entre eux. En revanche, dans un rapport hiérarchique, ils pourraient tous être au sein du même scénario ou d'une catégorie et le lecteur pourra rapidement déduire ce qui est, ou du moins où est, la cause de l'erreur. + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Structurer une suite avec le nom de l'unité testé et les scénarios mènera au rapport pratique montré ci-dessous +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +// Unit under test +describe("Transfer service", () => { + //Scenario + describe("When no credit", () => { + //Expectation + test("Then the response status should decline", () => {}); + + //Expectation + test("Then it should send email to admin", () => {}); + }); +}); +``` + +![alt text](assets/hierarchical-report.png) + +
    + +### :thumbsdown: Exemple d'anti-pattern: Une liste de tests à plat qui rend l'identification du problème difficile pour le lecteur + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Mocha") + +```javascript +test("Then the response status should decline", () => {}); + +test("Then it should send email", () => {}); + +test("Then there should not be a new transfer record", () => {}); +``` + +![alt text](assets/flat-report.png) + +
    + +
    + +

    + +## ⚪ ️1.13 Autre bonnes pratiques génériques + +:white_check_mark: **À faire:** Ce post se concentre sur des conseils de tests qui sont en rapport, ou au moins peuvent être présentés, avec Node JS. Ce point, cependant, regroupe quelques conseils sans rapport avec Node qui sont bien connus. + +Apprends et pratique [les principes TDD](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) - ils ont beaucoup de valeurs pour la plupart, mais ne soit pas intimidés s'ils ne correspondent pas à ton style, tu n'es pas le seul. Envisage d'écrire les tests avec le code dans un [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), vérifie que chaque test vérifie exactement une chose, quand tu trouves un bug - avant de le fixer, écrit un test qui détectera le bug à l'avenir, laisse chaque test échouer au moins une fois avant de devenir vert, commence un module en écrivant du code simple et rapide qui valide le test - puis refactor graduellement et passe le code a un niveau de production, évite toute dépendance à l'environnement (paths, OS, etc) +
    + +❌ **Autrement:** Tu manqueras les perles de sagesses recueillies pendant des décennies. + +

    + +# Section 2️⃣: Tests Backend + +## ⚪ ️2.1 Enrichis ton portefeuille de test: Vois plus loin que les tests unitaire et la pyramide + +:white_check_mark: **À faire:** La [pyramide de tests](https://martinfowler.com/bliki/TestPyramid.html), bien que vielle de plus de 10 ans, est un bon modèle qui suggère trois types de tests et influence la plupart des stratégies de tests des développeurs. Dans un même temps, une poignée de nouvelles techniques de tests brillantes ont émergé et sont dans l'ombre de la pyramide de tests. Étant donné l'étendu des changements que l'on a vu ces 10 dernières années (micro-services, cloud, serverless), est-il seulement possible qu'un vieux modèle soit adapté à *tout* les types d'applications ? Le monde du test ne devrait-il pas accueillir de nouvelles techniques ? + +Ne vous méprenez pas, en 2019, la pyramide de tests, le TDD et les tests unitaires sont toujours une technique puissante et sont probablement le meilleur choix pour beaucoup d'applications. Seulement, comme les autres modèles, malgré qu'il soit utile, [il doit être faux parfois](https://en.wikipedia.org/wiki/All_models_are_wrong). Par exemple, imagine une application IoT qui traite de nombreux événements dans une queue (message-bus) comme Kafka/RabbitMQ, qui vont ensuite dans un entrepot de donnée puis sont lus par une UI d'analyse. Est-ce qu'on devrait vraiment dépenser 50% de notre budget de test pour écrire des tests unitaires sur une application qui est centrée sur l'intégration et n'a presque aucune logique ? Plus la diversité des applications augmente (bots, crypto, Alexa-skills) plus les chances sont grandes de trouver un scénario ou la pyramide de test n'est pas le meilleur choix. + +Il est temps d'enrichir ton portefeuille de test et de devenir familier avec plus de types de tests (les prochains points suggèrent quelques idées), des modèles tels que celui de la pyramide de tests mais aussi d'associer les types de tests aux problèmes que tu rencontres dans le monde réel ('Hey, notre API est cassé, écrivons des consumer-driven contract testing!'), diversifie tes tests comme un investisseur qui construit son portefeuille en se basant sur l'analyse des risques - estime où les problèmes risquent de se poser et applique des mesures de prévention pour réduire ces risques. + +Un mot d'avertissement: l'argument du TDD dans le monde du développement à un visage typique de fausse dichotomie, certains disent de l'utiliser de partout, d'autres pensent que c'est le diable. Tous ceux qui parlent en absolu ont tord :] + +
    + +❌ **Autrement:** Tu va rater des outils avec un retour sur investissement incroyable, certains comme Fuzz, lint, ou mutation peuvent apporter de la valeur en 10 minutes + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Cindy Sridharan propose un portefeuille de tests riche dans son excellent post 'Testing Microservices - the same way' + +![alt text](assets/bp-12-rich-testing.jpeg "Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the sane way’") + +☺️Example: [YouTube: “Beyond Unit Tests: 5 Shiny Node.JS Test Types (2018)” (Yoni Goldberg)](https://www.youtube.com/watch?v=-2zP494wdUY&feature=youtu.be) + +
    + +![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "A test name that constitutes 3 parts") + +
    + +

    + +## ⚪ ️2.2 Les tests de composant pourrait être ton meilleur arrangement + +:white_check_mark: **À faire:** Chaque test unitaire couvre une petite portion de l'application et il est coûteux de couvrir l'ensemble, alors que les tests end-to-end couvrent facilement une grande partie mais sont lent, pourquoi ne pas appliquer une approche intermédiaire et écrire des tests qui sont plus gros que les tests unitaires mais plus petits que les tests end-to-end ? Les tests de composant (Component testing) sont méconnus du monde de test mais ils offrent le meilleur des deux mondes: des performances raisonnable et la possibilité d'appliquer le pattern TDD + une couverture correcte et réaliste + +Les tests de composant se concentrent sur "l'unité" du microservice, ils fonctionnent sur l'API, ne mock rien qui appartient au microservice lui-même (une vrai DB, ou au moins une version in-memory de cette DB) mais stub tout ce qui est externe, comme les appels à d'autres microservices. En faisant ça, on test ce que l'on déploie, on approche l'application de l'extérieur vers l'intérieur et on gagne en confiance dans un laps de temps raisonnable. + +
    + +❌ **Autrement:** Tu risque de passer de longues journée à écrire des tests unitaire pour te rendre compte que tu n'as que 20% de couverture + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Supertest permet d'approcher l'API Express (rapide et couvre plusieurs niveaux) + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) allows approaching Express API in-process (fast and cover many layers)") + +
    + +

    + +## ⚪ ️2.3 Vérifier que les nouvelles versions ne cassent pas l'API avec les tests de contrat + +:white_check_mark: **À faire:** Ton microservice a plusieurs clients, et tu exécutes plusieurs versions du service pour des raisons de compatibilité (pour que tout le monde soit content). Puis tu changes un champ et 'boom!', un client important qui compte sur ce champ est en colère. C'est le Catch-22 du monde de l'intégration : Il est très difficile pour le coté serveur de considérer toutes les attentes des clients. D'un autre coté, les clients ne peuvent pas réaliser de tests puisque le serveur contrôles les dates de sorties. [Les "consumer-driven contracts" et le framework PACT](https://docs.pact.io) sont nés pour formaliser ce processus avec une approche disruptive - ce n'est pas le serveur qui définit ses propres plans de test, mais le client qui définit les tests du ...serveur! PACT peut enregistrer les attentes du client et les placer dans un emplacement partagé, "broker", afin que le serveur puisse extraire les attentes et s'exécuter sur chaque version en utilisant la librairie PACT pour détecter les contrats rompus - une attente du client qui n'est pas satisfaite. En faisant ça, toutes les incompatibilités d'API server-client sont repérés tôt pendant le build/CI et peuvent vous éviter beaucoup de frustration. +
    + +❌ **Autrement:** L'alternative sont les tests manuels épuisants ou la peur du déploiement +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: + +![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") + +![alt text](assets/bp-14-testing-best-practices-contract-flow.png) + +
    + +

    + +## ⚪ ️ 2.4 Tester tes middlewares de manière isolée + +:white_check_mark: **À faire:** Beaucoup évitent les tests de Middleware parce qu'ils représentent une petite portion du système et requièrent un serveur express live. Ce sont deux mauvaises raisons - les Middlewares sont petits, mais affectent toute ou la plupart des requêtes et peuvent être testés simplement en tant que fonction qui reçoit un objet JS {req,res}. Pour tester un middleware, il suffit de l'invoquer et d'espionner ([avec Sinon par exemple](https://www.npmjs.com/package/sinon) l'interaction avec l'objet {req,res} pour s'assurer que la fonction a effectuée la bonne action. La librairie [node-mock-http](https://www.npmjs.com/package/node-mocks-http) va encore plus loin et prend en compte l'objet {req,res} tout en surveillant son comportement. Par exemple, il peut vérifier que le status http qui à été défini sur l'objet res correspond aux attentes (voir l'exemple ci-dessous) +
    + +❌ **Autrement:** Un bug dans un middleware Express === un bug dans toutes ou la plupart des requêtes +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Tester le middleware en isolation sans effectuer d'appel réseau et sans réveiller l'ensemble de la machine Express + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +//the middleware we want to test +const unitUnderTest = require("./middleware"); +const httpMocks = require("node-mocks-http"); +//Jest syntax, equivelant to describe() & it() in Mocha +test("A request without authentication header, should return http status 403", () => { + const request = httpMocks.createRequest({ + method: "GET", + url: "/user/42", + headers: { + authentication: "" + } + }); + const response = httpMocks.createResponse(); + unitUnderTest(request, response); + expect(response.statusCode).toBe(403); +}); +``` + +
    + +

    + +## ⚪ ️2.5 Mesurer et refactoriser en utilisant des outils d'analyse statique + +:white_check_mark: **À faire:** Utiliser des outils d'analyse statique donne des moyens objectif d'améliorer la qualité et de garder le code maintenable. Tu peux ajouter un outil d'analyse statique à ton build CI pour l'annuler si il détecte un "code smell". Ses arguments de vente par rapport au linting simple sont la capacité d'inspecter la qualité dans le contexte de plusieurs fichiers (e.g. détecter des duplications), effectuer des analyses avancées (e.g. complexité du code) et suivre l'histoire et le progrés d'un problème de code. Deux exemples d'outils que tu peux utiliser sont [SonarQube](https://www.sonarqube.org/) (4,900+ [stars](https://github.com/SonarSource/sonarqube)) et [Code Climate](https://codeclimate.com/) (2,000+ [stars](https://github.com/codeclimate/codeclimate)) + +Credit: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + +❌ **Autrement:** Avec du code de mauvaise qualité, les bugs et la performance seront toujours un problème qu'aucune nouvelle librairie ou fonctionnalité de pointe ne peux corriger + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: CodeClimate, un outil commercial qui peux identifier des méthodes complexes: + +![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg "Examples with CodeClimate") + +![alt text](assets/bp-16-yoni-goldberg-quality.png "CodeClimate, a commercial tool that can identify complex methods:") + +
    + +

    + +## ⚪ ️ 2.6 Vérifier ta préparation pour le chaos liés a Node + +:white_check_mark: **À faire:** Bizarrement, la plupart des tests software concernent uniquement la logique et les données, mais certaines des pires choses qui peuvent arriver ( et qui sont vraiment difficile à atténuer ) sont les problèmes d'infrastructures. Par exemple, est-ce que tu as déjà testé ce qui arrivera quand la mémoire du processus est surchargée, ou quand le serveur/process tombe, est-ce que ton système de monitoring détecte lorsque l'API devient 50% plus lente ? Pour tester et atténuer ce type de choses - [l'ingénierie du Chaos](https://principlesofchaos.org/) est né de Netflix. +Il vise à fournir une sensibilisation, des frameworks et des outils pour tester la résilience de notre application aux problèmes chaotiques. Par exemple, l'un de ces fameux outils, [le chaos monkey](https://github.com/Netflix/chaosmonkey), tue aléatoirement des serveurs pour vérifier que notre service peut toujours servir les utilisateurs et ne repose pas sur un unique serveur ( Il y a aussi une version Kubernetes, [kube-monkey](https://github.com/asobti/kube-monkey), qui tue des pods). Tous ces outils fonctionnent au niveau de l'hébergeur/la plateforme, mais que se passe-t-il si tu veux générer un chaos Node pour vérifier comment ton process gère les erreurs non prévus, les rejets de promesse, la surcharge de mémoire v8 avec l'allocation maximum de 1.7Gb ou est-ce que ton UX reste satisfaisante si l'event loop est bloqué régulièrement ? Pour répondre à ça, j'ai écrit, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) qui fournit toute sorte d'actions chaotiques liées a Node. +
    + +❌ **Autrement:** Pas d'échappatoire ici, la loi de Murphy heurtera votre production sans merci +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Le chaos-Node peut générer toute sortes de d'erreurs Node.js afin que tu puisses tester la résilience de ton application au chaos + +![alt text](assets/bp-17-yoni-goldberg-chaos-monkey-nodejs.png "Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to chaos") + +
    + +
    + +## ⚪ ️2.7 Éviter les fixtures et seeds globals, ajouter les données par test + +:white_check_mark: **À faire:** En suivant la règle d'or (point 0), chaque test doit ajouter et agir sur son propre jeu d'entrée en base de données pour éviter d'être couplés et faciliter le raisonnement à propos de la logique du test. En réalité, cette règle est souvent violée par les testeurs qui remplissent la base de données avant de lancer les tests (aussi connu sous le nom ‘test fixture’) afin d'améliorer les performances. Même si la performance est effectivement une inquiétude valide, elle peut être atténuée (voir "Component testing"), en revanche, la complexité des tests est un chagrin bien plus douloureux qui devrait régir les autres considérations la plupart du temps. En pratique, chaque cas testé doit explicitement ajouter les entrées en base de données dont il a besoin et n'agir que sur ces entrées. Si la performance devient une inquiétude critique - un compromis peut se trouver sous la forme de seeds pour les jeux de tests qui ne modifient pas les données (queries). +
    + +❌ **Autrement:** Certains tests échoue, le déploiement est annulé, l'équipe va dépenser un temps précieux maintenant, est-ce qu'on a un bug ? Investiguons, oh non - il semble que deux tests modifiaient les même données + +
    + +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: les tests ne sont pas indépendants et reposent sur un hook global pour des données globales en DB + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +before(async () => { + //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + await DB.AddSeedDataFromJson('seed.json'); +}); +it("When updating site name, get successful confirmation", async () => { + //I know that site name "portal" exists - I saw it in the seed files + const siteToUpdate = await SiteService.getSiteByName("Portal"); + const updateNameResult = await SiteService.changeName(siteToUpdate, "newName"); + expect(updateNameResult).to.be(true); +}); +it("When querying by site name, get the right site", async () => { + //I know that site name "portal" exists - I saw it in the seed files + const siteToCheck = await SiteService.getSiteByName("Portal"); + expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ +}); + +``` + +
    + +### :clap: Bien faire les choses, exemple: On peut rester dans le test, chaque test agis sur ses propres données + +```javascript +it("When updating site name, get successful confirmation", async () => { + //test is adding a fresh new records and acting on the records only + const siteUnderTest = await SiteService.addSite({ + name: "siteForUpdateTest" + }); + const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); + expect(updateNameResult).to.be(true); +}); +``` + +
    + +

    + +# Section 3️⃣: Tests Frontend + +## ⚪ ️ 3.1 Separer l'UI des fonctionnalités + +:white_check_mark: **À faire:** Lorsqu'on veut tester la logique d'un composant, les détails UI deviennent du bruit qui devrait être extrait, afin que les tests se concentrent purement sur les données. En pratique, extrait les données désirées du markup d'une façon abstraite qui n'est pas trop couplée avec l'implémentation graphique, assert seulement les données (vs des détails graphiques HTML/CSS) et désactive les animations qui ralentissent. Tu peux être tenté d'éviter le rendu et ne tester que les parties derrière l'UI (e.g. services, actions, store) mais il s'agira de tests fictionnels qui ne ressemblent pas à la réalité et ne révéleront pas les cas ou la bonne donnée n'arrive pas à l'UI. +
    + +❌ **Autrement:** Les données calculées de ton test peuvent être prêtes en 10ms, mais l'ensemble du test durera 500ms (100 tests = 1 min) à cause d'animation qui ne nous concerne pas dans le cadre du test. +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Séparer les détails UI + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +test("When users-list is flagged to show only VIP, should display only VIP members", () => { + // Arrange + const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; + + // Act + const { getAllByTestId } = render(); + + // Assert - Extract the data from the UI first + const allRenderedUsers = getAllByTestId("user").map(uiElement => uiElement.textContent); + const allRealVIPUsers = allUsers.filter(user => user.vip).map(user => user.name); + expect(allRenderedUsers).toEqual(allRealVIPUsers); //compare data with data, no UI here +}); +``` + +
    + +### :thumbsdown: Exemple d'anti pattern: L'assertion mélange des détails UX et les données + +```javascript +test("When flagging to show only VIP, should display only VIP members", () => { + // Arrange + const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; + + // Act + const { getAllByTestId } = render(); + + // Assert - Mix UI & data in assertion + expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); +}); +``` + +
    + +

    + +## ⚪ ️ 3.2 Query les éléments HTML en te basant sur des attributs qui ont peu de chance de changer + +:white_check_mark: **À faire:** Query les éléments HTML en te basant sur des attributs qui ont de grandes chances de survivre à un changement graphique, contrairement aux sélecteurs CSS ou aux labels des forms. Si l'élément n'as pas d'attribut de ce type, crée un attribut dédié au test comme 'test-id-submit-sutton'. Utiliser cette méthode permet non seulement d'être sûr que vos tests fonctionnels/logique ne cassent pas à cause d'un changement visuel mais il devient également plus clair pour toute votre équipe que cet élément et son attribut sont utilisés par les tests et ne devraient pas être supprimés. +
    + +❌ **Autrement:** Tu veux tester la fonctionnalité de connexion qui couvre de nombreux composants, logiques et services, tout est configuré parfaitement - subs, spies, les appels Ajax sont isolés. Tout semble parfait. Puis le test échoue car le designer à changé la class CSS du div de 'thick-border' à 'thin-border' + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Query un élément en utilisant un attribut dédié aux tests + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") + +```html +// the markup code (part of React component) +

    + + {value} + + +

    +``` + +```javascript +// this example is using react-testing-library +test("Whenever no data is passed to metric, show 0 as default", () => { + // Arrange + const metricValue = undefined; + + // Act + const { getByTestId } = render(); + + expect(getByTestId("errorsLabel").text()).toBe("0"); +}); +``` + +
    + +### :thumbsdown: Exemple d'anti pattern: Compter sur les attributs CSS + +```html + +{value} + +``` + +```javascript +// this exammple is using enzyme +test("Whenever no data is passed, error metric shows zero", () => { + // ... + + expect(wrapper.find("[className='d-flex-column']").text()).toBe("0"); +}); +``` + +
    + +
    + +## ⚪ ️ 3.3 Lorsque c'est possible, tester avec un composant réaliste et totalement rendu + +:white_check_mark: **Do:** Lorsqu'ils sont de taille raisonnable, tests tes composants de l'extérieur comme le font tes utilisateurs, rend complètement l'UI, agit dessus, et vérifie que l'UI rendu se comporte comme on l'attend. +Évite toute sorte de mocking, de rendu partiels ou superficiel - cette approche peut résulter en bugs non détectés à cause du manque de détails et rendre plus difficile la maintenance des tests puisque les tests modifient les propriétés interne (voir le point 'Privilégier les tests blackbox'). Si l'un des composants enfants ralentis significativement (e.g animations) ou complique la configuration - considère de le remplacer explicitement avec un faux. + +Maintenant que c'est dit, une mise en garde s'impose: cette technique fonctionne pour des petit/moyens composants qui ont un nombre raisonnable de composants enfants. Rendre complètement un composant avec trop d'enfants compliquera le raisonnement à propos des échecs de tests (analyse de la cause originelle) et peut être trop lent. Dans ces cas, écrit seulement quelques tests pour ce parent, et plus de tests pour ses enfants. + +
    + +❌ **Autrement:** Lorsque tu fouilles dans les détails internes du composant en invoquant ses méthodes privées, et en vérifiant l'état interne - tu devras refactoriser tous les tests lorsque tu refactorisera l'implémentation du composant. Est-ce que tu as vraiment la capacité de tenir ce niveau de maintenance ? + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Travailler de façon réaliste sur un composant complètement rendu + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Enzyme-blue.svg "Examples with Enzyme") + +```javascript +class Calendar extends React.Component { + static defaultProps = { showFilters: false }; + + render() { + return ( +
    + A filters panel with a button to hide/show filters + +
    + ); + } +} + +//Examples use React & Enzyme +test("Realistic approach: When clicked to show filters, filters are displayed", () => { + // Arrange + const wrapper = mount(); + + // Act + wrapper.find("button").simulate("click"); + + // Assert + expect(wrapper.text().includes("Choose Filter")); + // This is how the user will approach this element: by text +}); +``` + +### :thumbsdown: Exemple d'anti pattern: Mocker la réalité avec un rendu superficiel + +```javascript +test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { + // Arrange + const wrapper = shallow(); + + // Act + wrapper + .find("filtersPanel") + .instance() + .showFilters(); + // Tap into the internals, bypass the UI and invoke a method. White-box approach + + // Assert + expect(wrapper.find("Filter").props()).toEqual({ title: "Choose Filter" }); + // what if we change the prop name or don't pass anything relevant? +}); +``` + +
    + +
    + +## ⚪ ️ 3.4 Ne pas attendre, utiliser la gestion des évènements asynchrone implémenté dans les frameworks. Essayer aussi d'accélérer les choses + +:white_check_mark: **À faire:** Souvent, le temps de complétion de l'unité qu'on test est inconnu (e.g animations qui suspendent l'apparition d'éléments) - Dans ce cas, évite d'attendre (e.g. setTimeOut ) et préfère des méthodes déterministe que la plupart des frameworks fournissent. Certaines librairies permettent d'attendre certaines opérations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), d'autres fournissent une API pour attendre comme [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). +Parfois il est plus élégant de stub la ressource lente, comme une API, une fois que le moment de réponse devient déterminé, le composant peut être re-rendu explicitement. Lorsque l'on dépend d'un composant externe qui attend, il peut être utile d'[accélérer l'horloge](https://jestjs.io/docs/en/timer-mocks). +Attendre est un pattern à éviter puisqu'il force tes tests à être lent ou risqué ( s'ils n'attendent pas assez longtemps ). À chaque fois qu'attendre ou requêter sont inévitable et qu'il n'y a pas de support de la part du framework de test, des librairies comme [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) peuvent aider avec une solution demi-déterministe. +
    + +❌ **Autrement:** En attendant pour un long moment, les tests seront plus lent. En attendant trop peu, les tests échoueront si l'unité testée n'a pas répondu dans les temps. Cela se résume donc à un compromis entre l'instabilité et les mauvaises performances. + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: E2E API qui se résoud uniquement lorsque l'opération asynchrone est terminée (Cypress) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +// using Cypress +cy.get("#show-products").click(); // navigate +cy.wait("@products"); // wait for route to appear +// this line will get executed only when the route is ready +``` + +### :clap: Bien faire les choses, exemple: Librairie de tests qui attend les éléments du DOM + +```javascript +// @testing-library/dom +test("movie title appears", async () => { + // element is initially not present... + + // wait for appearance + await wait(() => { + expect(getByText("the lion king")).toBeInTheDocument(); + }); + + // wait for appearance and return the element + const movie = await waitForElement(() => getByText("the lion king")); +}); +``` + +### :thumbsdown: Exemple d'anti pattern: Code custom qui attend + +```javascript +test("movie title appears", async () => { + // element is initially not present... + + // custom wait logic (caution: simplistic, no timeout) + const interval = setInterval(() => { + const found = getByText("the lion king"); + if (found) { + clearInterval(interval); + expect(getByText("the lion king")).toBeInTheDocument(); + } + }, 100); + + // wait for appearance and return the element + const movie = await waitForElement(() => getByText("the lion king")); +}); +``` + +
    + +
    + +## ⚪ ️ 3.5 Regarder comment le contenu est servi sur le réseau + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") + +✅ **À faire:** Applique un monitoring active qui s'assure que le chargement de la page sur un vrai réseau est optimisé - ça inclue les questions UX comme un chargement lent ou un bundle non minifié. Le marché des outils d'inspection n'est pas petit: des outils basiques comme pingdom](https://www.pingdom.com/), AWS CloudWatch, [gcp StackDriver](https://cloud.google.com/monitoring/uptime-checks/) peuvent être configuré rapidement pour vérifier sur le server est disponible et répond sous un délai raisonnable. Cela ne fait qu'effleurer la surface de ce qui pourrait aller mal, il est donc préférable de choisir des outils spécialisés pour le frontend (e.g [lighthouse](https://developers.google.com/web/tools/lighthouse/), [pagespeed](https://developers.google.com/speed/pagespeed/insights/)) et d'effectuer une analyse plus complète. L'attention doit être portée sur les symptômes, les métriques qui affectent directement l'expérience utilisateur, comme le temps de chargement d'une page, [meaningful paint](https://scotch.io/courses/10-web-performance-audit-tips-for-your-next-billion-users-in-2018/fmp-first-meaningful-paint), [le temps jusqu'à ce que la page devienne intéractive (TTI)](https://calibreapp.com/blog/time-to-interactive/). En plus de ça, on peut également surveiller les causes techniques, comme s'assurer que le contenu est complet, le temps jusqu'au premier byte, l'optimisation des images, s'assurer d'une taille de DOM raisonnable, SSL et autres. Il est recommandable d'avoir ces monitorings complets à la fois pendant le développement, dans le processus CI et surtout - 24h/24 7j/7 sur les serveurs/CDN de production +
    + +❌ **Autrement:** Il doit être décevant de se rendre compte qu'après tout le soin apporté à la création d'une interface utilisateur, des tests 100% fonctionnels réussis et des bundles sophistiqué - l'expérience utilisateur est horrible et lente à cause d'une mauvaise configuration du CDN. + +
    + +
    Exemple de code + +### :clap: Bien faire les choses, exemple: Rapport d'inspection du temps de chargement avec Lighthouse + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + +
    + +
    + +## ⚪ ️ 3.6 Stub les ressources lente ou incertaine comme l'API backend + +:white_check_mark: **À faire:** Lorsque tu codes tes tests mainstream ( pas les tests E2E ), évite d'impliquer toute ressource qui n'est pas sous ta responsabilité et sous ton contrôle comme l'API et utilise des stubs à la place (i.e. test double). En pratique, à la place de vrais appels à une API, utilise une librairie de tests double ( comme [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) pour simuler la réponse. L'avantage principal est d'éviter les comportements incertains - les APIs de tests ou de staging par définition ne sont pas toujours stable et de temps en temps peuvent faire échouer tes tests même si ton composant se comporte bien (l'environnement de production n'a pas été fait pour les tests et limite généralement les requêtes). Faire ça permettra de simuler plusieurs comportements d'API qui devrait diriger le comportement de ton composant, comme lorsqu'aucune donnée n'est trouvée ou que l'API émet une erreur. Enfin et surtout, les appels réseau vont énormément ralentir les tests. +
    + +❌ **Autrement:** Le test moyen ne tourne pas plus de quelques ms, un API call moyen dure environ 100ms. Cela rend les tests ~20x plus lent. +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Stub ou intercepter les appels API +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +// unit under test +export default function ProductsList() { + const [products, setProducts] = useState(false); + + const fetchProducts = async () => { + const products = await axios.get("api/products"); + setProducts(products); + }; + + useEffect(() => { + fetchProducts(); + }, []); + + return products ?
    {products}
    :
    No products
    ; +} + +// test +test("When no products exist, show the appropriate message", () => { + // Arrange + nock("api") + .get(`/products`) + .reply(404); + + // Act + const { getByTestId } = render(); + + // Assert + expect(getByTestId("no-products-message")).toBeTruthy(); +}); +``` + +
    + +
    + +## ⚪ ️ 3.7 Avoir quelques tests end-to-end qui lancent le système entier + +:white_check_mark: **À faire:** Même si E2E (end-to-end) veut généralement dire test UI avec un vrai navigateur (voir point 3.6), pour d'autre ils signifient des tests qui englobent le système entier, en incluant le vrai backend. Ce type de tests a beaucoup de valeurs puisqu'ils couvrent les erreurs d'intégrations entre le frontend et le backend à cause d'une mauvaise compréhension des schémas d'échanges. Ils sont aussi un moyen efficace de découvrir des erreurs d'intégrations entre backends (e.g. le microservice A qui envoie le mauvais message au microservice B) et même de détecter des erreurs de déploiement - Il n'y a pas de framework backend pour les tests E2E qui soit aussi simple et mature que les frameworks UI comme [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). Le point négatif de ces tests, c'est le haut cout de configuration pour un environnement avec autant de composants, et surtout leur fragilité - avec 50 microservices, même si un seul échoue l'ensemble du test E2E échoue. Pour cette raison, cette technique doit être utilisée avec parcimonie, il ne faut pas avoir plus de 1-10 tests de ce type. Ceci dit, même un petit nombre de tests E2E sont susceptibles de détecter les problèmes pour lesquels ils sont en place - les défauts de déploiement et d'intégration. Il est conseillé de les exécuter sur un environnement de pré-production. + +
    + +❌ **Autrement:** L'UI peut investir beaucoup en testant ces fonctionnalités seulement pour réaliser que les données retournées par le backend sont différentes de ce qui était attendu +
    + +## ⚪ ️ 3.8 Accélérer les tests E2E en réutilisant les informations d'authentification + +:white_check_mark: **à faire:** Dans des tests E2E qui incluent un vrai backend et utilisent un token utilisateur valide pour les appels API, ce n'est pas rentable d'isoler les tests à un niveau ou l'utilisateur est créé et authentifié à chaque requete. À la place, authentifie l'utilisateur une seule fois avant que l'exécution des tests commence (i.e before-all hook), enregistre le token en local et réutilise le dans les requetes. Ça semble violer un des principes de test principal - garder les tests autonomes sans associer les ressources. Même si c'est une inquiétude valide, dans les tests E2E la performance est une inquiétude clé et créer 1-3 requêtes API avant chaque test peut mener a un temps d'execution horrible. Réutiliser les informations d'authentification ne veut pas dire que les tests doivent agir sur la même entrée utilisateur - si le test compte sur les entrées utilisateur (e.g. test l'historique de paiement d'un utilisateur) alors assure toi de générer ces entrées dans le test et évite de les partager avec d'autres tests. Rappelle-toi aussi que le backend peut être simulé - Si les tests se concentrent sur le frontend, il vaut mieux les isoler et simuler l'API backend (voir point 3.6). +
    + +❌ **Autrement:** Si on prend 200 cas de tests et qu'on estime l'authentification à 100ms = 20 secondes simplement pour s'authentifier encore et encore + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Se connecter dans le before-all pas dans le before-each +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +let authenticationToken; + +// happens before ALL tests run +before(() => { + cy.request('POST', 'http://localhost:3000/login', { + username: Cypress.env('username'), + password: Cypress.env('password'), + }) + .its('body') + .then((responseFromLogin) => { + authenticationToken = responseFromLogin.token; + }) +}) + +// happens before EACH test +beforeEach(setUser => () { + cy.visit('/home', { + onBeforeLoad (win) { + win.localStorage.setItem('token', JSON.stringify(authenticationToken)) + }, + }) +}) + +``` + +
    + +
    + +## ⚪ ️ 3.9 Avoir un test E2E qui parcours juste les pages du site + +:white_check_mark: **À faire:** Pour le suivi de production et la vérification pendant le développement, lance un seul test E2E qui visite toute ou la plupart des pages du site et vérifie qu'aucune n'échoue. Ce type de test apporte un bon retour sur investissement puisqu'il est très simple à écrire et maintenir, mais peut détecter tout type d'erreur en incluant les problèmes fonctionnels, de réseau ou de déploiement. Les autres types de smoke test et sanity check ne sont pas aussi fiable et exhaustifs - certaines équipes opérationnelles ne font que ping la page d'accueil (production) ou les développeurs qui lancent plusieurs tests d'intégrations qui ne révèlent pas les problèmes de packaging ou liés au navigateur. Il est évident que ce smoke test ne remplace pas les tests fonctionnels, mais il sert à détecter rapidement les problèmes. + +
    + +❌ **Autrement:** Tout peut sembler parfait, tous les tests passent, le health-check de production est également positif, mais le composant de paiement a eu des erreurs de packaging et seul la route /payment ne s'affiche pas + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Smoke test qui parcours toute les pages + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +it("When doing smoke testing over all page, should load them all successfully", () => { + // exemplified using Cypress but can be implemented easily + // using any E2E suite + cy.visit("https://mysite.com/home"); + cy.contains("Home"); + cy.contains("https://mysite.com/Login"); + cy.contains("Login"); + cy.contains("https://mysite.com/About"); + cy.contains("About"); +}); +``` + +
    + +
    + +## ⚪ ️ 3.10 Exposer les tests comme un document collaboratif + +:white_check_mark: **À faire:** En plus d'améliorer la stabilité de l'application, les tests apportent une autre opportunité intéressante - ils servent comment une documentation de l'app. +Puisque les tests parlent naturellement à un niveau moins technique avec un langage plus produit/UX, en utilisant les bons outils, ils peuvent être utilisés comme un outil de communication qui aligne toute l'équipe - les développeurs et les clients. +Par exemple, certains frameworks permettent d'exprimer les parcours et les attentes (i.e les plans de tests) en utilisant un langage lisible par l'humain, donc chaque personne impliquée, y compris les product manager, peuvent lire, approuver et communiquer sur les tests qui sont juste devenu le document de spécification. Cette technique s'appelle aussi 'test d'acceptance' puisqu'il permet au client de définir ses critères de validité en langage simple. Il s'agit de [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) dans sa forme la plus pure. L'un des frameworks populaire qui permet ça est [Cucumber qui a un goût de Javascript](https://github.com/cucumber/cucumber-js), voir l'exemple ci-dessous. Une autre opportunité similaire, [StoryBook](https://storybook.js.org/) permet d'exposer les composants UI comme un catalogue graphique dans lequel on peut se promener à travers les différents états de chaque composant (e.g afficher une grille avec ou sans filtre, l'afficher avec plusieurs lignes ou aucune, etc), voir à quoi il ressemble, et comment déclencher cet état - cela peut servir aux équipe produit mais sert surtout de documentation aux développeurs qui utilisent ces composants + +❌ **Autrement:** Après avoir investi des ressources dans les tests, ce serait juste dommage de ne pas se servir de cet investissement pour apporter encore plus de valeur + +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Décrire les tests dans un language humain avec cucumber-js +![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") + +```javascript +// this is how one can describe tests using cucumber: plain language that allows anyone to understand and collaborate + +Feature: Twitter new tweet + + I want to tweet something in Twitter + + @focus + Scenario: Tweeting from the home page + Given I open Twitter home + Given I click on "New tweet" button + Given I type "Hello followers!" in the textbox + Given I click on "Submit" button + Then I see message "Tweet saved" + +``` + +### :clap: Bien faire les choses, exemple: Visualiser nos composants, leurs états et entrées en utilisant Storybook +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 Détecter les problèmes visuels avec des outils automatisés + +:white_check_mark: **À faire:** Configure des outils automatisés pour capturer des screenshots de l'UI quand des changements sont présentés et détecter les problèmes visuels comme du contenu qui se superpose ou qui est cassé. Cela permet de vérifier que non seulement les bonnes données sont présente mais également que l'utilisateur peut les voir correctement. Cette technique n'est pas très courante, notre état d'esprit quand on pense aux tests est tourné sur les tests fonctionnels mais c'est le visuel que l'utilisateur expérimente et avec le nombre d'appareils différents disponible il est simple de rater un bug UI important. Certains outils gratuits procurent les bases - générer et enregistrer les screenshots pour qu'ils soient inspectés par un humain. Même si cette approche peut être suffisante pour de petites apps, son défaut comme tout test manuel est qu'il demande une intervention humaine à chaque fois que quelque chose change. D'un autre côté, il est assez difficile de détecter des problèmes UI automatiquement à cause du manque de définition claire - C'est ici que le domaine de 'Visual Regression' entre en jeu et résout ce problème en comparant d'ancienne UI avec les changements les plus récent pour détecter les différences. Certains outils gratuits peuvent fournir certaines de ces fonctionnalités (e.g [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>)) mais peuvent demander un temps de configuration considérable. Les outils commerciaux (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) vont un peu plus loin en simplifiant l'installation et en apportant des fonctionnalités avancés comme la gestion de l'UI, des alertes, de la capture automatique en éliminant le "bruit visuel" (e.g. pubs, animations) et même l'analyse du changement DOM/CSS qui a provoqué ce problème. + +
    + +❌ **Autrement:** Quelle est la qualité d'une page qui affiche un bon contenu (100% des tests passent), charge instantanément mais dont la moitié du contenu est cachée ? + +
    + +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: Une régression visuelle typique - le bon contenu qui est mal servi + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + +### :clap: Bien faire les choses, exemple: Configurer wraith pour capturer et comparer les snapshots de l'UI + +![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") + +``` +​# Add as many domains as necessary. Key will act as a label​ + +domains: + english: "http://www.mysite.com"​ + +​# Type screen widths below, here are a couple of examples​ + +screen_widths: + + - 600​ + - 768​ + - 1024​ + - 1280​ + +​# Type page URL paths below, here are a couple of examples​ +paths: + about: + path: /about + selector: '.about'​ + subscribe: + selector: '.subscribe'​ + path: /subscribe +``` + +### :clap: Bien faire les choses, exemple: Utiliser Applitools pour obtenir des comparaisons de snapshots et d'autres fonctionnalités avancées + +![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg "Using Applitools") ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +import * as todoPage from "../page-objects/todo-page"; + +describe("visual validation", () => { + before(() => todoPage.navigate()); + beforeEach(() => cy.eyesOpen({ appName: "TAU TodoMVC" })); + afterEach(() => cy.eyesClose()); + + it("should look good", () => { + cy.eyesCheckWindow("empty todo list"); + todoPage.addTodo("Clean room"); + todoPage.addTodo("Learn javascript"); + cy.eyesCheckWindow("two todos"); + todoPage.toggleTodo(0); + cy.eyesCheckWindow("mark as completed"); + }); +}); +``` + +
    + +

    + +# Section 4️⃣: Mesurer l'efficacité des tests + +

    + +## ⚪ ️ 4.1 Avoir assez de couverture pour être confiant, ~80% semble être le nombre magique + +:white_check_mark: **À faire:** Le but des tests est d'être assez confiant pour avancer rapidement, évidemment, plus le code est testé plus l'équipe peut être confiante. La couverture (coverage) est une mesure du nombre de lignes de code (et branches, statements, etc) sont atteint par les tests. À partir de quand est-ce suffisant ? 10-30% est évidemment trop bas pour avoir la moindre idée de la validité du build, d'un autre côté 100% est vraiment coûteux et peut dévier votre intérêt des parties importantes pour des coins exotiques du code. La réponse longue est que ça dépend de plusieurs facteurs comme le type de l'application - si tu construis la prochaine génération d'Airbus A380 alors 100% est obligatoire, pour un site d'image de dessin animé 50% peut être déjà trop. Même si la plupart des amateurs de tests assurent que le bon seuil dépend du contexte, la plupart d'entre eux mentionnent également le nombre 80% est une bonne règle générale ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) qui devrait répondre au besoin de la plupart des applications. + +Conseil d'implémentation: Tu peux vouloir configurer ton intégration continue pour qu'elle ait un seuil de couverture ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) et arrêter les builds qui ne répondent pas à ce standard (il est également possible de configurer un seuil par composant, voir l'exemple ci-dessous). En plus de ça, envisage de détecter les baisses de couverture (quand un nouveau commit à une couverture inférieure) - cela poussera les développeurs à augmenter ou au moins à conserver la même quantité de code testé. Maintenant que c'est dit, la couverture n'est qu'une mesure, une quantitative, ce n'est pas assez pour dire si vos tests sont robustes. Et il peut aussi être biaisé comme on le verra dans le point suivant + +
    + +❌ **Autrement:** La confiance et les nombres vont ensemble, sans vraiment savoir si tu testes la majorité du système, il y aura de la peur, et la peur va te ralentir + +
    + +
    Exemple de code + +
    + +### :clap: Exemple: Un rapport de couverture classique + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: Bien faire les choses, exemple: Configurer la couverture par composant (avec Jest) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") + +![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest)") + +
    + +

    + +## ⚪ ️ 4.2 Inspecter les rapports de couverture pour détecter les sections qui ne sont pas testées et autres bizarreries + +:white_check_mark: **À faire:** Certains problèmes passent juste sous le radar et sont difficiles à détecter en utilisant des outils traditionnels. Ce ne sont pas vraiment des bugs mais plutôt des comportement surprenants de l'application qui peuvent avoir un impact important. Par exemple, souvent certaines parties du code sont rarement voir jamais invoquées - tu penses que la classe 'PricingCalculator' s'occupe toujours de déterminer le prix du produit mais il se trouve qu'elle n'est jamais invoquée alors qu'on a plus de 10000 produits en base de données et de nombreuses ventes ... Les rapports de couvertures t'aident à déterminer si l'application se comporte comme tu penses qu'elle le fait. En plus de ça, ils peuvent aussi montrer le type de code qui n'est pas testé - Être informé que 80% du code est testé n'indique pas si les parties critiques sont couvertes. Générer des rapports est simple - lance juste ton application en production ou pendant les tests avec le tracking de couverture activé et récupère des rapports qui montrent à quelle fréquence chaque partie du code est invoquée. Si tu prends ton temps pour regarder ces données, tu pourras trouver des pièges +
    + +❌ **Autrement:** Si tu ne sais pas quelles parties du code ne sont pas couvertes par les tests, tu ne sais pas d'où peuvent venir les problèmes + +
    + +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: Qu'est-ce qui ne va pas dans ce rapport de couverture ? + +Basé sur un scénario réel, où nous avont tracké l'usage de notre application en QA et detecté un pattern intéressant sur l'authentification (indice: la quantité d'erreur de connexion n'est pas proportionnelle, quelque chose ne va pas. Finalement il s'est avéré qu'un bug front-end n'arrétait pas d'appeler l'API d'authentification) + +![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report?") + +
    + +

    + +## ⚪ ️ 4.3 Mesurer la couverture logique en utilisant les tests de mutations + +:white_check_mark: **À faire:** Les données de couverture traditionnelles mentent souvent: elles peuvent montrer 100% de couverture, mais aucune de tes fonctions, pas même une seule, ne retourne la bonne réponse. Pourquoi ? Il mesure simplement le nombre de lignes de code que les tests ont visitées, mais ils ne vérifient pas si les tests ont effectivement testé quelque chose et vérifié la réponse. Comme quelqu'un qui effectuerai un voyage d'affaires et qui montre les tampons sur son passeport - Cela ne prouve pas qu'il a travaillé, seulement qu'il a visité quelques aéroports et hôtels. + +Les tests de mutations sont là pour aider à mesurer la quantité de code qui a effectivement été TESTÉ et pas juste VISITÉ. [Stryker](https://stryker-mutator.io/) est une librairie Javascript pour les tests de mutation et son implémentation est très soignée : + +(1) Il change volontairement le code et "implante des bugs". Par exemple, le code newOrder.price === 0 devient newOrder.price != 0. Ces "bugs" sont appelés des mutations + +(2) Il lance les tests, si tous réussissent, alors on a un problème - Les tests n'ont pas remplis leur rôle en découvrant les bugs, les mutations sont dites survivantes. Si les tests échouent, c'est bon, les mutations ont été tuées. + +Savoir que toute ou la plupart des mutations ont été tués donne une meilleure confiance qu'un rapport de couverture traditionnel et le temps de configuration est similaire. +
    + +❌ **Autrement:** Tu seras dupé en croyant que 85% de couverture de code signifie que tes tests détecteront les bugs dans 85% du code + +
    + +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: 100% de couverture, 0% testé + +![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") + +```javascript +function addNewOrder(newOrder) { + logger.log(`Adding new order ${newOrder}`); + DB.save(newOrder); + Mailer.sendMail(newOrder.assignee, `A new order was places ${newOrder}`); + + return { approved: true }; +} + +it("Test addNewOrder, don't use such test names", () => { + addNewOrder({ assignee: "John@mailer.com", price: 120 }); +}); //Triggers 100% code coverage, but it doesn't check anything +``` + +
    + +### :clap: Bien faire les choses, exemple: Un rapport Stryker, un outil pour les tests de mutations, qui détecte et compte la quantité de code qui n'est pas testé (Mutations) + +![alt text](assets/bp-20-yoni-goldberg-mutation-testing.jpeg "Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations)") + +
    + +

    + +## ⚪ ️4.4 Éviter les problèmes dans le code de test avec les Test linters + +:white_check_mark: **À faire:** Un groupe de plugins ESLint ont été développés spécifiquement pour inspecter le code de test et détecter les problèmes. Par exemple, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) t'avertiras lorsqu'un test est écrit à un niveau global (pas un enfant d'une déclaration describe()) ou lorsque les tests sont [sautés](https://mochajs.org/#inclusive-tests), ce qui peut conduire à une fausse croyance que les tests passent. De façon similaire, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) peut avertir lorsqu'un test n'a pas d'assertion (ne vérifie rien) +
    + +❌ **Autrement:** Voir 90% de couverture de code et 100% de tests verts fera apparaître un grand sourire sur ton visage, jusqu'à ce que tu réalises que de nombreux tests ne vérifient rien et que plusieurs suites de tests ont été sautées. Avec un peu de chance, tu n'as rien déployé basé sur de fausses observations + +
    +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: Un cas de test plein d'erreur, heuresement toutes détectés par les Linters + +```javascript +describe("Too short description", () => { + const userToken = userService.getDefaultToken() // *error:no-setup-in-describe, use hooks (sparingly) instead + it("Some description", () => {});//* error: valid-test-description. Must include the word "Should" + at least 5 words +}); + +it.skip("Test name", () => {// *error:no-skipped-tests, error:error:no-global-tests. Put tests only under describe or suite + expect("somevalue"); // error:no-assert +}); + +it("Test name", () => {*//error:no-identical-title. Assign unique titles to tests +}); +``` + +
    + +

    + +# Section 5️⃣: CI et autres mesures de qualité + +

    + +## ⚪ ️ 5.1 Enrichir ses linter et annuler les builds qui ont des problèmes de lint + +:white_check_mark: **À faire:** Les linters sont un bonus gratuit, avec 5 minutes de configurations, tu as gratuitement un auto-pilote qui surveille ton code et repère les problèmes pendant que tu tapes. Les jours où les linters étaient réservés à l'esthétique sont terminés (pas de point-virgules!). De nos jours, les linters peuvent détecter des problèmes sérieux comme des erreurs qui ne sont pas thrown correctement et les pertes d'informations. En plus de ta liste de règles basiques (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), considère d'ajouter des linters spécialisés comme [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) qui peux détecter les tests sans assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) qui détecte les promesses qui ne se resolvent pas (le code ne va jamais continuer), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) qui peut découvrir les regex qui peuvent être utilisé pour des attaques DOS, et [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) qui est capable de t'indiquer lorsque le code utilise une méthode de librairie qui fait partie des méthodes du cœur V8 comme Lodash.\_map(...) + +
    + +❌ **Autrement:** Imagine un jour de pluie ou ta production n'arrête pas de crasher mais les logs ne montrent aucune stack trace d'erreur. Qu'est-ce qu'il s'est passé ? Ton code a malencontreusement émis un objet qui n'était pas une erreur et la stack trace a été perdu, une bonne raison de se taper la tête contre les murs. 5 minutes de configuration d'un linter pourraient permettre de détecter cette erreur et sauver la journée + +
    + +
    Exemple de code + +
    + +### :thumbsdown: Exemple d'anti pattern: Le mauvais objet d'erreur est émit par erreur, aucune stack-trace ne va apparaitre pour cette erreur. Heuresement, ESLint catch le prochain beug de production + +![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug") + +
    + +

    + +## ⚪ ️ 5.2 Raccourcir la boucle de retours avec du CI local pour les développeurs + +:white_check_mark: **À faire:** Tu utilises un outil de CI avec une bonne inspection de qualité comme des tests, du linting, des checks de vulnérabilités, etc ? Aide les développeurs à lancer également cette pipeline en local pour solliciter un retour instantané et raccourcir la [boucle de feedback](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Pourquoi ? Un processus de tests efficace constitue de nombreuses boucles itératives: (1) essai -> (2) retours -> (3) refactoriser. Plus le retour est rapide, plus le développeur peut effectuer d'itérations d'améliorations par modules et perfectionner le résultat. D'un autre côté, lorsque les retours sont lent à arriver, moins d'améliorations peuvent être effectuées au sein d'une journée, l'équipe peut être déjà passée à un autre sujet/tache/module et peut ne pas être prête a affiner ce module. + +En pratique, certains fournisseurs de CI (Exemple: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) autorisent le lancement de la pipeline en local. Certains outils commerciaux comme [wallaby fournissent des informations de valeur et des tests](https://wallabyjs.com/) pendant que le développeur prototype (pas d'affiliation). Alternativement, tu peux simplement ajouter un script npm au package.json qui lance toute les commandes de qualités (e.g. tests, lint, vulnérabilités) - utilise des outils comme [concurrently](https://www.npmjs.com/package/concurrently) pour la parallélisation et des code de retour différents de 0 si l'un des outils échoue. Maintenant le développeur peut juste lancer une commande - e.g. 'npm run quality' - pour recevoir un retour instantané. Envisage également d'annuler un commit si le contrôle de qualité ne passe pas en utilisant githook ([husky can help](https://github.com/typicode/husky)) +
    + +❌ **Autrement:** Quand les résultats de qualité arrivent le jour suivant le développement, les tests ne sont pas une partie fluide du développement mais plutôt une étape formelle après coup +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Script npm qui effectue une inspection de la qualité du code, tout est lancé en parallèle sur demande ou lorsque le développeur essaye de push du code + +```javascript +"scripts": { + "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", + "inspect:lint": "eslint .", + "inspect:vulnerabilities": "npm audit", + "inspect:license": "license-checker --failOn GPLv2", + "inspect:complexity": "plato .", + + "inspect:all": "concurrently -c \"bgBlue.bold,bgMagenta.bold,yellow\" \"npm:inspect:quick-testing\" \"npm:inspect:lint\" \"npm:inspect:vulnerabilities\" \"npm:inspect:license\"" + }, + "husky": { + "hooks": { + "precommit": "npm run inspect:all", + "prepush": "npm run inspect:all" + } +} + +``` + +
    + +

    + +## ⚪ ️5.3 Effectuer des tests e2e sur un vrai miroir de production + +:white_check_mark: **À faire:** Les tests end to end (E2E) sont le défi principal de chaque pipeline CI - créer un miroir éphémère de la production à la volée avec tous les services clouds lié peut être fastidieux et coûteux. Le jeu est de trouver le meilleur compromis: [Docker-compose](https://serverless.com/) permet de créer des environnement dockerisés isolés avec des containers identiques en utilisant un simple fichier text mais la technologie backend (e.g réseau, modèle de déploiement) est différent de la vrai production. Tu peux l'associer à [‘AWS Local’](https://github.com/localstack/localstack) pour travailler avec un stub des services AWS. Si tu es [serverless](https://serverless.com/), plusieurs frameworks comme serverless et [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) permettent l'invocation locale de code FaaS. + +Le large écosystème de Kubernetes doit encore formaliser un outil standard pour la mise en miroir locale et CI, bien que de nombreux nouveaux outils soient lancés fréquemment. Une des approches est de lancer un 'minimized-Kubernetes' en utilisant des outils comme [Minikube](https://kubernetes.io/docs/setup/minikube/) et [MicroK8s](https://microk8s.io/). Une autre approche est de tester avec un 'vrai-Kebernetes' distant, certains fournisseurs CI (e.g. [Codefresh](https://codefresh.io/)) ont une intégration native avec l'environnement Kubernetes et rendent simple le lancement d'une pipeline CI sur le vrai environnement, d'autres permettent d'exécuter des scripts custom sur le Kubernetes distant. +
    + +❌ **Autrement:** Utiliser des technologies différentes pour la production et pour les tests demande de maintenir deux modèles de déploiement et créer une séparation entre l'équipe dev et l'équipe ops. +
    + +
    Exemple de code + +
    + +### :clap: Exemple: Une pipeline CI qui génère un cluster Kubernetes à la volée ([Credit: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) + +
    deploy:
    stage: deploy
    image: registry.gitlab.com/gitlab-examples/kubernetes-deploy
    script:
    - ./configureCluster.sh $KUBE_CA_PEM_FILE $KUBE_URL $KUBE_TOKEN
    - kubectl create ns $NAMESPACE
    - kubectl create secret -n $NAMESPACE docker-registry gitlab-registry --docker-server="$CI_REGISTRY" --docker-username="$CI_REGISTRY_USER" --docker-password="$CI_REGISTRY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL"
    - mkdir .generated
    - echo "$CI_BUILD_REF_NAME-$CI_BUILD_REF"
    - sed -e "s/TAG/$CI_BUILD_REF_NAME-$CI_BUILD_REF/g" templates/deals.yaml | tee ".generated/deals.yaml"
    - kubectl apply --namespace $NAMESPACE -f .generated/deals.yaml
    - kubectl apply --namespace $NAMESPACE -f templates/my-sock-shop.yaml
    environment:
    name: test-for-ci
    + +
    + +

    + +## ⚪ ️5.4 Paralléliser l'exécution des tests + +:white_check_mark: **À faire:** Lorsque c'est fait correctement, les tests sont tes amis 24/7 en fournissant un retour quasi instantané. En pratique, exécuter 500 tests unitaires liés au processeur sur un seul thread peut prendre trop longtemps. Heureusement, les outils de tests et les plateformes CI moderne (comme [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) et [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) peuvent paralléliser les tests sur plusieurs processus et améliorer significativement le temps de retour. Certains fournisseurs CI font également de la parallélisation de tests à travers des containers (!) ce qui raccourcis encore plus la boucle de retour. Que ce soit localement sur plusieurs processus, ou sur un serveur Cloud avec plusieurs machines - paralléliser demande de garder les tests autonomes puisqu'ils peuvent tourner sur différents processus. + +❌ **Autrement:** Obtenir les résultats de tests 1h après avoir publié du nouveau code, pendant que tu es déjà en train de coder la fonctionnalité suivante, est une bonne recette pour rendre les tests moins pertinents +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: Mocha parallel & Jest distancent facilement le Mocha traditionnel grace à la parallélisation ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) + +![alt text](assets/bp-24-yonigoldberg-jest-parallel.png "Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization (Credit: JavaScript Test-Runners Benchmark)") + +
    + +

    + +## ⚪ ️5.5 Rester loin des problèmes légaux en utilisants des vérifications de license et de plagiat + +:white_check_mark: **À faire:** Les problèmes de licences et de plagiat ne sont probablement pas au centre de votre attention pour l'instant, mais pourquoi ne pas cocher également cette case en 10 minutes ? Plusieurs packages npm comme [license check](https://www.npmjs.com/package/license-checker) et [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commerciaux avec un essai gratuit) peuvent être facilement intégré dans ta pipeline CI et inspecter les problèmes tels que les dépendances avec des licences restrictives ou du code qui a été copié-collé à partir de Stack Overflow et qui violerai certains droits d'auteur + +❌ **Autrement:** Involontairement, les développeurs peuvent utiliser un package avec une license inapproprié, ou copier/coller du code commercial et tomber sur des problèmes légaux +
    + +
    Exemple de code + +
    + +### :clap: Bien faire les choses, exemple: + +```javascript +//install license-checker in your CI environment or also locally +npm install -g license-checker + +//ask it to scan all licenses and fail with exit code other than 0 if it found unauthorized license. The CI system should catch this failure and stop the build +license-checker --summary --failOn BSD + +``` + +
    + +![alt text](assets/bp-25-nodejs-licsense.png) + +
    + +

    + +## ⚪ ️5.6 Inspecter constamment les dépendences vulnérables + +:white_check_mark: **À faire:** Même les dépendances les plus réputées comme Express ont des vulnérabilités connues. Cela peut être apprivoisé facilement avec des outils de la communauté comme [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), ou des outils commerciaux comme [snyk](https://snyk.io/) (qui offre également une version de la communauté gratuite). Les deux peuvent être appelés depuis ton CI à chaque build + +❌ **Autrement:** Garder ton code exempt de vulnérabilités sans les outils appropriés demande de suivre constamment les publications en ligne à propos des nouvelles menaces. Plutôt fastidieux. + +
    + +
    Exemple de code + +
    + +### :clap: Exemple: Résultat d'audit Npm + +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + +

    + +## ⚪ ️5.7 Automatiser les mises-à-jour de dépendences + +:white_check_mark: **À faire:** L'introduction récente du package-lock.json par Yarn et npm à introduit un vrai défi (la route vers l'enfer est pavée de bonnes intentions) - par défaut maintenant, les packages ne sont pas mis à jour. Même une équipe qui lance plusieurs nouveaux déploiements avec 'npm install' & 'npm update' n'aura pas de nouvelles mise à jour. Cela conduit au mieux, à des dépendances à des packages de qualité inférieure, au pire à du code vulnérable. Les équipes dépendent maintenant de la bonne volonté et de la mémoire des développeur pour mettre à jour manuellement le package.json ou utiliser des outils [comme ncu](https://www.npmjs.com/package/npm-check-updates). Une méthode plus fiable pourrait être d'automatiser le processus de récupération des versions de dépendances les plus fiables, bien qu'il n'y ait pas de solutions miracle, il y a deux possibilités d'automatisation: + +(1) Le CI peut faire échouer les builds qui ont des dépendances obsolètes - en utilisant des outils comme [‘npm outdated’](https://docs.npmjs.com/cli/outdated) ou 'npm-check-updates (ncu)'. Faire ça forcera les développeurs à mettre à jour les dépendances. + +(2) Utiliser un outil commercial qui peut scanner le code et envoyer automatiquement une pull-request avec les dépendances mises à jour. La question intéressante restante est, quel devrait être la politique de mises à jour - Mettre à jour chaque patch génère trop de surcharge, mettre à jour juste après une release majeure peut introduire une version instable (de nombreuses vulnérabilités sont découverte dans les premiers jours après la release, [voir l'incident](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope). + +Une politique de mise à jour efficace peut autoriser une 'période d'acquisition' - laisser le code en retard par rapport à @latest pour quelque temps et versions avant de considérer la copie locale comme obsolète (e.g la version locale est 1.3.1 et la version du repo est 1.3.8) +
    + +❌ **Autrement:** Ta production utilisera des packages qui ont été taggé explicitement par leurs auteurs comme risquées + +
    + +
    Exemple de code + +
    + +### :clap: Exemple: [ncu](https://www.npmjs.com/package/npm-check-updates) peut être utilisé manuellement ou dans une pipeline CI pour détecter à quel point le code est en retard vis a vis des dernières versions + +![alt text](assets/bp-27-yoni-goldberg-npm.png "ncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions") + +
    + +

    + +## ⚪ ️ 5.8 Autres conseils CI, sans rapports avec Node + +:white_check_mark: **À faire:** Ce post se concentre sur les conseils de tests qui sont en lien avec, ou peuvent être illustrés, avec Node JS. Ce point, cependant, regroupe quelques conseils sans rapports avec Node qui sont bien connus + +
    1. Utilise une syntaxe déclarative. C'est la seule option pour la plupart des fournisseurs mais d'anciennes versions de Jenkins autorisent l'utilisation du code ou de l'UI
    2. Choisis un fournisseur qui a une intégration Docker native
    3. Échoue rapidement, lance les tests les plus rapides d'abord. Crée des 'tests de fumée' pour certaines étapes qui regroupe plusieurs inspections rapide (e.g liting, tests unitaires) et fourni des commentaires rapides à celui qui commit le code
    4. Facilite le parcours des informations de build, cela inclut les rapports de tests, de couverture, de mutation, les logs ..etc
    5. Crée plusieurs pipelines/jobs pour chaque événement, réutiliser les étapes entre eux. Par exemple, configure un job pour les commits de features sur une branche et un différent pour une PR sur master. Laisse chacun réutiliser la logique en utilisant des étapes partagées (la plupart des fournisseurs ont des mécanismes pour réutiliser le code)
    6. Ne mets jamais de secrets dans la déclaration du job, récupère-les depuis un secret store ou depuis les configurations du job
    7. Augmente explicitement la version dans un build de release, ou au moins vérifie que le développeur l'a fait
    8. Build une fois et effectue toute les inspections sur l'artefact de build (e.g. Docker image)
    9. Test dans un environnement éphémère qui ne change pas d'état entre les builds. Le cache des nodes peut être la seule exception
    +
    + +❌ **Autrement:** Tu rateras des années de sagesse + +

    + +## ⚪ ️ 5.9 Structure de build: Lancer les mêmes étapes CI sur plusieurs versions de Node + +:white_check_mark: **À faire:** Le contrôle de qualité est un jeu de hasard, plus tu couvres de terrain, plus tu as de chance de détecter les problèmes rapidement. Quand tu développes des packages réutilisable ou que tu lances une production avec plusieurs clients qui ont différentes configurations et versions de Node, le CI doit lancer la pipeline de tests sur toutes les configurations possible. Par exemple, imaginons qu'on utilise MySQL pour certains clients et Postgres pour d'autres - Certains fournisseurs CI supportent une fonctionnalitée appelée 'Matrix' qui permet de lancer les tests contre toutes les permutations de MySQL, Postgres et plusieurs versions de Node comme 8, 9 et 10. Cela peut se faire en utilisant seulement des configurations sans efforts supplémentaire (en considérant que tu as des tests ou d'autres contrôles de qualités). D'autres CIs qui ne supportent pas Matrix peuvent avoir des extensions qui permettent ça +
    + +❌ **Autrement:** Après avoir fait tout le travail d'écrire des tests, va-t-on laisser passer des bugs seulement à cause de problèmes de configurations ? + +
    + +
    Exemple de code + +
    + +### :clap: Exemple: Utiliser les définition de build de Travis (fournisseur CI) pour lancer le même test sur plusieurs versions de Node + +
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    +
    + +

    + +# L'équipe + +## Yoni Goldberg + +
    + +
    + +**Rôle:** Auteur + +**À propos:** Je suis un consultant indépendant qui travaille avec des entreprises Fortune 500 et des startups pour peaufiner leurs applications JS et Node.JS. Plus qu'aucun autre sujet, je suis fasciné par et vise à maîtriser l'art du test. Je suis aussi l'auteur de [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices) + +**📗 Cours en ligne:** Tu aimes ce guide et tu veux pousser tes compétences de test à l'extreme ? Pense à regarder mon cours complet [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +
    + +**Me suivre:** + +- [🐦 Twitter](https://twitter.com/goldbergyoni/) +- [📞 Contact](https://testjavascript.com/contact-2/) +- [✉️ Newsletter](https://testjavascript.com/newsletter//) + +
    +
    +
    + +## [Bruno Scheufler](https://github.com/BrunoScheufler) + +**Rôle:** Réviseur et conseiller technique + +A pris soin de revoir, améliorer, linter et peaufiner tout les textes + +**À propos:** Ingénieur web full-stack, passioné par Node.js et GraphQL + +
    +
    + +## [Ido Richter](https://github.com/idori) + +**Rôle:** Concept, design et bons conseils + +**À propos:** Un développeur front-end averti, expert CSS et maniaque des émojis + +## [Kyle Martin](https://github.com/js-kyle) + +**Rôle:** Aide à faire tourner ce projet, et revois les pratiques liés à la sécurité + +**À propos:** Aime travailler sur des projets Node.JS et la sécurité des applications web. + +## Contributors ✨ + +Merci à ces merveilleuses personnes qui ont contribué à ce repo! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Scott Davis

    🖋

    Adrien REDON

    🖋

    Stefano Magni

    🖋

    Yeoh Joer

    🖋

    Jhonny Moreira

    🖋

    Ian Germann

    🖋

    Hafez

    🖋

    Ruxandra Fediuc

    🖋

    Jack

    🖋

    Peter Carrero

    🖋

    Huhgawz

    🖋

    Haakon Borch

    🖋

    Jaime Mendoza

    🖋

    Cameron Dunford

    🖋

    John Gee

    🖋

    Aurelijus Rožėnas

    🖋

    Aaron

    🖋

    Tom Nagle

    🖋

    Yves yao

    🖋

    Userbit

    🖋

    Glaucia Lemos

    🚧

    koooge

    🖋

    Michal

    🖋

    roywalker

    🖋

    dangen

    🖋

    biesiadamich

    🖋

    Yanlin Jiang

    🖋

    sanguino

    🖋

    Morgan

    🖋

    Lukas Bischof

    ⚠️ 🖋

    JuanMa Ruiz

    🖋

    Luís Ângelo Rodrigues Jr.

    🖋

    José Fernández

    🖋

    Alejandro Gutierrez Barcenilla

    🖋

    Jason

    🖋

    Otavio Araujo

    ⚠️ 🖋

    Alex Ivanov

    🖋
    + + + + + diff --git a/readme-pl.md b/readme-pl.md index 3d64312e..eb217e2d 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -1,12 +1,14 @@ +## 🎊 Ogłoszenie - Kwiecień 2022: Właśnie ukazała się nowa edycja z 5 nowymi najlepszymi praktykami, wieloma przykładami kodu i 4 nowymi tłumaczeniami językowymi +
    # 👇 Powody dla których ten przewodnik może przenieść twoje umiejętności testowania na wyższy poziom
    -## 📗 45+ najlepszych praktyk: super kompleksowe i wyczerpujące +## 📗 50+ najlepszych praktyk: super kompleksowe i wyczerpujące Jest to przewodnik po niezawodności JavaScript i Node.js od A-Z. Podsumowuje i przygotowuje dla Ciebie dziesiątki najlepszych postów na blogu, książek i narzędzi dostępnych na rynku @@ -33,6 +35,12 @@ Zacznij od zrozumienia wszechobecnych praktyk testowania, które są podstawą k - 🇨🇳[Chinese](readme-zh-CN.md) - dzięki uprzejmości [Yves yao](https://github.com/yvesyao) - 🇰🇷[Korean](readme.kr.md) - dzięki uprzejmości [Rain Byun](https://github.com/ragubyun) - 🇵🇱[Polish](readme.pl.md) - dzięki uprzejmości [Michal Biesiada](https://github.com/mbiesiad) +- 🇪🇸[Spanish](readme-es.md) - dzięki uprzejmości [Miguel G. Sanguino](https://github.com/sanguino) +- 🇧🇷[Portuguese-BR](readme-pt-br.md) - dzięki uprzejmości [Iago Angelim Costa Cavalcante](https://github.com/iagocavalcante) , [Douglas Mariano Valero](https://github.com/DouglasMV) oraz [koooge](https://github.com/koooge) +- 🇫🇷[French](readme-fr.md) - dzięki uprzejmości [Mathilde El Mouktafi](https://github.com/mel-mouk) +- 🇯🇵[Japanese (draft)](https://github.com/yuichkun/javascript-testing-best-practices/blob/master/readme-jp.md) - dzięki uprzejmości [Yuichi Yogo](https://github.com/yuichkun) oraz [ryo](https://github.com/kawamataryo) +- 🇹🇼[Traditional Chinese](readme-zh-TW.md) - dzięki uprzejmości [Yubin Hsu](https://github.com/yubinTW) +- 🇺🇦[Ukrainian](readme-ua.md) - dzięki uprzejmości [Serhii Shramko](https://github.com/Shramkoweb) - Chcesz przetłumaczyć na swój język? Proszę skorzystaj z issue 💜

    @@ -41,7 +49,7 @@ Zacznij od zrozumienia wszechobecnych praktyk testowania, które są podstawą k #### [`Sekcja 0: Złota zasada`](#sekcja-0️⃣-złota-zasada) -Jedna rada, która inspiruje wszystkich innych (1 specjalny punkt) +Jedna rada, która inspiruje wszystkie inne (1 punkt specjalny) #### [`Sekcja 1: Anatomia testu`](#sekcja-1-anatomia-testu-1) @@ -49,7 +57,7 @@ Podstawa - konstruowanie czystych testów (12 wypunktowań) #### [`Sekcja 2: Backend`](#sekcja-2️⃣-backend-testing) -Pisanie backendu i wydajne testy Mikroserwisów (8 wypunktowań) +Pisanie backendu i wydajne testy mikroserwisów (13 wypunktowań) #### [`Sekcja 3: Frontend`](#sekcja-3️⃣-frontend-testing) @@ -57,7 +65,7 @@ Pisanie testów dla webowego interfejsu użytkownika, w tym testy komponentów i #### [`Sekcja 4: Pomiary skuteczności testów`](#sekcja-4%EF%B8%8F%E2%83%A3-pomiar-skuteczno%C5%9Bci-testu) -Watching the watchman - pomiar jakości testu (4 wypunktowania) +Pilnowanie strażnika - pomiar jakości testu (4 wypunktowania) #### [`Sekcja 5: Continuous Integration`](#sekcja-5️⃣-ci-oraz-inne-miary-jakości) @@ -72,7 +80,7 @@ Wytyczne dla CI w świecie JS (9 wypunktowań) ## ⚪️ 0 Złota zasada: Projektowanie dla lean testing :white_check_mark: **Opis:** -Testowany kod nie przypomina kodu produkcyjnego - zaprojektuj go tak, by był prosty, krótki, pozbawiony abstrakcji, płaski, przyjemny w pracy, lean. Trzeba spojrzeć na test i natychmiast uzyskać cel. +Kod testowy nie jest kodem produkcyjnym - zaprojektuj go tak, aby był krótki, śmiertelnie prosty, płaski i przyjemny w pracy. Należy spojrzeć na test i natychmiast uzyskać intencję. Nasz umysł jest przepełniony głównym kodem produkcyjnym, nie mamy 'przestrzeni roboczej' na dodatkową złożoność. Jeśli spróbujemy wcisnąć kolejny trudny kod do naszego słabego mózgu, spowolni to pracę zespołu, co działa wbrew temu, co testujemy. W praktyce wiele zespołów po prostu rezygnuje z testów. @@ -140,6 +148,11 @@ describe('Products Service', function() { +
    +
    © Credits & read-more + 1. Roy Osherove - Naming standards for unit tests +
    +

    ## ⚪ ️ 1.2 Struktura testów według wzorca AAA @@ -518,12 +531,12 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {

    -## ⚪ ️1.9 Unikaj globalnych test fixture i seeds, dodawaj dane na test +## ⚪ ️1.9 Kopiuj kod, ale tylko to, co niezbędne -:white_check_mark: **Opis:** Kierując się złotą zasadą (punkt 0), każdy test powinien dodawać i działać na swoim własnym zestawie wierszy BD, aby zapobiec sprzężeniu i łatwo uzasadnić przebieg testu. W rzeczywistości jest to często naruszane przez testerów, którzy zapełniają bazę danych danymi przed uruchomieniem testów ([znany również jako ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) w celu poprawy wydajności. Chociaż wydajność jest istotnym problemem - można ją złagodzić (patrz punkt „Testowanie komponentów”), jednak złożoność testów jest bardzo bolesnym smutkiem, który powinien rządzić innymi względami przez większość czasu. Praktycznie spraw, aby każdy przypadek testowy wyraźnie dodał potrzebne rekordy BD i działał tylko na tych rekordach. Jeśli wydajność stanie się kluczowym problemem - zrównoważony kompromis może przyjść w postaci inicjowania jedynego zestawu testów, które nie powodują mutacji danych (np. zapytania) +:white_check_mark: **Opis:** Dołącz wszystkie niezbędne szczegóły, które wpływają na wynik testu, ale nic więcej. Jako przykład rozważ test, który powinien rozłożyć na czynniki 100 wierszy wejściowego JSON - wklejanie tego w każdym teście jest nużące. Wyodrębnienie go na zewnątrz do transferFactory.getJSON() spowoduje, że test będzie niejasny. Bez danych trudno jest skorelować wynik testu z przyczyną („dlaczego ma zwracać status 400?”). Klasyczna książka wzorców x-unit nazwała ten wzorzec „tajemniczym gościem” - coś niewidocznego wpłynęło na wyniki naszych testów, nie wiemy co dokładnie. Możemy zrobić lepiej, wyodrębniając powtarzalne długie części na zewnątrz ORAZ wyraźnie wspomnij, które konkretne szczegóły mają znaczenie dla testu. Idąc z powyższym przykładem, test może przekazać parametry, które podkreślają to, co jest ważne: transferFactory.getJSON({sender: undefined}). W tym przykładzie czytelnik powinien natychmiast wywnioskować, że puste pole nadawcy jest powodem, dla którego test powinien oczekiwać błędu walidacji lub innego podobnego odpowiedniego wyniku.
    -❌ **W przeciwnym razie:** Niewiele testów kończy się niepowodzeniem, wdrożenie zostało przerwane, nasz zespół spędza teraz cenny czas, czy mamy błąd? Zbadajmy, och nie - wydaje się, że dwa testy mutowały te same dane seed +❌ **W przeciwnym razie:** Kopiowanie 500 wierszy JSON spowoduje, że Twoje testy nie będą mogły być konserwowane i będą nieczytelne. Wyniesienie wszystkiego na zewnątrz zakończy się niejasnymi testami, które są trudne do zrozumienia
    @@ -531,49 +544,46 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {
    -### :thumbsdown: Przykład antywzorca: testy nie są niezależne i polegają na pewnym globalnym hook do zasilania globalnych danych BD +### :thumbsdown: Przykład antywzorca: niepowodzenie testu jest niejasne, ponieważ cała przyczyna jest zewnętrzna i ukryta w ogromnym formacie JSON -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Przykłady z Mocha") ```javascript -before(() => { - //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework - await DB.AddSeedDataFromJson('seed.json'); -}); -it("When updating site name, get successful confirmation", async () => { - //I know that site name "portal" exists - I saw it in the seed files - const siteToUpdate = await SiteService.getSiteByName("Portal"); - const updateNameResult = await SiteService.changeName(siteToUpdate, "newName"); - expect(updateNameResult).to.be(true); -}); -it("When querying by site name, get the right site", async () => { - //I know that site name "portal" exists - I saw it in the seed files - const siteToCheck = await SiteService.getSiteByName("Portal"); - expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ -}); +test("When no credit, then the transfer is declined", async() => { + // Arrange + const transferRequest = testHelpers.factorMoneyTransfer() //get back 200 lines of JSON; + const transferServiceUnderTest = new TransferService(); + // Act + const transferResponse = await transferServiceUnderTest.transfer(transferRequest); + + // Assert + expect(transferResponse.status).toBe(409);// But why do we expect failure: All seems perfectly valid in the test 🤔 + }); ```
    -### :clap: Przykład robienia tego dobrze: Możemy pozostać w teście, każdy test działa na własny zestaw danych +### :clap: Przykład robienia tego prawidłowo: Test wskazuje, co jest przyczyną wyniku testu ```javascript -it("When updating site name, get successful confirmation", async () => { - //test is adding a fresh new records and acting on the records only - const siteUnderTest = await SiteService.addSite({ - name: "siteForUpdateTest" - }); - const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); +test("When no credit, then the transfer is declined ", async() => { + // Arrange + const transferRequest = testHelpers.factorMoneyTransfer({userCredit:100, transferAmount:200}) //obviously there is lack of credit + const transferServiceUnderTest = new TransferService({disallowOvercharge:true}); - expect(updateNameResult).to.be(true); -}); -``` + // Act + const transferResponse = await transferServiceUnderTest.transfer(transferRequest); + + // Assert + expect(transferResponse.status).toBe(409); // Obviously if the user has no credit it should fail + }); + ``` -
    +

    ## ⚪ ️ 1.10 Nie wychwytuj błędów, oczekuj ich @@ -767,6 +777,9 @@ Słowo ostrzeżenia: argument TDD w świecie oprogramowania ma typową fałszyw :white_check_mark: **Opis:** Każdy test jednostkowy obejmuje niewielką część aplikacji i jest to kosztowne, aby pokryć całość, podczas gdy kompleksowe testy z łatwością obejmują dużo gruntu, ale są niestabilne i wolniejsze, dlaczego nie zastosować zrównoważonego podejścia i napisać testy, które są większe niż testy jednostkowe, ale mniejsze niż testy kompleksowe? Testowanie komponentów to nieoceniona piosenka świata testowego - zapewniają to, co najlepsze z obu światów: rozsądną wydajność i możliwość zastosowania wzorców TDD + realistyczne i doskonałe pokrycie. Testy komponentów koncentrują się na mikroserwisowej ‘jednostce’, działają przeciwko interfejsowi API, nie mockują niczego, co należy do samego mikroserwisu (np. prawdziwa baza danych lub przynajmniej wersja tej bazy danych w pamięci), ale usuwają wszystko, co jest zewnętrzne, jak wywołania innych mikrousług. W ten sposób testujemy to, co wdrażamy, podchodzimy do aplikacji od zewnątrz do wewnątrz i zyskujemy dużą pewność w rozsądnym czasie. + +[Mamy pełny przewodnik, który jest poświęcony wyłącznie pisaniu testów komponentów we właściwy sposób](https://github.com/testjavascript/nodejs-integration-tests-best-practices) +
    ❌ **W przeciwnym razie:** Możesz spędzać długie dni na pisaniu testów jednostkowych, aby dowiedzieć się, że masz tylko 20% zasięgu systemu @@ -951,6 +964,164 @@ it("When updating site name, get successful confirmation", async () => { +
    + +## ⚪ ️2.8 Wybierz przejrzystą strategię czyszczenia danych: po wszystkim (zalecane) lub po każdym + +:white_check_mark: **Opis:** Moment, w którym testy czyszczą bazę danych, określa sposób pisania testów. Dwie najbardziej opłacalne opcje to czyszczenie po wszystkich testach i czyszczenie po każdym teście. Wybierając tę drugą opcję, czyszczenie po każdym teście gwarantuje czyste tabele i buduje wygodne korzyści testowe dla programisty. Na początku testu nie istnieją żadne inne rekordy, można mieć pewność, które dane są odpytywane, a nawet można pokusić się o zliczenie wierszy podczas asercji. Ma to poważne wady: podczas uruchamiania w trybie wieloprocesowym testy prawdopodobnie będą ze sobą kolidować. Podczas gdy proces-1 czyści tabele, w tej chwili proces-2 wysyła zapytania o dane i kończy się niepowodzeniem (ponieważ baza danych została nagle usunięta przez proces-1). Ponadto trudniej jest rozwiązywać problemy z testami zakończonymi niepowodzeniem — odwiedzenie bazy danych nie spowoduje wyświetlenia żadnych rekordów. + +Drugą opcją jest czyszczenie po zakończeniu wszystkich plików testowych (lub nawet codziennie!). Takie podejście oznacza, że ta sama baza danych z istniejącymi rekordami obsługuje wszystkie testy i procesy. Aby uniknąć nadepnięcia sobie nawzajem na palce, testy muszą dodawać i działać na określonych rekordach, które dodały. Chcesz sprawdzić, czy dodano jakiś rekord? Załóżmy, że istnieją inne tysiące rekordów i zapytaj o rekordy, które zostały dodane jawnie. Chcesz sprawdzić, czy rekord został usunięty? Nie można założyć, że tabela jest pusta, sprawdź, czy nie ma tam tego konkretnego rekordu. Ta technika przynosi kilka potężnych korzyści: działa natywnie w trybie wieloprocesowym, gdy programista chce zrozumieć, co się stało — dane są tam i nie są usuwane. Zwiększa również szansę na znalezienie błędów, ponieważ baza danych jest pełna rekordów i nie jest sztucznie opróżniona. [Zobacz pełną tabelę porównawczą tutaj](https://github.com/testjavascript/nodejs-integration-tests-best-practices/blob/master/graphics/db-clean-options.png). +
    + +❌ **W przeciwnym razie:** Bez strategii oddzielania rekordów lub czyszczenia — testy będą stąpać po sobie nawzajem; Korzystanie z transakcji będzie działać tylko w przypadku relacyjnych baz danych i może się skomplikować, gdy pojawią się transakcje wewnętrzne + +
    + +
    Przykłady kodu + +
    + +### :clap: Czyszczenie po WSZYSTKICH testach. Niekoniecznie po każdym uruchomieniu. Im więcej danych mamy w trakcie testów - tym bardziej przypomina to profity produkcyjne + +```javascript + // After-all clean up (recommended) +// global-teardown.js +module.exports = async () => { + // ... + if (Math.ceil(Math.random() * 10) === 10) { + await new OrderRepository().cleanup(); + } +}; +``` + +
    + +
    + +## ⚪ ️2.9 Odizoluj komponent od świata za pomocą interceptora HTTP + +:white_check_mark: **Opis:** Odizoluj testowany składnik, przechwytując wszystkie wychodzące żądania HTTP i dostarczając żądaną odpowiedź, aby interfejs HTTP API współpracownika nie został trafiony. Nock jest doskonałym narzędziem do tej misji, ponieważ zapewnia wygodną składnię do definiowania zachowania usług zewnętrznych. Izolacja jest koniecznością, aby zapobiec szumowi i spowolnieniu działania, ale przede wszystkim aby symulować różne scenariusze i reakcje. Dobry symulator lotu nie polega na malowaniu czystego błękitnego nieba, ale na sprowadzaniu bezpieczeństwa podczas burz i chaosu. Jest to wzmocnione w architekturze mikrousług, w której należy zawsze koncentrować się na jednym komponencie bez angażowania reszty świata. Chociaż możliwe jest symulowanie zachowania usługi zewnętrznej za pomocą dublowania testowego (mockowanie), lepiej nie dotykać wdrożonego kodu i działać na poziomie sieci, aby testy były czysto typu black-box. Wadą izolacji jest nie wykrywanie zmian w komponencie współpracownika i brak zrozumienia nieporozumień między dwiema usługami — pamiętaj, aby zrekompensować to za pomocą kilku testów kontraktowych lub E2E +
    + +❌ **W przeciwnym razie:** Niektóre usługi udostępniają wersję fake, która może zostać wdrożona lokalnie przez rozmówcę, zwykle za pomocą Dockera — ułatwi to konfigurację i zwiększy wydajność, ale nie pomoże w symulowaniu różnych odpowiedzi; Niektóre usługi zapewniają środowisko „piaskownicy”, więc prawdziwa usługa zostaje trafiona, ale nie są wyzwalane żadne koszty ani skutki uboczne — zmniejszy to szum związany z konfiguracją usługi innej firmy, ale nie pozwoli również na symulację scenariuszy + +
    + +
    Przykłady kodu + +
    + +### :clap: Zapobieganie połączeniom sieciowym z komponentami zewnętrznymi umożliwia tworzenie scenariuszy symulacji i minimalizowanie szumu + +```javascript +// Intercept requests for 3rd party APIs and return a predefined response +beforeEach(() => { + nock('http://localhost/user/').get(`/1`).reply(200, { + id: 1, + name: 'John', + }); +}); +``` +
    +
    + +## ⚪ ️2.10 Przetestuj schemat odpowiedzi, głównie w przypadku pól generowanych automatycznie + +:white_check_mark: **Opis:** Gdy nie można potwierdzić konkretnych danych, sprawdź istnienie i typy pól obowiązkowych. Czasami odpowiedź zawiera ważne pola z danymi dynamicznymi, których nie można przewidzieć podczas pisania testu, takie jak daty i rosnące liczby. Jeśli kontrakt API obiecuje, że te pola nie będą miały wartości null i będą zawierać odpowiednie typy, konieczne jest przetestowanie tego. Większość bibliotek asercji obsługuje typy sprawdzania. Jeśli odpowiedź jest mała, sprawdź dane zwrotne i wpisz razem w ramach tego samego potwierdzenia (patrz przykład kodu). Jeszcze jedną opcją jest zweryfikowanie całej odpowiedzi z dokumentem OpenAPI (Swagger). Większość programów uruchamiających testy ma rozszerzenia społeczności, które weryfikują odpowiedzi API w oparciu o ich dokumentację. + + +
    + +❌ **W przeciwnym razie:** Chociaż wywołujący kod/API opiera się na jakimś polu z danymi dynamicznymi (np. ID, data), nie wróci i nie zerwie umowy + +
    + +
    Przykłady kodu + +
    + +### :clap: Zapewnienie, że pola z wartością dynamiczną istnieją i mają właściwy typ + +```javascript + test('When adding a new valid order, Then should get back approval with 200 response', async () => { + // ... + //Assert + expect(receivedAPIResponse).toMatchObject({ + status: 200, + data: { + id: expect.any(Number), // Any number satisfies this test + mode: 'approved', + }, + }); +}); +``` + +
    + +
    + +## ⚪ ️2.11 Sprawdź corner cases integracji i chaos + +:white_check_mark: **Opis:** Sprawdzając integracje wyjdź poza happy i sad paths. Sprawdź nie tylko błędne odpowiedzi (np. błąd HTTP 500), ale także anomalie na poziomie sieci, takie jak powolne i przekroczone limity czasu odpowiedzi. Udowodni to, że kod jest odporny i może obsługiwać różne scenariusze sieciowe, takie jak obieranie właściwej ścieżki po przekroczeniu limitu czasu, brak problemów z prądem i czy zawiera wyłącznik umożliwiający ponowną próbę. Renomowane narzędzia przechwytujące mogą z łatwością symulować różne zachowania sieciowe, takie jak gorączkowa usługa, która czasami kończy się niepowodzeniem. Może nawet zdać sobie sprawę, kiedy domyślna wartość limitu czasu klienta HTTP jest dłuższa niż symulowany czas odpowiedzi i natychmiast zgłosić wyjątek limitu czasu, bez czekania + + +
    + +❌ **W przeciwnym razie:** Wszystkie twoje testy przechodzą pomyślnie, tylko produkcja ulegnie awarii lub nie będzie poprawnie zgłaszać błędów, gdy strony trzecie wyślą wyjątkowe odpowiedzi + +
    + +
    Przykłady kodu + +
    + +### :clap: Zapewnienie, że w przypadku awarii sieci wyłącznik może uratować sytuację + +```javascript + test('When users service replies with 503 once and retry mechanism is applied, then an order is added successfully', async () => { + //Arrange + nock.removeInterceptor(userServiceNock.interceptors[0]) + nock('http://localhost/user/') + .get('/1') + .reply(503, undefined, { 'Retry-After': 100 }); + nock('http://localhost/user/') + .get('/1') + .reply(200); + const orderToAdd = { + userId: 1, + productId: 2, + mode: 'approved', + }; + + //Act + const response = await axiosAPIClient.post('/order', orderToAdd); + + //Assert + expect(response.status).toBe(200); +}); +``` + +
    + +
    + + +## ⚪ ️2.12 Przetestuj pięć potencjalnych wyników + +:white_check_mark: **Opis:** Planując testy, rozważ uwzględnienie pięciu typowych wyników przepływu. Kiedy twój test uruchamia jakąś akcję (np. wywołanie API), dzieje się reakcja, dzieje się coś znaczącego i wzywa do testowania. Pamiętaj, że nie dbamy o to, jak wszystko działa. Skupiamy się na wynikach, rzeczach, które są zauważalne z zewnątrz i mogą mieć wpływ na użytkownika. Te wyniki/reakcje można podzielić na 5 kategorii: + +• Odpowiedź — test wywołuje akcję (np. przez API) i otrzymuje odpowiedź. Teraz zajmuje się sprawdzaniem poprawności danych odpowiedzi, schematu i statusu HTTP + +• Nowy stan — po wywołaniu akcji niektóre **publicznie dostępne** dane są prawdopodobnie modyfikowane + +• Wywołania zewnętrzne — po wywołaniu akcji aplikacja może wywołać składnik zewnętrzny za pośrednictwem protokołu HTTP lub dowolnego innego transportu. Na przykład połączenie w celu wysłania SMS-a, e-maila lub obciążenia karty kredytowej + +• Kolejki wiadomości — wynikiem przepływu może być wiadomość w kolejce + +• Obserwowalność — Niektóre rzeczy muszą być monitorowane, na przykład błędy lub niezwykłe wydarzenia biznesowe. Gdy transakcja się nie powiedzie, oczekujemy nie tylko właściwej reakcji, ale także poprawnej obsługi błędów i prawidłowego logowania/metryk. Informacje te trafiają bezpośrednio do bardzo ważnego użytkownika — użytkownika Ops (tj. produkcyjnego SRE/administratora) + + +

    # Sekcja 3️⃣: Frontend Testing @@ -1001,7 +1172,7 @@ test("When flagging to show only VIP, should display only VIP members", () => { const { getAllByTestId } = render(); // Assert - Mix UI & data in assertion - expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); + expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); }); ``` @@ -1031,8 +1202,8 @@ test("When flagging to show only VIP, should display only VIP members", () => { // the markup code (part of React component)

    - {value} - + {value} +

    ``` @@ -1267,7 +1438,7 @@ export default function ProductsList() { fetchProducts(); }, []); - return products ?
    {products}
    :
    No products
    ; + return products ?
    {products}
    :
    No products
    ; } // test @@ -1918,38 +2089,59 @@ Podziękowania dla tych wspaniałych ludzi, którzy przyczynili się do tego rep - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + + + +

    Scott Davis

    🖋

    Adrien REDON

    🖋

    Stefano Magni

    🖋

    Yeoh Joer

    🖋

    Jhonny Moreira

    🖋

    Ian Germann

    🖋

    Hafez

    🖋

    Scott Davis

    🖋

    Adrien REDON

    🖋

    Stefano Magni

    🖋

    Yeoh Joer

    🖋

    Jhonny Moreira

    🖋

    Ian Germann

    🖋

    Hafez

    🖋

    Ruxandra Fediuc

    🖋

    Jack

    🖋

    Peter Carrero

    🖋

    Huhgawz

    🖋

    Haakon Borch

    🖋

    Jaime Mendoza

    🖋

    Cameron Dunford

    🖋

    John Gee

    🖋

    Aurelijus Rožėnas

    🖋

    Aaron

    🖋

    Tom Nagle

    🖋

    Yves yao

    🖋

    Userbit

    🖋

    Glaucia Lemos

    🚧

    Ruxandra Fediuc

    🖋

    Jack

    🖋

    Peter Carrero

    🖋

    Huhgawz

    🖋

    Haakon Borch

    🖋

    Jaime Mendoza

    🖋

    Cameron Dunford

    🖋

    koooge

    🖋

    Michal

    🖋

    roywalker

    🖋

    dangen

    🖋

    biesiadamich

    🖋

    Yanlin Jiang

    🖋

    sanguino

    🖋

    John Gee

    🖋

    Aurelijus Rožėnas

    🖋

    Aaron

    🖋

    Tom Nagle

    🖋

    Yves yao

    🖋

    Userbit

    🖋

    Glaucia Lemos

    🚧

    Morgan

    🖋

    Lukas Bischof

    ⚠️ 🖋

    JuanMa Ruiz

    🖋

    Luís Ângelo Rodrigues Jr.

    🖋

    José Fernández

    🖋

    Alejandro Gutierrez Barcenilla

    🖋

    Jason

    🖋

    koooge

    🖋

    Otavio Araujo

    ⚠️ 🖋

    Alex Ivanov

    🖋

    Yiqiao Xu

    🖋

    YuBin, Hsu

    🌍 💻
    - + diff --git a/readme-pr-fr.md b/readme-pr-fr.md new file mode 100644 index 00000000..20831337 --- /dev/null +++ b/readme-pr-fr.md @@ -0,0 +1,2055 @@ + + + +
    + +# 👇 چرا این راهنما می تواند مهارت های آزمایشی شما را به سطح بعدی برساند + +
    + +## 📗 50+ تا از بهترین شیوه ها: فوق العاده جامع و جامع + +این راهنمای قابلیت اطمینان JavaScript & Node.js از A-Z است. ده‌ها مورد از بهترین پست‌ها، کتاب‌ها و ابزارهایی که بازار ارائه می‌دهد را برای شما خلاصه و گردآوری می‌کند. + +## 🚢 پیشرفته: 10000 مایل فراتر از اصول اولیه می رود + +به سفری بروید که بسیار فراتر از اصول اولیه به موضوعات پیشرفته ای مانند آزمایش در تولید، آزمایش جهش، آزمایش مبتنی بر دارایی و بسیاری از ابزارهای استراتژیک و حرفه ای دیگر سفر می کند. اگر تمام کلمات این راهنما را بخوانید، احتمالاً مهارت های تست زنی شما بسیار بالاتر از حد متوسط خواهد بود + +## 🌐 Full-stack: front, backend, CI, هر چیزی + +با درک روش‌های تست همه جا حاضر که پایه و اساس هر سطح برنامه هستند، شروع کنید. سپس، به حوزه انتخابی خود بپردازید: frontend/UI، backend، CI یا شاید همه آنها؟ + +
    + +### Yoni Goldberg نوشته شده توسط + +- JavaScript & Node.js مشاوره +- 📗 [تست کردن Node.js & JavaScript از A تا Z](https://www.testjavascript.com) - دوره جامع آنلاین من با بیش از [7 ساعت ویدیو](https://www.testjavascript.com), 14 انواع تست و بیش از 40 بهترین روش +- [من را در توییتر دنبال کنید](https://twitter.com/goldbergyoni/) +- [کارگاه بعدی: ورونا، ایتالیا 🇮🇹، 20 آوریل](https://2022.jsday.it/workshop/nodejs_testing.html) + +
    + + +## `فهرست مطالب` + +#### [`بخش 0: قاعده طلایی`](#section-0️⃣-the-golden-rule) + +یک توصیه واحد که الهام بخش بقیه است (1 گلوله ویژه) + +#### [`بخش 1: آناتومی تست`](#section-1-the-test-anatomy-1) + +فونداسیون - تست های تمیز ساختاری (12 گلوله) + +#### [`Backend :بخش 2`](#section-2️⃣-backend-testing) + +نوشتن تست های backend و میکروسرویس به طور موثر (13 گلوله) + +#### [`Frontend :بخش 3`](#section-3️⃣-frontend-testing) + +نوشتن تست ها برای رابط کاربری وب شامل تست های کامپوننت و E2E +(11 گلوله) + +#### [`بخش 4: اندازه گیری اثربخشی آزمون ها`](#section-4️⃣-measuring-test-effectiveness) + +تماشای نگهبان - اندازه گیری کیفیت تست (4 گلوله) + +#### [`بخش 5: یکپارچه سازی مداوم`](#section-5️⃣-ci-and-other-quality-measures) + +دستورالعمل های CI در دنیای JS (9 گلوله) + +

    + +# بخش 0️⃣: قاعده طلایی + +
    + +## ⚪️ 0 قانون طلایی: طراحی برای تست ناب + +:white_check_mark: **انجام دادن:** + +تست کردن کد صرفا نوشتن کد نیست - آن را طوری طراحی کنید که کوتاه، ساده، مسطح، و کار کردن با آن لذت بخش باشد. باید به یک تست نگاه کرد و فوراً هدف را دریافت کرد. + +ببینید، ذهن ما از قبل مشغول کار اصلی ما - نوشتن کد است. برای پیچیدگی اضافی، هیچ فضایی وجود ندارد. اگر بخواهیم یک سیستم sus-system دیگر را در مغز ضعیف خود بفشاریم، سرعت تیم را کاهش می‌دهد که برخلاف دلیلی که ما تست می‌کنیم عمل می‌کند. عملاً اینجاست که بسیاری از تیم‌ها تست را رها می‌کنند. + +تست ها فرصتی برای چیز دیگری هستند - یک دستیار دوستانه، کمک خلبان، که ارزش زیادی برای یک سرمایه گذاری کوچک ارائه می دهد. علم به ما می گوید که ما دو سیستم مغزی داریم: سیستم 1 برای فعالیت های بی دردسر مانند رانندگی ماشین در یک جاده خالی و سیستم 2 که برای عملیات پیچیده و آگاهانه مانند حل یک معادله ریاضی استفاده می شود. تست خود را برای سیستم 1 طراحی کنید، زمانی که به کد تست نگاه می کنید، باید به راحتی یک سند HTML را تغییر دهید، نه مانند حل 2X (17 × 24). + +این را می توان با تکنیک های انتخابی انتخاب گیلاس، ابزارها و اهداف آزمایشی که مقرون به صرفه هستند و ROI عالی ارائه می دهند، به دست آورد. فقط به اندازه نیاز تست کنید، سعی کنید آن را زیرک نگه دارید، گاهی اوقات حتی ارزش آن را دارد که برخی از تست ها را کنار بگذارید و قابلیت اطمینان را برای چابکی و سادگی معامله کنید. + +![alt text](/assets/headspace.png "We have no head room for additional complexity") + +بیشتر توصیه های زیر مشتقات این اصل است. + +### آماده برای شروع؟ + +

    + +# بخش 1: آناتومی تست + +
    + +## ⚪ ️ 1.1 شامل 3 قسمت در هر نام آزمون + +:white_check_mark: **انجام دادن:** یک گزارش آزمایشی باید بگوید که آیا بازبینی برنامه فعلی الزامات افرادی را که لزوماً با کد آشنا نیستند برآورده می کند: تست کننده ، مهندس DevOps که در حال استقرار است و شما آینده دو سال بعد. اگر آزمون ها در سطح الزامات صحبت کنند و شامل 3 بخش باشند، می توان به بهترین وجه به این امر دست یافت: + +(1) چه چیزی در حال آزمایش است؟ به عنوان مثال، متد ProductsService.addNewProduct + +(2) در چه شرایط و سناریویی؟ برای مثال هیچ قیمتی به متد داده نمی شود + +(3) نتیجه ی قابل انتظار چیست؟ به عنوان مثال، محصول جدید تایید نشده است + +
    + +❌ **در غیر این صورت:** یک استقرار به‌تازگی ناموفق بود، تست با نام «افزودن محصول» ناموفق بود. آیا این به شما می گوید که دقیقاً چه چیزی خراب است؟ + +
    + +**👇 توجه داشته باشید:** هر گلوله دارای نمونه کد و گاهی اوقات یک تصویر تصویری نیز می باشد. برای گسترش کلیک کنید +
    + +
    نمونه کد + +
    + +### :clap: مثال درست: نام تستی که از 3 قسمت تشکیل شده است + +![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Using Mocha to illustrate the idea") + +```javascript +//1. واحد در حال تست +describe('خدمات محصولات', function() { + describe('افزودن محصول جدید', function() { + //2. سناریو و 3. انتظار + it('هنگامی که قیمتی مشخص نشده است، وضعیت محصول در انتظار تایید است', ()=> { + const newProduct = new ProductService().add(...); + expect(newProduct.status).to.equal('pendingApproval'); + }); + }); +}); + +``` + +
    + +### :clap: مثال درست: نام آزمایشی که از 3 قسمت تشکیل شده است + +![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") + +
    + + +## ⚪ ️ 1.2 تست های ساختار با الگوی AAA + +:white_check_mark: **انجام دادن:** ساختار تست های خود را با 3 بخش به خوبی جدا شده مفدار دهی کنید، اجرا کنید و مقایسه کنید (AAA). پیروی از این ساختار تضمین می کند که خواننده هیچ CPU مغزی را برای درک برنامه آزمایشی خرج نمی کند: + +اول A - مفدار دادن: تمام کدهای راه اندازی برای رساندن سیستم به سناریویی که تست هدف آن شبیه سازی است. این ممکن است شامل نمونه سازی واحد در حال آزمایش سازنده، اضافه کردن رکوردهای DB، mocking/stubbing اشیا و هر کد آماده سازی دیگر باشد. + +دوم A - اجرا: واحد تحت تست را اجرا کنید. معمولا 1 خط کد + +سوم A - مفایسه: اطمینان حاصل کنید که ارزش دریافتی انتظارات را برآورده می کند. معمولا 1 خط کد + +
    + +❌ **در غیر این صورت:** نه تنها ساعت ها برای درک کد اصلی وقت می گذارید، بلکه چیزی که باید ساده ترین قسمت روز باشد (تست) مغز شما را کش می دهد. + +
    + +
    نمونه کد + +
    + +### :clap: مثال درست: تستی که با الگوی AAA ساختار یافته است + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +describe("طبقه بندی مشتری", () => { + test("هنگامی که مشتری بیش از 500 دلار هزینه کرد، باید به عنوان حق بیمه طبقه بندی شود", () => { + //مفدار دهی کردن + const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; + const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); + + //اجرا کردن + const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); + + //مفابسه کردن + expect(receivedClassification).toMatch("premium"); + }); +}); +``` + +
    + +### :thumbsdown: مثال ضد الگو: بدون جدایی، یک انبوه، سخت تر برای تفسیر + +```javascript +test("باید به عنوان حق بیمه طبقه بندی شود", () => { + const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; + const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); + const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); + expect(receivedClassification).toMatch("premium"); +}); +``` + +
    + +

    + +## ⚪ ️1.3 انتظارات را به زبان محصول توصیف کنید: از ادعاهای سبک BDD استفاده کنید + +:white_check_mark: **انجام دادن:** کدنویسی تست‌های خود به سبک بیانی به خواننده این امکان را می‌دهد تا بدون صرف حتی یک چرخه مغز - CPU، فوراً به نتیجه برسد. وقتی کدهای ضروری را می نویسید که مملو از منطق شرطی است، خواننده مجبور می شود چرخه های مغز - CPU بیشتری اعمال کند. در این صورت، انتظارات را به زبانی شبیه به انسان، به سبک BDD اعلانی با استفاده از «انتظار» یا «باید» و بدون استفاده از کد سفارشی کدنویسی کنید. اگر Chai & Jest شامل ادعای مورد نظر نیست و بسیار قابل تکرار است، در نظر بگیرید [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) یا نوشتن یک [پلاگین Chai سفارشی](https://www.chaijs.com/guide/plugins/) +
    + +❌ **در غیر این صورت:** تیم تست های کمتری می نویسد و تست های مزاحم را با .skip تزئین می کند() + +
    + +
    نمونه کد
    + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +### :thumbsdown: مثال ضد الگو: خواننده فقط برای دریافت داستان تستی که باید کدهای نه چندان کوتاه و ضروری را مرور کند. + +```javascript +test("هنگام درخواست ادمین، مطمئن شوید که در نتایج فقط مدیران سفارش داده شده هستند", () => { + //با فرض اینکه ما دو ادمین "admin1"، "admin2" و "user1" را به اینجا اضافه کرده ایم. + const allAdmins = getUsers({ adminOnly: true }); + + let admin1Found, + adming2Found = false; + + allAdmins.forEach(aSingleUser => { + if (aSingleUser === "user1") { + assert.notEqual(aSingleUser, "user1", "A user was found and not admin"); + } + if (aSingleUser === "admin1") { + admin1Found = true; + } + if (aSingleUser === "admin2") { + admin2Found = true; + } + }); + + if (!admin1Found || !admin2Found) { + throw new Error("Not all admins were returned"); + } +}); +``` + +
    + +### :clap: مثال درست: مرور تست اعلانی زیر یک نسیم است + +```javascript +it("When asking for an admin, ensure only ordered admins in results", () => { + //با فرض اینکه ما به اینجا دو ادمین اضافه کرده ایم + const allAdmins = getUsers({ adminOnly: true }); + + expect(allAdmins) + .to.include.ordered.members(["admin1", "admin2"]) + .but.not.include.ordered.members(["user1"]); +}); +``` + +
    + +

    + +## ⚪ ️ 1.4 به تست جعبه سیاه پایبند باشید: فقط متد های عمومی را آزمایش کنید + +:white_check_mark: **اتجام دادن:** آزمایش قطعات داخلی تقریباً هیچ هزینه زیادی را به همراه ندارد. اگر کد/API شما نتایج درستی را ارائه می‌دهد، آیا واقعاً باید 3 ساعت آینده خود را برای آزمایش نحوه عملکرد داخلی آن سرمایه‌گذاری کنید و سپس این تست‌های شکننده را حفظ کنید؟ هر زمان که یک رفتار عمومی بررسی می‌شود، پیاده‌سازی خصوصی نیز به طور ضمنی آزمایش می‌شود و آزمایش‌های شما تنها در صورت وجود مشکل خاصی (به عنوان مثال خروجی اشتباه) شکسته می‌شوند. این رویکرد به عنوان "آزمایش رفتار" نیز نامیده می شود. از طرف دیگر، اگر قطعات داخلی را آزمایش کنید (رویکرد جعبه سفید) - تمرکز شما از برنامه ریزی نتیجه کامپوننت به جزئیات دقیق تغییر می کند و ممکن است تست شما به دلیل اصلاح کننده های جزئی کد شکسته شود، اگرچه نتایج خوب هستند - این به طور چشمگیری تعمیر و نگهداری را افزایش می دهد. بار +
    + +❌ **در غیر این صورت:** تست های شما مانند [پسری هست که عین گرگ فریاد می زند](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): فریاد زدن با فریادهای مثبت کاذب (مثلاً، یک آزمایش به دلیل تغییر نام یک متغیر خصوصی ناموفق بود). جای تعجب نیست که مردم به زودی اعلان‌های CI را نادیده می‌گیرند تا اینکه روزی یک باگ واقعی نادیده گرفته شود… + +
    +
    نمونه کد + +
    + +### :thumbsdown: مثال ضد الگو: یک مورد تستی بدون هیچ دلیل موجهی قطعات داخلی را تست می کند + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") + +```javascript +class ProductService { + //این متذ فقط در داخل استفاده می شود + //تغییر این نام باعث می شود که آزمون ها با شکست مواجه شوند + calculateVATAdd(priceWithoutVAT) { + return { finalPrice: priceWithoutVAT * 1.2 }; + //تغییر فرمت نتیجه یا نام کلید بالا باعث شکست تست ها می شود + } + //متد عمومی + getPrice(productId) { + const desiredProduct = DB.getProduct(productId); + finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice; + return finalPrice; + } +} + +it("تست جعبه سفید: هنگامی که روش های داخلی 0 vat دریافت می کنند، 0 پاسخ می دهد", async () => { + //هیچ الزامی برای اجازه دادن به کاربران برای محاسبه مالیات بر ارزش افزوده وجود ندارد، فقط قیمت نهایی را نشان می دهد. با این وجود، ما به دروغ اصرار داریم که درونی کلاس را آزمایش کنیم + expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0); +}); +``` + +
    + +

    + +## ⚪ ️ ️1.5 دو برابر تست مناسب را انتخاب کنید: از تمسخر به نفع خرد و جاسوس خودداری کنید + +:white_check_mark: **انجام دادن:** تست های دوبل یک شر ضروری هستند زیرا با اجزای داخلی برنامه همراه هستند، اما برخی از آنها ارزش بسیار زیادی را ارائه می دهند ([در اینجا یک یادآوری در مورد تست دوبل بخوانید: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). + +قبل از استفاده از تست دوبل، یک سوال بسیار ساده بپرسید: آیا از آن برای تست عملکردی که در سند الزامات ظاهر می شود یا می تواند ظاهر شود استفاده کنم؟ اگر نه، بوی تست جعبه سفید است. + +به عنوان مثال، اگر می‌خواهید آزمایش کنید که برنامه‌تان در هنگام قطع سرویس پرداخت، رفتار معقولی دارد، ممکن است سرویس پرداخت را خاموش کنید و مقداری «بدون پاسخ» را فعال کنید تا مطمئن شوید که واحد مورد آزمایش مقدار مناسب را برمی‌گرداند. این رفتار / پاسخ / نتیجه برنامه ما را تحت سناریوهای خاصی بررسی می کند. همچنین ممکن است از جاسوسی استفاده کنید تا ادعا کنید که ایمیلی زمانی ارسال شده است که آن سرویس از کار افتاده است———این دوباره یک بررسی رفتاری است که احتمالاً در سند الزامات ظاهر می‌شود («اگر پرداخت امکان ذخیره نشد» رایانامه ارسال کنید. از طرف دیگر، اگر سرویس پرداخت را مسخره می‌کنید و مطمئن می‌شوید که با انواع جاوا اسکریپت مناسب فراخوانی شده است — آزمایش شما روی چیزهای داخلی متمرکز است که هیچ ربطی به عملکرد برنامه ندارند و احتمالاً اغلب تغییر می‌کنند. +
    + +❌ **در غیر این صورت:** هر گونه بازسازي كد، جست و جوي تمامي ساختگي هاي موجود در كد و به روز كردن آن را الزامي مي كند. تست ها به جای یک دوست مفید تبدیل به یک بار می شوند + +
    + +
    نمونه کد + +
    + +### :thumbsdown: مثال ضد الگو: mock ها بر روی داخلی تمرکز می کنند + +![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg "Examples with Sinon") + +```javascript +it("هنگامی که یک محصول معتبر در شرف حذف است، مطمئن شوید که DAL یک بار با محصول مناسب و پیکربندی مناسب تماس گرفته شده است.", async () => { + //فرض کنید قبلاً یک محصول اضافه کرده ایم + const dataAccessMock = sinon.mock(DAL); + //هومممم بد است: تست قطعات داخلی در واقع هدف اصلی ما در اینجا است، نه فقط یک side effect + dataAccessMock + .expects("deleteProduct") + .once() + .withArgs(DBConfig, theProductWeJustAdded, true, false); + new ProductService().deletePrice(theProductWeJustAdded); + dataAccessMock.verify(); +}); +``` + +
    + +### :clap:مثال درست: جاسوسان بر روی تست الزامات متمرکز هستند، اما به عنوان یک side effect، به طور اجتناب ناپذیری به درونی ها دست می زنند. + +```javascript +it("هنگامی که یک محصول معتبر در شرف حذف است، مطمئن شوید که یک ایمیل ارسال شده است", async () => { + //فرض کنید قبلاً یک محصول را در اینجا اضافه کرده ایم + const spy = sinon.spy(Emailer.prototype, "sendEmail"); + new ProductService().deletePrice(theProductWeJustAdded); + //هومممم خوب است: ما با داخلی سروکار داریم؟ بله، اما به عنوان یک side effect تست الزامات (ارسال ایمیل) + expect(spy.calledOnce).to.be.true; +}); +``` + +
    + +

    + +## 📗 آیا می خواهید همه این تمرین ها را با ویدیوی زنده یاد بگیرید؟ + +###از دوره آنلاین من دیدن کنید [Testing Node.js & JavaScript از A تا Z](https://www.testjavascript.com) + +

    + +## ⚪ ️1.6 «گول نزنید»، از داده های ورودی واقعی استفاده کنید + +:white_check_mark: **انجام دادن:** اغلب اشکالات تولید تحت برخی ورودی‌های بسیار خاص و شگفت‌انگیز آشکار می‌شوند— هرچه ورودی آزمایش واقعی‌تر باشد، شانس تشخیص زودهنگام باگ‌ها بیشتر می‌شود. از کتابخانه‌های اختصاصی مانند [Chance](https://github.com/chancejs/chancejs) یا [Faker](https://www.npmjs.com/package/faker) برای تولید داده‌های شبه واقعی که شبیه انواع و اقسام آن‌ها هستند، استفاده کنید. شکل داده های تولید به عنوان مثال، چنین کتابخانه‌هایی می‌توانند شماره تلفن، نام کاربری، کارت اعتباری، نام شرکت و حتی متن «lorem ipsum» واقعی را تولید کنند. همچنین می‌توانید آزمایش‌هایی (در بالای آزمایش‌های واحد، نه به عنوان جایگزین) ایجاد کنید که داده‌های جعلی را تصادفی می‌کند تا واحد شما تحت آزمایش کشیده شود یا حتی داده‌های واقعی را از محیط تولید شما وارد کند. می خواهید آن را به سطح بعدی ببرید؟ گلوله بعدی (تست مبتنی بر ویژگی) را ببینید. +
    + +❌ **در غیر این صورت:** وقتی از ورودی های مصنوعی مانند "Foo" استفاده می کنید، تمام آزمایش های توسعه شما به اشتباه سبز نشان داده می شوند، اما زمانی که هکر رشته بدی مانند "@3e2ddsf" را عبور می دهد، ممکن است تولید قرمز شود. ##' 1 fdsfds . fds432 AAAA” + +
    + +
    نمونه کد + +
    + +### :thumbsdown: مثال ضد الگو: مجموعه آزمایشی که به دلیل داده های غیرواقعی قبول می شود + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +const addProduct = (name, price) => { + const productNameRegexNoSpace = /^\S*$/; //space مجاز نیست + + if (!productNameRegexNoSpace.test(name)) return false; //این شرط به دلیل ورودی نادرست هرگز اجرا نمی شود + + //بقیه کد ها + return true; +}; + +test("اشتباه: هنگام افزودن محصول جدید با ویژگی های معتبر، تأیید موفقیت آمیز دریافت کنید", async () => { + //رشته "Foo" که در همه تست ها استفاده می شود، هرگز نتیجه نادرستی را ایجاد نمی کند + const addProductResult = addProduct("Foo", 5); + expect(addProductResult).toBe(true); + //مثبت-کاذب: عملیات موفقیت آمیز بود زیرا ما هرگز تلاش زیادی نکردیم + //نام محصول شامل فاصله ها +}); +``` + +
    + +### :clap:مثال درست:تصادفی سازی ورودی واقعی + +```javascript +it("بهتر: هنگام افزودن محصول معتبر جدید، تأیید موفقیت آمیز دریافت کنید", async () => { + const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); + //ورودی تصادفی ایجاد شده: {'Sleek Cotton Computer', 85481} + expect(addProductResult).to.be.true; + //تست ناموفق بود، ورودی تصادفی مسیری را آغاز کرد که ما هرگز برای آن برنامه ریزی نکرده بودیم. + //ما یک باگ را زود کشف کردیم! +}); +``` + +
    + +

    + +## ⚪ ️ 1.7 بسیاری از ترکیبات ورودی را با استفاده از تست مبتنی بر ویژگی تست کنید + +:white_check_mark: **انجام دادن:** معمولاً برای هر آزمون چند نمونه ورودی انتخاب می کنیم. حتی زمانی که قالب ورودی شبیه داده های دنیای واقعی باشد (به گلوله مراجعه کنید [‘گول نزن’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), ما فقط چند ترکیب ورودی را پوشش می‌دهیم (method(''، true، 1)، روش ("string"، false، 0))، با این حال، در تولید، یک API که با 5 پارامتر فراخوانی می شود را می توان با هزاران پارامتر مختلف فراخوانی کرد. جایگشت، یکی از آنها ممکن است فرآیند ما را پایین بیاورد ([تست فاز را ببینید](https://en.wikipedia.org/wiki/Fuzzing)). اگر بتوانید یک تست بنویسید که 1000 جایگشت از ورودی های مختلف را به طور خودکار ارسال می کند و کد ما برای کدام ورودی پاسخ مناسب را نمی دهد؟ تست مبتنی بر ویژگی تکنیکی است که دقیقاً همین کار را انجام می دهد: با ارسال تمام ترکیبات ورودی ممکن به واحد تحت آزمایش شما، مشکل یافتن یک باگ را افزایش می دهد. به عنوان مثال، با توجه به یک متد —  addNewProduct(id, name, isDiscount)—— کتابخانه های پشتیبانی کننده این متد را با ترکیب های زیادی از (عدد، رشته، بولی) مانند (1، "iPhone"، false)، (2، "Galaxy" فراخوانی می کنند. "، درست است، واقعی). شما می توانید تست مبتنی بر ویژگی را با استفاده از برنامه آزمایشی مورد علاقه خود (موکا، جست و غیره) با استفاده از کتابخانه هایی مانند [js-verify](https://github.com/jsverify/jsverify) یا [بررسی تست](https://github.com/leebyron/testcheck-js) (مستندات خیلی بهتر). به روز رسانی: Nicolas Dubien در نظرات زیر به[تسویه حساب سریع](https://github.com/dubzzz/fast-check#readme) که به نظر می رسد برخی از ویژگی های اضافی را ارائه می دهد و همچنین به طور فعال حفظ می شود +
    + +❌ **در غیر این صورت:** ناخودآگاه، ورودی‌های تست را انتخاب می‌کنید که فقط مسیرهای کدی را پوشش می‌دهند که به خوبی کار می‌کنند. متأسفانه، این کارایی تست را به عنوان وسیله ای برای افشای اشکالات کاهش می دهد + +
    + +
    نمونه کد + +
    + +### :clap: مثال درست: آزمایش بسیاری از جایگشت های ورودی با "بررسی سریع" + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +import fc from "fast-check"; + +describe("خدمات محصول", () => { + describe("افزودن جدید", () => { + //این 100 بار با ویژگی های تصادفی مختلف اجرا می شود + it("محصول جدید با ویژگی های تصادفی و در عین حال معتبر، همیشه موفق اضافه کنید", () => + fc.assert( + fc.property(fc.integer(), fc.string(), (id, name) => { + expect(addNewProduct(id, name).status).toEqual("approved"); + }) + )); + }); +}); +``` + +
    + +

    + +## ⚪ ️ 1.8 در صورت نیاز، فقط از snapshots کوتاه و درون خطی استفاده کنید + +:white_check_mark: **انجام دادن:** زمانی که نیاز باشد [snapshot تست کردن](https://jestjs.io/docs/en/snapshot-testing), فقط از عکس های فوری کوتاه و متمرکز استفاده کنید (i.e. 3-7 خط) که به عنوان بخشی از آزمون گنجانده شده است ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) و نه در فایل های خارجی. رعایت این دستورالعمل تضمین می‌کند که تست‌های شما قابل توضیح و کمتر شکننده هستند. + +از سوی دیگر، آموزش‌ها و ابزارهای «snapshot کلاسیک»، ذخیره فایل‌های بزرگ (مانند نشانه‌گذاری رندر مؤلفه، نتیجه API JSON) را بر روی برخی رسانه‌های خارجی تشویق می‌کنند و اطمینان حاصل می‌کنند که هر بار هنگام اجرای تست، نتیجه دریافت‌شده با نسخه ذخیره‌شده مقایسه شود. برای مثال، این می تواند به طور ضمنی آزمون ما را به 1000 خط با 3000 مقدار داده که نویسنده آزمون هرگز نخوانده و درباره آن استدلال نکرده است، مرتبط کند. چرا این اشتباه است؟ با انجام این کار، 1000 دلیل برای شکست تست شما وجود دارد - کافی است یک خط تغییر کند تا snapshot نامعتبر شود و این احتمالاً زیاد اتفاق می افتد. چند بار؟ برای هر فضا، نظر یا تغییر جزئی CSS/HTML. نه تنها این، نام آزمون سرنخی در مورد شکست نمی دهد، زیرا فقط بررسی می کند که 1000 خط تغییر نکرده است، همچنین نویسنده آزمون را تشویق می کند که سند طولانی را که نمی تواند بررسی کند، به عنوان واقعی مورد نظر بپذیرد. تایید کنید. همه اینها علائم آزمون مبهم و مشتاق است که متمرکز نیست و هدف آن دستیابی به بیش از حد است + +شایان ذکر است که موارد کمی وجود دارد که snapshotهای طولانی و خارجی قابل قبول باشد - هنگام ادعا بر روی طرح و نه داده (استخراج مقادیر و تمرکز بر روی فیلدها) یا زمانی که سند دریافتی به ندرت تغییر می‌کند. +
    + +❌ **در غیر این صورت:** تست رابط کاربری ناموفق است. به نظر می رسد کد درست است، صفحه نمایش پیکسل های عالی را ارائه می دهد، چه اتفاقی افتاده است؟ تست snapshot شما تفاوتی بین سند مبدأ با سند دریافتی فعلی پیدا کرد - یک کاراکتر فاصله به علامت گذاری اضافه شد... + +
    + +
    نمونه کد + +
    + +### :thumbsdown: مثال ضد الگو: جفت کردن تست ما به 2000 خط کد + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +it("TestJavaScript.com به درستی ارائه شده است", () => { + //مفدار دهی کردن + + //اجرا کردن + const receivedPage = renderer + .create( Test JavaScript ) + .toJSON(); + + //مقایسه کردن + expect(receivedPage).toMatchSnapshot(); + //ما اکنون به طور ضمنی یک سند 2000 خطی را حفظ می کنیم + //هر شکست خط یا comment اضافی - این تست را شکسته خواهد کرد +}); +``` + +
    + +### :clap: مثال درست: انتظارات قابل مشاهده و متمرکز هستند + +```javascript +it("هنگام بازدید از صفحه اصلی TestJavaScript.com، یک منو نمایش داده می شود", () => { + //مفدار دهی کردن + + //اجرا کردن + const receivedPage = renderer + .create( Test JavaScript ) + .toJSON(); + + //مفایسه کردن + + const menu = receivedPage.content.menu; + expect(menu).toMatchInlineSnapshot(` + +`); +}); +``` + +
    + +

    + +## ⚪ ️کد را کپی کنید، اما فقط آنچه لازم است + +:white_check_mark: **انجام دادن:** تمام جزئیات لازم را که بر نتیجه آزمایش تأثیر می گذارد، وارد کنید، اما نه بیشتر. به عنوان مثال، تستی را در نظر بگیرید که باید 100 خط ورودی JSON را در نظر بگیرید - چسباندن آن در هر تست خسته کننده است. استخراج آن از خارج به transferFactory.getJSON() باعث می‌شود تست مبهم باقی بماند - بدون داده، ارتباط نتیجه آزمایش با علت دشوار است ("چرا قرار است وضعیت 400 را برگرداند؟"). کتاب کلاسیک الگوهای واحد x نام این الگو را «مهمان اسرارآمیز» گذاشتند -  چیزی نادیده بر نتایج آزمایش ما تأثیر گذاشت، ما دقیقاً نمی‌دانیم چه چیزی. ما می‌توانیم با استخراج قطعات طولانی قابل تکرار در خارج و به صراحت اشاره کنیم که کدام جزئیات خاص برای آزمایش مهم هستند، بهتر عمل کنیم. با استفاده از مثال بالا، آزمون می‌تواند پارامترهایی را پاس کند که آنچه مهم است را برجسته می‌کند: transferFactory.getJSON({sender: undefined}). در این مثال، خواننده باید فوراً استنباط کند که قسمت خالی فرستنده دلیلی است که آزمون باید منتظر خطای اعتبارسنجی یا هر نتیجه کافی مشابه دیگری باشد. +
    + +❌ **در غیر این صورت:** کپی کردن 500 خط JSON باعث می‌شود که تست‌های شما قابل نگهداری و خواندن نباشند. جابجایی همه چیز به بیرون با آزمون های مبهمی که درک آنها سخت است به پایان می رسد + +
    + +
    نمونه کد + +
    + +### :thumbsdown: مثال ضد الگو: شکست تست نامشخص است زیرا همه علت خارجی است و در JSON بزرگ پنهان می شود + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +test("وقتی اعتباری وجود ندارد، انتقال رد می شود", async() => { + // مفدار دهی کردن + const transferRequest = testHelpers.factorMoneyTransfer() //200 خط JSON را برگردانید. + const transferServiceUnderTest = new TransferService(); + + // اجرا کردن + const transferResponse = await transferServiceUnderTest.transfer(transferRequest); + + // مفایسه کردن + expect(transferResponse.status).toBe(409);// اما چرا ما انتظار شکست را داریم: به نظر می رسد همه در آزمون کاملاً معتبر هستند 🤔 + }); +``` + +
    + +### :clap: مثال درست: آزمایش نشان می دهد که علت نتیجه آزمایش چیست + +```javascript + +test("وقتی اعتباری وجود ندارد، انتقال رد می شود", async() => { + // مفدار دهی کردن + const transferRequest = testHelpers.factorMoneyTransfer({userCredit:100, transferAmount:200}) //بدیهی است که کمبود اعتبار وجود دارد + const transferServiceUnderTest = new TransferService({disallowOvercharge:true}); + + // اجرا کردن + const transferResponse = await transferServiceUnderTest.transfer(transferRequest); + + // مقایسه کردن + expect(transferResponse.status).toBe(409); //بدیهی است که اگر کاربر اعتباری نداشته باشد، باید شکست بخورد + }); + ``` + +
    + +

    + +## ⚪ ️ 1.10 اشتباهات را متوجه نشوید، منتظر آنها باشید + +:white_check_mark: **انجام دادن:** هنگامی که می‌خواهید ادعا کنید که برخی از ورودی‌ها باعث ایجاد خطا می‌شوند، ممکن است استفاده از try-catch-finally درست به نظر برسد و ادعا کند که بند catch وارد شده است. نتیجه یک مورد تست نامناسب و مفصل است (مثال زیر) که هدف آزمون ساده و انتظارات نتیجه را پنهان می کند. + +یک جایگزین زیباتر استفاده از ادعای اختصاصی Chai یک خطی است: expect(method).to.throw (یا در Jest: expect(method).toThrow()). کاملاً اجباری است که اطمینان حاصل شود که استثنا دارای خاصیتی است که نوع خطا را بیان می کند، در غیر این صورت با توجه به یک خطای عمومی، برنامه نمی تواند به جای نمایش یک پیام ناامیدکننده به کاربر، کار زیادی انجام دهد. +
    + +❌ **در غیر این صورت:** استنتاج از گزارش‌های آزمایشی (مثلاً گزارش‌های CI) چالش برانگیز خواهد بود + +
    + +
    نمونه کد + +
    + +### :thumbsdown: مثال ضد الگو: یک مورد تست طولانی که سعی می‌کند وجود خطا را با استفاده از آزمون تأیید کند. + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +it("هنگامی که نام محصول وجود ندارد، خطای 400 را نشان می دهد", async () => { + let errorWeExceptFor = null; + try { + const result = await addNewProduct({}); + } catch (error) { + expect(error.code).to.equal("InvalidInput"); + errorWeExceptFor = error; + } + expect(errorWeExceptFor).not.to.be.null; + //اگر این مقایسه ناموفق باشد، نتایج/گزارش‌های تست فقط نشان داده می‌شوند + //که مقداری تهی است، کلمه ای در مورد یک استثنا وجود نخواهد داشت +}); +``` + +
    + +### :clap: مثال درست: انتظاری قابل خواندن برای انسان که به راحتی قابل درک است، حتی ممکن است توسط QA یا PM فنی + +```javascript +it("هنگامی که نام محصول وجود ندارد، خطای 400 را نشان می دهد", async () => { + await expect(addNewProduct({})) + .to.eventually.throw(AppError) + .with.property("code", "InvalidInput"); +}); +``` + +
    + +

    + +## ⚪ ️ 1.11 تست های خود را تگ کنید + +:white_check_mark: **انجام دادن:** تست های مختلف باید در سناریوهای مختلف اجرا شوند: دود سریع، بدون IO، تست ها باید زمانی که یک توسعه‌دهنده فایلی را ذخیره یا متعهد می‌کند، اجرا می‌شوند، تست های کامل پایان به انتها معمولاً هنگام ارسال درخواست کشش جدید اجرا می‌شوند، و غیره. با برچسب گذاری تست ها با کلمات کلیدی مانند #cold #api #sanity تا بتوانید با مهار تست خود دست بگیرید و زیر مجموعه مورد نظر را فراخوانی کنید. برای مثال، به این صورت است که فقط گروه تست سلامت عقل را با موکا فرا می‌خوانید: mocha — grep ‘sanity’ +
    + +❌ **در غیر این صورت:** اجرای تمام تست‌ها، از جمله تست‌هایی که ده‌ها پرس‌وجو در DB را انجام می‌دهند، هر زمانی که یک توسعه‌دهنده یک تغییر کوچک ایجاد کند می‌تواند بسیار کند باشد و توسعه‌دهندگان را از اجرای تست ها دور نگه دارد. + +
    + +
    نمونه کد + +
    + +### :clap: مثال درست: برچسب گذاری تست ها به عنوان "#cold-test" به اجراکننده آزمون اجازه می دهد فقط تست های سریع را اجرا کند (تست های سرد===سریع که هیچ IO انجام نمی دهند و حتی زمانی که توسعه دهنده در حال تایپ کردن است می توانند مکررا اجرا شوند) + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +//این تست سریع است (بدون DB) و ما آن را مطابق با آن برچسب گذاری می کنیم +//اکنون کاربر/CI می تواند آن را به طور مکرر اجرا کند +describe("سفارش سرویس", function() { + describe("اضافه کردن سفارش جدید #تست سرد #عقل", function() { + test("سناریو - هیچ ارزی عرضه نشد. انتظار - از ارز پیش فرض #عقل استفاده کنید", function() { + //بقیه کدها + }); + }); +}); +``` + +
    + +

    + +## ⚪ ️ 1.12 تست ها را حداقل در 2 سطح دسته بندی کنید + +:white_check_mark: **انجام دادن:** ساختاری را در مجموعه آزمایشی خود اعمال کنید تا یک بازدیدکننده گاه به گاه بتواند به راحتی الزامات (آزمون ها بهترین مستندات هستند) و سناریوهای مختلفی که در حال آزمایش هستند را درک کند. یک روش متداول برای این کار قرار دادن حداقل 2 بلوک «توضیح» در بالای تست‌هایتان است: اولی برای نام واحد تحت آزمایش و دومی برای سطح اضافی طبقه‌بندی مانند سناریو یا دسته‌های سفارشی است (نمونه‌های کد را ببینید و چاپ کنید. صفحه زیر). انجام این کار گزارش‌های آزمون را نیز بسیار بهبود می‌بخشد: خواننده به راحتی دسته‌های تست‌ها را استنباط می‌کند، در بخش مورد نظر کاوش می‌کند و تست‌های مردودی را به هم مرتبط می‌کند. علاوه بر این، پیمایش کد یک مجموعه با آزمایش های زیاد برای یک توسعه دهنده بسیار آسان تر خواهد شد. چندین ساختار جایگزین برای مجموعه آزمایشی وجود دارد که می‌توانید آنها را مانند آنها در نظر بگیرید [داده شده-وقتی-پس](https://github.com/searls/jasmine-given) و [RITE](https://github.com/ericelliott/riteway) + +
    + +❌ **در غیر این صورت:** وقتی به گزارشی با فهرستی مسطح و طولانی از آزمون‌ها نگاه می‌کنیم، خواننده باید متن‌های طولانی را به طور کامل بخواند تا سناریوهای اصلی را نتیجه‌گیری کند و مشترکات رد شدن در آزمون‌ها را به هم مرتبط کند. مورد زیر را در نظر بگیرید: هنگامی که 7/100 تست شکست می خورند، نگاه کردن به یک لیست ثابت نیاز به خواندن متن تست های مردود دارد تا ببینید چگونه آنها با یکدیگر ارتباط دارند. با این حال، در یک گزارش سلسله مراتبی، همه آنها می توانند تحت یک جریان یا دسته باشند و خواننده به سرعت استنباط می کند که علت اصلی شکست چیست یا حداقل کجاست. + +
    + +
    نمونه کد + +
    + +### :clap: مثال درست: مجموعه ساختاری با نام واحد تحت آزمایش و سناریوها به گزارش مناسبی منجر می شود که در زیر نشان داده شده است. + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +// واحد در حال تست +describe("سرویس انتقال", () => { + //سناریو + describe("زمانی که اعتباری وجود ندارد", () => { + //انتظار + test("سپس وضعیت پاسخ باید کاهش یابد", () => {}); + + //انتظار + test("سپس باید برای مدیر ایمیل ارسال شود", () => {}); + }); +}); +``` + +![alt text](assets/hierarchical-report.png) + +
    + +### :thumbsdown: مثال ضد الگو: یک لیست مسطح از تست ها شناسایی داستان های کاربر و ارتباط بین تست های شکست خورده را برای خواننده سخت تر می کند. + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Mocha") + +```javascript +test("سپس وضعیت پاسخ باید کاهش یابد", () => {}); + +test("سپس باید ایمیل بفرستد", () => {}); + +test("در این صورت نباید سابقه نقل و انتقالات جدیدی وجود داشته باشد", () => {}); +``` + +![alt text](assets/flat-report.png) + +
    + +
    + +

    + +## ⚪ ️1.13 موارد دیگر برای نوشتن یک تست خوب + +:white_check_mark: **انجام دادن:** این پست روی توصیه‌های آزمایشی متمرکز شده است، یا حداقل می‌توان آن را با Node JS مثال زد. با این حال، این گلوله، چند نکته غیر مرتبط با Node را که به خوبی شناخته شده اند، گروه بندی می کند + +یاد بگیرید و تمرین کنید [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) آنها برای بسیاری بسیار ارزشمند هستند، اما اگر با سبک شما سازگاری ندارند، نترسید، شما تنها نیستید. تست ها را قبل از کد در بنویسید [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), اطمینان حاصل کنید که هر تست دقیقاً یک چیز را بررسی می کند، وقتی یک باگ پیدا کردید — قبل از رفع یک تست بنویسید که این اشکال را در آینده شناسایی کند، اجازه دهید هر تست حداقل یک بار قبل از سبز شدن شکست بخورد، با نوشتن یک کد سریع و ساده، یک ماژول را شروع کنید. آزمایش را برآورده می کند - سپس به تدریج اصلاح کنید و آن را به سطح درجه تولید ببرید، از هر گونه وابستگی به محیط (مسیرها، سیستم عامل و غیره) اجتناب کنید. +
    + +❌ **در غیر این صورت:** دلت برای مرواریدهای خردی که برای چندین دهه جمع آوری شده اند را از دست خواهید داد + +

    + +# Section 2️⃣: Backend تست کردن + +## ⚪ ️2.1 مجموعه تست خود را زیاد کنید: فراتر از Unit Test و هرم تست نرم افزار نگاه کنید + +:white_check_mark: **انجام دادن:** [تست کردن هرم](https://martinfowler.com/bliki/TestPyramid.html), اگرچه 10 سال سن دارد، اما یک مدل عالی و مرتبط است که سه نوع تست را پیشنهاد می کند و بر استراتژی تست اکثر توسعه دهندگان تأثیر می گذارد. در همان زمان، بیش از تعداد انگشت شماری از تکنیک‌های آزمایشی جدید براق ظاهر شدند و در سایه‌های هرم آزمایش پنهان شدند. با توجه به تمام تغییرات چشمگیری که در 10 سال اخیر دیده ایم (سرویس های میکرو، ابر، بدون سرور)، آیا حتی ممکن است یک مدل کاملاً قدیمی برای *همه* انواع برنامه ها مناسب باشد؟ آیا دنیای تست نباید از تکنیک های تست جدید استقبال کند؟ + +اشتباه نکنید، در سال 2019 تست هرم، تست TDD و واحد هنوز یک تکنیک قدرتمند هستند و احتمالاً بهترین تطابق برای بسیاری از برنامه ها هستند. فقط مانند هر مدل دیگری با وجود مفید بودن, [گاهی اوقات باید اشتباه باشد](https://en.wikipedia.org/wiki/All_models_are_wrong). به عنوان مثال، یک برنامه IoT را در نظر بگیرید که بسیاری از رویدادها را در یک گذرگاه پیام مانند Kafka/RabbitMQ وارد می‌کند، که سپس به انبار داده سرازیر می‌شود و در نهایت توسط برخی از رابط‌های تحلیلی پرس و جو می‌شود. آیا واقعاً باید 50 درصد از بودجه تست خود را صرف نوشتن آزمون های واحد برای برنامه ای کنیم که یکپارچه محور است و تقریباً هیچ منطقی ندارد؟ با افزایش تنوع انواع برنامه ها (ربات ها، کریپتو، مهارت های الکسا) شانس بیشتری برای یافتن سناریوهایی وجود دارد که در آن هرم آزمایشی بهترین تطابق نیست. + +وقت آن رسیده است که مجموعه آزمایشی خود را غنی کنید و با انواع تست های بیشتری آشنا شوید (گلوله های بعدی ایده های کمی را پیشنهاد می کنند)، مدل های ذهنی مانند هرم آزمایش، اما همچنین انواع آزمایش را با مشکلات دنیای واقعی که با آن مواجه هستید مطابقت دهید ('Hey, API ما خراب است، بیایید تست قرارداد مبتنی بر مصرف‌کننده بنویسیم!»)، آزمایش‌های خود را مانند سرمایه‌گذاری که سبد سهامی را بر اساس تجزیه و تحلیل ریسک می‌سازد، متنوع کنید— ارزیابی کنید که در کجا ممکن است مشکلات ایجاد شوند و با برخی از اقدامات پیشگیرانه برای کاهش خطرات احتمالی مطابقت دهید. + +یک کلمه احتیاط: استدلال TDD در دنیای نرم افزار یک چهره دوگانگی کاذب معمولی دارد، برخی موعظه می کنند که از آن در همه جا استفاده کنند، برخی دیگر فکر می کنند که این شیطان است. هر کس به طور مطلق صحبت می کند اشتباه می کند:] + +
    + +❌ **در غیر این صورت:** برخی از ابزارها با ROI شگفت انگیز را از دست خواهید داد، برخی مانند Fuzz، lint و جهش می توانند در 10 دقیقه ارزش ارائه دهند. + +
    + +
    نمونه کد + +
    + +### :clap: مثال درست: سیندی سریدهان یک نمونه کار آزمایشی غنی را در پست شگفت‌انگیز خود «تست کردن میکروسرویس‌ها» پیشنهاد می‌کند - به همین روش. + +![alt text](assets/bp-12-rich-testing.jpeg "Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the sane way’") + +☺️Example: [YouTube: “Beyond Unit Tests: 5 Shiny Node.JS Test Types (2018)” (Yoni Goldberg)](https://www.youtube.com/watch?v=-2zP494wdUY&feature=youtu.be) + +
    + +![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "A test name that constitutes 3 parts") + +
    + +

    + +## ⚪ ️2.2 تست کامپوننت ممکن است بهترین کار شما باشد + +:white_check_mark: **انجام دادن:** هر Unit test بخش کوچکی از برنامه را پوشش می‌دهد و پوشش کل آن گران است، در حالی که تست E2E به راحتی زمین زیادی را پوشش می‌دهد، اما پوسته پوسته و کندتر است، چرا یک رویکرد متعادل را اعمال نکنیم و تست‌هایی بزرگ‌تر از آن بنویسیم. Unit tests اما کوچکتر از تست E2E؟ تست کامپوننت آهنگ ناخوانده دنیای آزمایش است— آنها بهترین ها را از هر دو دنیا ارائه می دهند: عملکرد معقول و امکان اعمال الگوهای TDD + پوشش واقعی و عالی. + +تست‌های کامپوننت روی «واحد» میکروسرویس تمرکز می‌کنند، آن‌ها بر خلاف API کار می‌کنند، هر چیزی را که متعلق به خود میکروسرویس است (مثلاً DB واقعی یا حداقل نسخه درون حافظه آن DB) را مسخره نمی‌کنند، اما هر چیزی را که خارجی است، خرد نمی‌کنند. مانند تماس با سایر میکروسرویس ها. با انجام این کار، آنچه را که به کار می‌گیریم آزمایش می‌کنیم، برنامه را از بیرون به داخل نزدیک می‌کنیم و در مدت زمان معقولی اعتماد به نفس بالایی به دست می‌آوریم. + +[ما یک راهنمای کامل داریم که صرفاً به نوشتن تست های مؤلفه به روش صحیح اختصاص دارد](https://github.com/testjavascript/nodejs-integration-tests-best-practices) + +
    + +❌ **در غیر این صورت:** ممکن است روزهای طولانی را صرف نوشتن Unit test کنید تا متوجه شوید که فقط 20 درصد پوشش سیستم را دارید + +
    + +
    نمونه کد + +
    + +### :clap: مقال درست: Supertest اجازه می دهد تا نزدیک شدن به Express API در فرآیند (سریع و پوشش چندین لایه) + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) allows approaching Express API in-process (fast and cover many layers)") + +
    + +

    + +## ⚪ ️2.3 اطمینان حاصل کنید که نسخه‌های جدید نرم افزار , تست هایی که برای api ها نوشته شده است را خراب نمی‌کنند + +:white_check_mark: **انجام دادن:** بنابراین Microservice شما چندین مشتری دارد و شما چندین نسخه از این سرویس را به دلایل سازگاری اجرا می کنید (راضی نگه داشتن همه). سپس مقداری فیلد را تغییر می‌دهید و «بوم!»، مشتری مهمی که به این زمینه متکی است عصبانی است. این Catch-22 دنیای یکپارچه سازی است: برای طرف سرور بسیار چالش برانگیز است که تمام انتظارات مشتری چندگانه را در نظر بگیرد— از سوی دیگر، مشتریان نمی توانند هیچ آزمایشی را انجام دهند زیرا سرور تاریخ انتشار را کنترل می کند. طیفی از تکنیک‌ها وجود دارند که می‌توانند مشکل قرارداد را کاهش دهند، برخی ساده هستند، برخی دیگر دارای ویژگی‌های غنی‌تر هستند و منحنی یادگیری تندتری را طلب می‌کنند. در یک رویکرد ساده و توصیه‌شده، ارائه‌دهنده API بسته npm را با تایپ API منتشر می‌کند (مانند JSDoc، TypeScript). سپس مصرف کنندگان می توانند این کتابخانه را دریافت کنند و از زمان هوشمندانه و اعتبارسنجی بهره مند شوند. یک رویکرد شیک تر از آن برای استفاده [PACT](https://docs.pact.io/) که برای رسمی کردن این فرآیند با رویکردی بسیار مخرب متولد شده‌اند—— نه سرور برنامه آزمایشی را از خود تعریف می‌کند، بلکه مشتری آزمایش‌های سرور را تعریف می‌کند! PACT می‌تواند انتظارات مشتری را ثبت کند و در یک مکان مشترک، «کارگزار» قرار دهد، بنابراین سرور می‌تواند انتظارات را جمع‌آوری کند و با استفاده از کتابخانه PACT روی هر ساختنی اجرا کند تا قراردادهای شکسته را شناسایی کند - یک انتظار مشتری که برآورده نشده است. با انجام این کار، همه ناهماهنگی های API سرور-کلینت در مراحل اولیه ساخت/CI شناسایی می شوند و ممکن است شما را از ناامیدی زیادی نجات دهد. +
    + +❌ **در غیر این صورت:** گزینه های جایگزین تست دستی خسته کننده یا ترس از استقرار هستند + +
    + +
    نمونه کد + +
    + +### :clap: مقال درست: + +![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") + +![alt text](assets/bp-14-testing-best-practices-contract-flow.png) + +
    + +

    + +## ⚪ ️ 2.4 middleware خود را جداگانه تست کنید + +:white_check_mark: **انجام دادن:** بسیاری از تست Middleware اجتناب می کنند زیرا آنها بخش کوچکی از سیستم را نشان می دهند و به یک سرور Express زنده نیاز دارند. هر دو دلیل اشتباه هستند——Middleware ها کوچک هستند اما بر همه یا بیشتر درخواست ها تأثیر می گذارند و می توانند به راحتی به عنوان توابع خالصی که اشیاء JS {req,res} را دریافت می کنند آزمایش شوند. برای آزمایش عملکرد میان‌افزار، فقط باید آن را فراخوانی و جاسوسی کرد ([برای مثال از Sinon استفاده کنید](https://www.npmjs.com/package/sinon)) در تعامل با دو object {req,res} برای اطمینان از انجام عملکرد صحیح عملکرد. کتابخانه [node-mock-http](https://www.npmjs.com/package/node-mocks-http) آن را حتی فراتر می برد و دو object {req,res} را همراه با جاسوسی از رفتار آنها فاکتور می کند. به عنوان مثال، می تواند ادعا کند که آیا وضعیت http که روی شی res تنظیم شده است با انتظارات مطابقت دارد یا خیر (به مثال زیر مراجعه کنید). +
    + +❌ **در غیر این صورت:** یک اشکال در میان افزار Express === یک اشکال در همه یا اکثر request ها + +
    + +
    نمونه کد + +
    + +### :clap: مقال درست: تست middleware به‌صورت مجزا بدون برقراری تماس‌های شبکه و بیدار کردن کل دستگاه Express + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +//middleware که می خواهیم تست کنیم +const unitUnderTest = require("./middleware"); +const httpMocks = require("node-mocks-http"); +//Jest syntax, equivelant to describe() & it() in Mocha +test("درخواست بدون هدر احراز هویت، باید وضعیت http 403 را برگرداند", () => { + const request = httpMocks.createRequest({ + method: "GET", + url: "/user/42", + headers: { + authentication: "" + } + }); + const response = httpMocks.createResponse(); + unitUnderTest(request, response); + expect(response.statusCode).toBe(403); +}); +``` + +
    + +

    + +## ⚪ ️2.5 اندازه گیری و بازسازی با استفاده از ابزارهای تحلیل استاتیکی + +:white_check_mark: **انجام دادن:** استفاده از ابزارهای تجزیه و تحلیل استاتیک با ارائه روش های عینی برای بهبود کیفیت کد و حفظ کد شما کمک می کند. می‌توانید ابزارهای تجزیه و تحلیل استاتیک را به ساخت CI خود اضافه کنید تا زمانی که بوی کد پیدا می‌شود، لغو شود. نقاط اصلی فروش آن نسبت به پرده‌بندی ساده، توانایی بازرسی کیفیت در زمینه فایل‌های متعدد (به عنوان مثال شناسایی موارد تکراری)، انجام تجزیه و تحلیل پیشرفته (مانند پیچیدگی کد) و پیگیری تاریخچه و پیشرفت مشکلات کد است. دو نمونه از ابزارهایی که می توانید استفاده کنید عبارتند از [SonarQube](https://www.sonarqube.org/) (4,900+ [ستاره](https://github.com/SonarSource/sonarqube)) و [کد آب و هوا](https://codeclimate.com/) (2,000+ [ستاره](https://github.com/codeclimate/codeclimate)) + +سازنده: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + +❌ **در غیر این صورت:** با کیفیت پایین کد، اشکالات و عملکرد همیشه مشکلی است که هیچ کتابخانه جدید و براق جدیدی نمی تواند آن را برطرف کند. + +
    + +
    نمونه کد + +
    + +### :clap: مقال درست: CodeClimate، یک ابزار تجاری است که می تواند متد های پیچیده را شناسایی کند: + +![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg "Examples with CodeClimate") + +![alt text](assets/bp-16-yoni-goldberg-quality.png "CodeClimate, a commercial tool that can identify complex methods:") + +
    + +

    + +## ⚪ ️ 2.6 آمادگی خود را برای هرج و مرج مرتبط با Node بررسی کنید + +:white_check_mark: **انجام دادن:** به طور عجیبی، اکثر تست‌های نرم‌افزار فقط در مورد منطق و داده‌ها هستند، اما برخی از بدترین چیزهایی که اتفاق می‌افتد (و واقعاً کاهش آن سخت است) مسائل زیرساختی است. به عنوان مثال، آیا تا به حال آزمایش کرده‌اید که چه اتفاقی می‌افتد وقتی حافظه پردازش شما بیش از حد بارگیری می‌شود، یا زمانی که سرور/فرآیند از بین می‌رود، یا آیا سیستم نظارت شما متوجه می‌شود که API 50٪ کندتر می‌شود؟ برای آزمایش و کاهش این نوع چیزهای بد——[مهندسی آشوب](https://principlesofchaos.org/) توسط نتفلیکس متولد شد. هدف آن ارائه آگاهی، چارچوب ها و ابزارهایی برای آزمایش انعطاف پذیری برنامه ما در برابر مسائل آشفته است. به عنوان مثال یکی از ابزارهای معروف آن، [میمون آشوب](https://github.com/Netflix/chaosmonkey), به طور تصادفی سرورها را می کشد تا اطمینان حاصل شود که سرویس ما همچنان می تواند به کاربران سرویس دهد و به یک سرور تکیه نمی کند (نسخه Kubernetes نیز وجود دارد، [میمون کوبه](https://github.com/asobti/kube-monkey), که غلاف ها را می کشد). همه این ابزارها در سطح میزبانی/پلتفرم کار می‌کنند، اما اگر می‌خواهید آشفتگی Node خالص را آزمایش و ایجاد کنید، چه می‌کنید، مانند بررسی اینکه چگونه فرآیند Node شما با خطاهای کشف نشده، رد کردن وعده‌های کنترل نشده، حافظه v8 بارگیری شده با حداکثر مجاز 1.7 گیگابایت یا اینکه آیا کنترل می‌شود چه می‌شود. زمانی که حلقه رویداد اغلب مسدود می شود، تجربه کاربری شما رضایت بخش باقی می ماند؟ برای پرداختن به این که نوشته ام، [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) که انواع اعمال آشفته مرتبط با Node را فراهم می کند +
    + +❌ **در غیر این صورت:** اینجا راه گریزی نیست، قانون مورفی بدون رحم به تولید شما ضربه می زند + +
    + +
    نمونه کد + +
    + +### :clap: مقال درست: : Node-chaos می‌تواند انواع شوخی‌های Node.js را ایجاد کند، بنابراین می‌توانید میزان انعطاف‌پذیری برنامه شما در برابر هرج و مرج را آزمایش کنید. + +![alt text](assets/bp-17-yoni-goldberg-chaos-monkey-nodejs.png "Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to chaos") + +
    + +
    + +## ⚪ ️2.7 اجتناب از تجهیزات تست جهانی و دانه، اضافه کردن داده ها در هر آزمون + +:white_check_mark: **انجام دادن:** با توجه به قانون طلایی (گلوله 0)، هر تست باید مجموعه ای از ردیف های DB خود را اضافه کرده و روی آن عمل کند تا از جفت شدن جلوگیری کند و به راحتی در مورد جریان تست استدلال کند. در واقع، این امر اغلب توسط آزمایش‌کنندگانی که قبل از اجرای آزمایش‌ها (که به عنوان «تست فیکسچر» نیز شناخته می‌شود) را با داده‌ها به منظور بهبود عملکرد می‌دانند، نقض می‌شود. در حالی که عملکرد در واقع یک نگرانی معتبر است. عملاً، هر مورد آزمایشی را به صراحت سوابق DB مورد نیاز خود را اضافه کنید و فقط روی آن رکوردها عمل کنید. اگر عملکرد به یک نگرانی حیاتی تبدیل شود - یک مصالحه متوازن ممکن است به شکل کاشت تنها مجموعه آزمایش‌هایی باشد که داده‌ها را تغییر نمی‌دهند (مثلاً درخواست‌ها) +
    + +❌ **در غیر این صورت:** تعداد کمی از تست ها شکست می خورند، استقرار متوقف می شود، تیم ما اکنون زمان گرانبهایی را صرف می کند، آیا ما باگ داریم؟ بیایید بررسی کنیم، اوه نه—به نظر می‌رسد دو تست داده‌های اولیه مشابهی را جهش می‌دادند + +
    + +
    نمونه کد + +
    + +### :thumbsdown: مثال ضد الگو: تست‌ها مستقل نیستند و برای تغذیه داده‌های DB جهانی به چند قلاب جهانی متکی هستند. + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +before(async () => { + //افزودن سایت ها و داده های مدیران به DB ما. داده ها کجاست؟ خارج از. در برخی framework های json یا مهاجرت خارجی + await DB.AddSeedDataFromJson('seed.json'); +}); +it("هنگام به روز رسانی نام سایت، تأیید موفقیت آمیز دریافت کنید", async () => { + //من می دانم که نام سایت "portal" وجود دارد - آن را در فایل های seed دیدم + const siteToUpdate = await SiteService.getSiteByName("Portal"); + const updateNameResult = await SiteService.changeName(siteToUpdate, "newName"); + expect(updateNameResult).to.be(true); +}); +it("هنگام پرس و جو بر اساس نام سایت، سایت مناسب را دریافت کنید", async () => { + //من می دانم که نام سایت "portal" وجود دارد - آن را در فایل های seed دیدم + const siteToCheck = await SiteService.getSiteByName("Portal"); + expect(siteToCheck.name).to.be.equal("Portal"); //شکست! تست قبلی نام را تغییر دهید:[ +}); + +``` + +
    + +### :clap: مقال درست: ما می توانیم در آزمون بمانیم، هر آزمون بر اساس مجموعه داده های خود عمل می کند + +```javascript +it("هنگام به روز رسانی نام سایت، تأیید موفقیت آمیز دریافت کنید", async () => { + //تست اضافه کردن یک رکورد جدید جدید و عمل کردن فقط بر روی رکوردها است + const siteUnderTest = await SiteService.addSite({ + name: "siteForUpdateTest" + }); + const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); + expect(updateNameResult).to.be(true); +}); +``` + +
    + +
    + +## ⚪ ️2.8 یک استراتژی پاکسازی داده واضح انتخاب کنید: بعد از همه (توصیه می شود) یا بعد از هر کدام + +:white_check_mark: **انجام دادن:** زمانی که تست ها پایگاه داده را تمیز می کنند، نحوه نگارش تست ها را تعیین می کند. دو گزینه مناسب، تمیز کردن بعد از همه آزمایش ها در مقابل تمیز کردن بعد از هر آزمایش است. با انتخاب گزینه دوم، تمیز کردن پس از هر آزمایش جداول تمیز را تضمین می کند و مزایای آزمایشی راحت را برای توسعه دهنده ایجاد می کند. هیچ رکورد دیگری هنگام شروع آزمایش وجود ندارد، می توان مطمئن بود که کدام داده مورد پرسش قرار می گیرد و حتی ممکن است وسوسه شود ردیف ها را در طول ادعاها بشمارد. این امر با معایب شدیدی همراه است: هنگام اجرا در حالت چند فرآیندی، آزمایش‌ها احتمالاً با یکدیگر تداخل دارند. در حالی که process-1 جداول را پاک می کند، در همان لحظه پردازش-2 برای داده ها جستجو می کند و با شکست مواجه می شود (زیرا DB به طور ناگهانی توسط فرآیند-1 حذف شده است). علاوه بر این، عیب‌یابی تست‌های شکست خورده سخت‌تر است - بازدید از DB هیچ رکوردی را نشان نمی‌دهد. + +گزینه دوم این است که پس از اتمام تمام فایل های تست (یا حتی روزانه!) پاکسازی کنید. این رویکرد به این معنی است که همان DB با رکوردهای موجود، تمام تست ها و فرآیندها را ارائه می دهد. برای جلوگیری از پا گذاشتن روی انگشتان یکدیگر، آزمون ها باید سوابق خاصی را که اضافه کرده اند اضافه کرده و بر اساس آنها عمل کنند. آیا باید بررسی کنید که رکوردی اضافه شده است؟ فرض کنید هزاران رکورد و پرس و جوی دیگر برای رکوردهایی وجود دارد که به صراحت اضافه شده اند. آیا باید بررسی کنید که یک رکورد حذف شده است؟ نمی توان جدول خالی را فرض کرد، بررسی کنید که این رکورد خاص وجود ندارد. این تکنیک دستاوردهای قدرتمند کمی را به همراه دارد: زمانی که یک توسعه‌دهنده می‌خواهد اتفاقی را که رخ داده است بفهمد - به طور بومی در حالت چند فرآیندی کار می‌کند - داده‌ها آنجا هستند و حذف نمی‌شوند. همچنین شانس یافتن باگ ها را افزایش می دهد زیرا DB پر از رکورد است و به طور مصنوعی خالی نیست. [جدول مقایسه کامل را اینجا ببینید](https://github.com/testjavascript/nodejs-integration-tests-best-practices/blob/master/graphics/db-clean-options.png). +
    + +❌ **در غیر این صورت:** بدون استراتژی برای جدا کردن رکوردها یا تمیز کردن - تست ها روی انگشتان یکدیگر قرار می گیرند. استفاده از تراکنش‌ها فقط برای DB رابطه‌ای کار می‌کند و احتمالاً زمانی که تراکنش‌های درونی وجود دارد، پیچیده می‌شود + +
    + +
    نمونه کد + +
    + +### :clap: تمیز کردن بعد از تمام آزمایشات نه لزوما بعد از هر دویدن. هرچه داده‌های بیشتری در حین انجام آزمایش‌ها داشته باشیم - بیشتر شبیه امتیازات تولید است + +```javascript + //بعد از همه پاکسازی (توصیه می شود) +// global-teardown.js +module.exports = async () => { + // ... + if (Math.ceil(Math.random() * 10) === 10) { + await new OrderRepository().cleanup(); + } +}; +``` + +
    + +
    + +## ⚪ ️2.9 با استفاده از رهگیر HTTP، جزء را از جهان جدا کنید + +:white_check_mark: **انجام دادن:** مؤلفه تحت آزمایش را با رهگیری هر درخواست HTTP خروجی و ارائه پاسخ مورد نظر جدا کنید تا HTTP API همکار آسیب نبیند. Nock یک ابزار عالی برای این ماموریت است زیرا یک نحو مناسب برای تعریف رفتار خدمات خارجی ارائه می دهد. جداسازی برای جلوگیری از نویز و عملکرد آهسته ضروری است، اما بیشتر برای شبیه‌سازی سناریوها و پاسخ‌های مختلف - یک شبیه‌ساز پرواز خوب به معنای رنگ‌آمیزی آسمان آبی شفاف نیست، بلکه ایجاد طوفان و هرج‌ومرج امن است. این در معماری Microservice که در آن تمرکز همیشه باید بر روی یک جزء واحد بدون درگیر کردن بقیه جهان باشد، تقویت شده است. اگرچه می توان رفتار سرویس خارجی را با استفاده از تست دوبل (مسخره) شبیه سازی کرد، اما ترجیحاً کد مستقر شده را لمس نکنید و در سطح شبکه عمل کنید تا تست ها را جعبه سیاه خالص نگه دارید. نقطه ضعف جداسازی عدم تشخیص زمانی که مؤلفه همکار تغییر می کند و متوجه نشدن سوء تفاهم بین دو سرویس است - مطمئن شوید که با استفاده از چند آزمایش قرارداد یا E2E این را جبران کنید. +
    + +❌ **در غیر این صورت:** برخی از سرویس‌ها یک نسخه جعلی را ارائه می‌کنند که می‌تواند توسط تماس‌گیرنده به صورت محلی، معمولاً با استفاده از Docker اجرا شود. برخی از سرویس‌ها محیط «sandbox» را ارائه می‌کنند، بنابراین سرویس واقعی ضربه می‌خورد اما هیچ هزینه یا عوارض جانبی ایجاد نمی‌شود - این کار صدای راه‌اندازی سرویس شخص ثالث را کاهش می‌دهد، اما امکان شبیه‌سازی سناریوها را نیز فراهم نمی‌کند. + +
    + +
    نمونه کد + +
    + +### :clap: جلوگیری از تماس شبکه با اجزای خارجی امکان شبیه سازی سناریوها و به حداقل رساندن نویز را فراهم می کند + +```javascript +// درخواست های API های شخص ثالث را رهگیری کنید و یک پاسخ از پیش تعریف شده را برگردانید +beforeEach(() => { + nock('http://localhost/user/').get(`/1`).reply(200, { + id: 1, + name: 'John', + }); +}); +``` + +
    + +## ⚪ ️2.10 طرح پاسخ را بیشتر در مواقعی که فیلدهای تولید خودکار وجود دارد، آزمایش کنید + +:white_check_mark: **انجام دادن:** هنگامی که ادعا کردن برای داده های خاص غیرممکن است، وجود و انواع فیلد اجباری را بررسی کنید. گاهی اوقات، پاسخ حاوی فیلدهای مهم با داده های پویا است که نمی توان آنها را هنگام نوشتن آزمون پیش بینی کرد، مانند تاریخ ها و افزایش اعداد. اگر قرارداد API وعده می دهد که این فیلدها پوچ نخواهند بود و انواع مناسب را در خود دارند، آزمایش آن ضروری است. اکثر کتابخانه های ادعا از انواع بررسی پشتیبانی می کنند. اگر پاسخ کوچک است، داده های برگشتی را بررسی کنید و با هم در همان ادعا تایپ کنید (به مثال کد مراجعه کنید). یک گزینه دیگر این است که کل پاسخ را در برابر یک سند OpenAPI (Swagger) تأیید کنید. اکثر اجراکنندگان آزمایش دارای پسوندهای جامعه هستند که پاسخ های API را در برابر اسناد آنها تأیید می کند. + + +
    + +❌ **در غیر این صورت:** اگرچه تماس‌گیرنده کد/API به فیلدهایی با داده‌های پویا (مثلاً شناسه، تاریخ) متکی است، اما در عوض نمی‌آید و قرارداد را زیر پا نمی‌گذارد. + +
    + +
    نمونه کد + +
    + +### :clap: با بیان اینکه فیلدهایی با مقدار پویا وجود دارند و نوع مناسبی دارند + +```javascript + test('هنگام اضافه کردن یک سفارش معتبر جدید، سپس باید با 200 پاسخ تأیید مجدد دریافت کنید', async () => { + // ... + //مقایسه کردن + expect(receivedAPIResponse).toMatchObject({ + status: 200, + data: { + id: expect.any(Number), // هر عددی این تست را برآورده می کند + mode: 'approved', + }, + }); +}); +``` + +
    + +
    + +## ⚪ ️2.12 موارد گوشه و آشوب ادغام ها را بررسی کنید + +:white_check_mark: **انجام دادن:** هنگام بررسی ادغام ها، از مسیرهای شاد و غم انگیز فراتر بروید. نه تنها پاسخ های خطا (به عنوان مثال، خطای HTTP 500) بلکه ناهنجاری های سطح شبکه مانند پاسخ های آهسته و به پایان رسیده را بررسی کنید. این ثابت می‌کند که کد انعطاف‌پذیر است و می‌تواند سناریوهای مختلف شبکه را مدیریت کند، مانند انتخاب مسیر درست پس از یک بازه زمانی، هیچ شرایط مسابقه شکننده‌ای ندارد، و حاوی یک قطع کننده مدار برای تلاش‌های مجدد است. ابزارهای رهگیر معتبر به راحتی می توانند رفتارهای مختلف شبکه مانند سرویس های گیج کننده را که گهگاه با شکست مواجه می شوند شبیه سازی کنند. حتی می‌تواند متوجه شود که چه زمانی مقدار زمان‌بندی پیش‌فرض مشتری HTTP از زمان پاسخ شبیه‌سازی‌شده بیشتر است و فوراً بدون انتظار، یک استثنای مهلت زمانی ایجاد کند. + + +
    + +❌ **در عیر این صورت:** تمام تست‌های شما با موفقیت انجام می‌شود، این فقط تولید است که از کار می‌افتد یا وقتی شخص ثالث پاسخ‌های استثنایی ارسال می‌کند، خطاها را به درستی گزارش نمی‌کند. + +
    + +
    نمونه کد + +
    + +### :clap: اطمینان از اینکه در خرابی شبکه، قطع کننده مدار می تواند روز را نجات دهد + +```javascript + test('هنگامی که سرویس کاربران یک بار با 503 پاسخ می دهد و مکانیسم امتحان مجدد اعمال می شود، سفارش با موفقیت اضافه می شود', async () => { + //مقدار دهی کردن + nock.removeInterceptor(userServiceNock.interceptors[0]) + nock('http://localhost/user/') + .get('/1') + .reply(503, undefined, { 'Retry-After': 100 }); + nock('http://localhost/user/') + .get('/1') + .reply(200); + const orderToAdd = { + userId: 1, + productId: 2, + mode: 'approved', + }; + + //اجرا کردن + const response = await axiosAPIClient.post('/order', orderToAdd); + + //مقایسه کردن + expect(response.status).toBe(200); +}); +``` + +
    + +
    + + +## ⚪ ️2.13 پنج نتیجه بالقوه را تست کنید + +:white_check_mark: **انجام دادن:** هنگام برنامه ریزی آزمایشات خود، پنج خروجی جریان معمولی را پوشش دهید. هنگامی که آزمایش شما در حال انجام برخی اقدامات است (به عنوان مثال، فراخوانی API)، واکنشی در حال رخ دادن است، چیزی معنادار رخ می دهد و نیاز به آزمایش دارد. توجه داشته باشید که ما به نحوه کار کردن اهمیت نمی دهیم. تمرکز ما روی نتایج است، چیزهایی که از بیرون قابل توجه هستند و ممکن است بر کاربر تأثیر بگذارند. این پیامدها/واکنش ها را می توان در 5 دسته قرار داد: + +• نتیحه - تست یک عمل را فراخوانی می کند (به عنوان مثال، از طریق API) و یک پاسخ دریافت می کند. اکنون به بررسی صحت داده های پاسخ، طرح و وضعیت HTTP می پردازد + +• state جدید - پس از فراخوانی یک عمل، برخی از داده‌های **در دسترس عموم** احتمالاً اصلاح می‌شوند + +• تماس های داخلی - پس از فراخوانی یک عمل، برنامه ممکن است یک مؤلفه خارجی را از طریق HTTP یا هر انتقال دیگر فراخوانی کند. به عنوان مثال، تماس برای ارسال پیامک، ایمیل یا شارژ کارت اعتباری + +• صفی از پیام ها - نتیجه یک جریان ممکن است یک پیام در یک صف باشد + +• قابلیت مشاهده - برخی از چیزها مانند خطاها یا رویدادهای تجاری قابل توجه باید نظارت شوند. هنگامی که یک تراکنش با شکست مواجه می شود، نه تنها ما انتظار پاسخ مناسب را داریم، بلکه مدیریت صحیح خطا و ثبت/سنجه های مناسب را نیز انتظار داریم. این اطلاعات مستقیماً به یک کاربر بسیار مهم می رود - کاربر ops (یعنی تولید SRE/admin) + + +

    + +# بخش 3️⃣: Frontend تست کردن + +## ⚪ ️ 3.1 UI را از لاجیک برنامه جدا کنید + +:white_check_mark: **انجام دادن:** هنگام تمرکز بر روی تست منطق مؤلفه، جزئیات UI تبدیل به نویز می شود که باید استخراج شود، بنابراین آزمایشات شما می توانند بر روی داده های خالص تمرکز کنند. عملاً داده‌های مورد نظر را از نشانه‌گذاری به روشی انتزاعی استخراج کنید که خیلی با پیاده‌سازی گرافیکی همراه نباشد، فقط روی داده‌های خالص (در مقابل جزئیات گرافیکی HTML/CSS) ادعا کنید و انیمیشن‌هایی را که کند می‌شوند غیرفعال کنید. ممکن است وسوسه شوید که از رندر کردن خودداری کنید و فقط قسمت پشتی رابط کاربری (مانند سرویس‌ها، اقدامات، فروشگاه) را آزمایش کنید، اما این منجر به آزمایش‌های تخیلی می‌شود که به واقعیت شباهت ندارند و مواردی را که داده‌های مناسب وجود ندارد را نشان نمی‌دهد. حتی وارد UI شوید + +
    + +❌ **در غیر این صورت:** داده‌های محاسباتی خالص آزمون شما ممکن است در 10 میلی‌ثانیه آماده باشد، اما پس از آن کل آزمون به دلیل برخی انیمیشن‌های فانتزی و نامربوط، 500 میلی‌ثانیه (100 تست = 1 دقیقه) طول خواهد کشید. + +
    + +
    نمونه کد + +
    + +### :clap: متال درست: جدا کردن جزئیات UI + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +test("هنگامی که لیست کاربران برای نشان دادن فقط VIP پرچم گذاری می شود، باید فقط اعضای VIP نمایش داده شود", () => { + // مقدار دهی کردن + const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; + + // اجرا کردن + const { getAllByTestId } = render(); + + // مفایسه کردن - ابتدا داده ها را از UI استخراج کنید + const allRenderedUsers = getAllByTestId("user").map(uiElement => uiElement.textContent); + const allRealVIPUsers = allUsers.filter(user => user.vip).map(user => user.name); + expect(allRenderedUsers).toEqual(allRealVIPUsers); //داده ها را با داده ها مقایسه کنید، اینجا رابط کاربری وجود ندارد +}); +``` + +
    + +### :thumbsdown: مثال ضد الگو: جزئیات و داده های رابط کاربری ترکیبی ادعا + +```javascript +test("هنگام پرچم گذاری برای نمایش فقط VIP، باید فقط اعضای VIP نمایش داده شود", () => { + // مقداری دهی کردن + const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; + + // اجرا کردن + const { getAllByTestId } = render(); + + // مقابسه کردن - ترکیب ui و data در این قسمت + expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); +}); +``` + +
    + +

    + +## ⚪ ️ 3.2 پرس و جو از عناصر HTML بر اساس ویژگی هایی که بعید است تغییر کنند + +:white_check_mark: **انجام دادن:** عناصر HTML را بر اساس ویژگی هایی که احتمالاً بر خلاف انتخابگرهای CSS و مانند برچسب های فرم، در تغییرات گرافیکی باقی می مانند، پرس و جو کنید. اگر عنصر تعیین‌شده چنین ویژگی‌هایی را ندارد، یک ویژگی تست اختصاصی مانند «test-id-submit-button» ایجاد کنید. پیمودن این مسیر نه تنها تضمین می‌کند که تست‌های عملکردی/منطقی شما هرگز به دلیل تغییرات ظاهری و احساسی شکسته نمی‌شوند، بلکه برای کل تیم مشخص می‌شود که این عنصر و ویژگی توسط تست ‌ها استفاده می‌شوند و نباید حذف شوند. + +
    + +❌ **در غیر این صورت:** شما می‌خواهید عملکرد ورود به سیستم را که شامل بسیاری از مؤلفه‌ها، منطق و سرویس‌ها می‌شود، آزمایش کنید، همه چیز به‌خوبی تنظیم شده است - موارد خرد، جاسوس‌ها، تماس‌های Ajax جدا شده‌اند. همه عالی به نظر می رسند سپس تست شکست می خورد زیرا طراح کلاس div CSS را از "thick-border" به "thin-border" تغییر داده است.' + +
    + +
    نمونه کد + +
    + +### :clap: انجام درست مثال: پرس و جو از یک عنصر با استفاده از یک ویژگی اختصاصی برای تست + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") + +```html +// کد نشانه گذاری (بخشی از مؤلفه React) +

    + + {value} + + +

    +``` + +```javascript +// این مثال از react-testing-library استفاده می کند +test("هر زمان که هیچ داده ای به متریک منتقل نمی شود، 0 را به عنوان پیش فرض نشان دهید", () => { + // مقدار دهی کردن + const metricValue = undefined; + + // اجرا کردن + const { getByTestId } = render(); + + expect(getByTestId("errorsLabel").text()).toBe("0"); +}); +``` + +
    + +### :thumbsdown: مثال ضد الگو: تکیه بر ویژگی های CSS + +```html + +{value} + +``` + +```javascript +// این مثال استفاده از آنزیم است +test("هر زمان که هیچ داده ای ارسال نشود، معیار خطا صفر را نشان می دهد", () => { + // ... + + expect(wrapper.find("[className='d-flex-column']").text()).toBe("0"); +}); +``` + +
    + +
    + +## ⚪ ️ 3.3 در صورت امکان، با یک جزء واقعی و کاملا رندر شده تست کنید + +:white_check_mark: **انجام دادن:** هر زمان که اندازه معقولی داشتید، مؤلفه خود را از خارج مانند کاربرانتان آزمایش کنید، UI را به طور کامل رندر کنید، بر اساس آن عمل کنید و ادعا کنید که رابط کاربری رندر شده مطابق انتظار عمل می کند. از هر گونه رندر تمسخر آمیز، جزئی و کم عمق خودداری کنید - این روش ممکن است به دلیل کمبود جزئیات منجر به اشکالات محفوظ نشده و تعمیر و نگهداری سخت تر شود زیرا آزمایش ها با اجزای داخلی به هم می خورند (به گلوله مراجعه کنید ['از تست جعبه سیاه حمایت کنید'](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-14-stick-to-black-box-testing-test-only-public-methods)). اگر یکی از اجزای کودک به طور قابل توجهی کند می شود (مثلاً انیمیشن) یا تنظیمات را پیچیده می کند - به طور واضح آن را با یک جعلی جایگزین کنید. + +با تمام آنچه گفته شد، یک کلمه احتیاط لازم است: این تکنیک برای اجزای کوچک/متوسط ​​که اندازه معقولی از اجزای کودک را در خود جای می دهند، کار می کند. رندر کردن کامل یک مؤلفه با تعداد زیاد فرزندان، استدلال در مورد شکست تست (تحلیل علت ریشه) را دشوار می کند و ممکن است بسیار کند شود. در چنین مواردی، فقط چند آزمایش بر روی آن جزء والد چاق بنویسید و آزمایش های بیشتری را بر روی فرزندان آن بنویسید + +
    + +❌ **در غیر این صورت:** هنگامی که با فراخوانی روش‌های خصوصی و بررسی وضعیت درونی یک کامپوننت به درونی آن وارد می‌شوید - باید همه آزمایش‌ها را هنگام بازسازی اجرای مؤلفه‌ها مجدداً تغییر دهید. آیا واقعاً برای این سطح از نگهداری ظرفیت دارید؟? + +
    + +
    نمونه کد + +
    + +### :clap: انجام درست آن مثال: کار به صورت واقع بینانه با یک جزء کاملاً رندر شده + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Enzyme-blue.svg "Examples with Enzyme") + +```javascript +class Calendar extends React.Component { + static defaultProps = { showFilters: false }; + + render() { + return ( +
    + A filters panel with a button to hide/show filters + +
    + ); + } +} + +//برای مثال از React & Enzyme استفاده می شود +test("رویکرد واقع گرایانه: هنگامی که برای نمایش فیلترها کلیک کنید، فیلترها نمایش داده می شوند", () => { + // مقدار دهی کردن + const wrapper = mount(); + + // اجرا کردن + wrapper.find("button").simulate("click"); + + // مقابسه کردن + expect(wrapper.text().includes("Choose Filter")); + // به این صورت است که کاربر به این عنصر نزدیک می شود: توسط متن +}); +``` + +### :thumbsdown: مثال ضد الگو: تمسخر واقعیت با رندر کم عمق + +```javascript +test("رویکرد کم عمق/ مسخره شده: وقتی برای نمایش فیلترها کلیک کنید، فیلترها نمایش داده می شوند", () => { + // مقدار دهی کردن + const wrapper = shallow(); + + // اجرا کردن + wrapper + .find("filtersPanel") + .instance() + .showFilters(); + // Tap into the internals, bypass the UI and invoke a method. White-box approach + + // مقایسه کردن + expect(wrapper.find("Filter").props()).toEqual({ title: "Choose Filter" }); + // اگر نام پروپوزال را تغییر دهیم یا چیزی مربوطه را پاس نکنیم چه؟ +}); +``` + +
    + +
    + +## ⚪ ️ 3.4 نخوابید، از framework‌های داخلی پشتیبانی برای رویدادهای همگام استفاده کنید. همچنین سعی کنید کارها را تسریع کنید + +:white_check_mark: **انجام دادن:** در بسیاری از موارد، واحد تحت آزمایش زمان کامل ناشناخته است (مثلاً انیمیشن ظاهر عنصر را به حالت تعلیق در می‌آورد) - در این صورت، از خوابیدن خودداری کنید (مثلاً setTimeOut) و روش‌های قطعی‌تری را که اکثر پلتفرم‌ها ارائه می‌دهند ترجیح دهید. برخی از کتابخانه ها امکان انتظار برای عملیات را فراهم می کنند (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), سایر API برای انتظار مانند [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). گاهی اوقات یک راه ظریف تر، خرد کردن منبع کند است، مانند API، و سپس زمانی که لحظه پاسخ قطعی شد، مولفه می تواند به صراحت دوباره ارائه شود. هنگامی که به برخی از اجزای خارجی که می خوابند وابسته می شوند، ممکن است مفید باشد [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). خوابیدن الگویی است که باید از آن اجتناب کنید زیرا باعث می شود آزمون شما آهسته یا پرخطر باشد (زمانی که برای مدت کوتاهی منتظر می مانید). هر زمان که خواب و نظرسنجی اجتناب ناپذیر است و هیچ پشتیبانی از چارچوب تست وجود ندارد، برخی از کتابخانه های npm مانند [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) می تواند با یک راه حل نیمه قطعی کمک کند +
    + +❌ **در غیر این صورت:** هنگام خواب طولانی مدت، آزمایش ها یک مرتبه کندتر خواهند بود. هنگامی که سعی می کنید برای تعداد کمی بخوابید، زمانی که واحد تحت آزمایش به موقع پاسخ ندهد، آزمایش با شکست مواجه می شود. بنابراین به یک مبادله بین پوسته پوسته شدن و عملکرد بد خلاصه می شود + +
    + +
    نمونه کد + +
    + +### :clap: انجام درست مثال: API E2E که فقط زمانی که عملیات همگام‌سازی انجام شود برطرف می‌شود (Cypress) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +// استفاده كردن از Cypress +cy.get("#show-products").click(); // navigate +cy.wait("@products"); // صبر کنید تا مسیر ظاهر شود +// این خط تنها زمانی اجرا می شود که مسیر آماده باشد +``` + +### :clap: انجام درست مثال: آزمایش کتابخانه ای که منتظر عناصر DOM است + +```javascript +// @testing-library/dom +test("movie title appears", async () => { + // عنصر در ابتدا وجود ندارد ... + + //منتظر ظهور باشید + await wait(() => { + expect(getByText("the lion king")).toBeInTheDocument(); + }); + + // منتظر ظاهر شدن باشید و عنصر را برگردانید + const movie = await waitForElement(() => getByText("the lion king")); +}); +``` + +### :thumbsdown: مثال ضد الگو: کد خواب سفارشی + +```javascript +test("movie title appears", async () => { + // عنصر در ابتدا وجود ندارد ... + + // منطق انتظار سفارشی (احتیاط: ساده، بدون مهلت زمانی) + const interval = setInterval(() => { + const found = getByText("the lion king"); + if (found) { + clearInterval(interval); + expect(getByText("the lion king")).toBeInTheDocument(); + } + }, 100); + + // منتظر ظاهر شدن باشید و عنصر را برگردانید + const movie = await waitForElement(() => getByText("the lion king")); +}); +``` + +
    + +
    + +## ⚪ ️ 3.5 مراقب نحوه ارائه محتوا از طریق شبکه باشید + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") + +✅ **انجام دادن:** برخی از مانیتورهای فعال را اعمال کنید که تضمین می‌کند بارگذاری صفحه در شبکه واقعی بهینه شده است - این شامل هر گونه نگرانی UX مانند بارگذاری صفحه آهسته یا بسته‌ای کوچک نشده می‌شود. بازار ابزار بازرسی کوتاه نیست: ابزارهای اساسی مانند [pingdom](https://www.pingdom.com/), AWS CloudWatch, [gcp StackDriver](https://cloud.google.com/monitoring/uptime-checks/) can be easily configured to watch whether the server is alive and response under a reasonable SLA. This only scratches the surface of what might get wrong, hence it's preferable to opt for tools that specialize in frontend (e.g. [lighthouse](https://developers.google.com/web/tools/lighthouse/), [pagespeed](https://developers.google.com/speed/pagespeed/insights/)) and perform richer analysis. The focus should be on symptoms, metrics that directly affect the UX, like page load time, [meaningful paint](https://scotch.io/courses/10-web-performance-audit-tips-for-your-next-billion-users-in-2018/fmp-first-meaningful-paint), [time until the page gets interactive (TTI)](https://calibreapp.com/blog/time-to-interactive/). On top of that, one may also watch for technical causes like ensuring the content is compressed, time to the first byte, optimize images, ensuring reasonable DOM size, SSL and many others. It's advisable to have these rich monitors both during development, as part of the CI and most important - 24x7 over the production's servers/CDN + +
    + +❌ **در غیر این صورت:** باید ناامید کننده باشد که متوجه شوید پس از چنین دقت زیادی برای ایجاد یک UI، تست های عملکردی 100% گذرانده شده و بسته بندی پیچیده - UX به دلیل پیکربندی اشتباه CDN وحشتناک و کند است. + +
    + +
    نمونه کد + +### :clap: انجام درست مثال: گزارش بازرسی بارگذاری صفحه فانوس دریایی + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + +
    + +
    + +## ⚪ ️ 3.6 منابع ضعیف و کندی مانند APIهای Backend را جمع آوری کنید + +:white_check_mark: **انجام دادن:** هنگام کدنویسی تست‌های اصلی خود (نه تست‌های E2E)، از درگیر کردن هر منبعی که خارج از مسئولیت و کنترل شماست مانند API پشتیبان خودداری کنید و به جای آن از خرده‌ها استفاده کنید (یعنی تست دوبار). عملا، به جای تماس های شبکه واقعی با API ها، از چند کتابخانه دوتایی (مانند [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) برای کلفت کردن پاسخ API. مزیت اصلی جلوگیری از پوسته پوسته شدن است - آزمایش یا مرحله‌بندی APIها طبق تعریف چندان پایدار نیستند و هر از چند گاهی در تست‌های شما شکست می‌خورند، اگرچه مؤلفه شما به خوبی رفتار می‌کند (انواع تولید برای آزمایش در نظر گرفته نشده است و معمولاً درخواست‌ها را کاهش می‌دهد). انجام این کار به شبیه‌سازی رفتارهای API مختلف اجازه می‌دهد که رفتار مؤلفه شما را مانند زمانی که هیچ داده‌ای پیدا نمی‌شود یا زمانی که API خطا می‌دهد، هدایت کند. آخرین اما نه کم‌اهمیت، تماس‌های شبکه سرعت آزمایش‌ها را بسیار کند می‌کند + +
    + +❌ **در غیر این صورت:** میانگین تست بیشتر از چند میلی ثانیه اجرا نمی شود، یک تماس API معمولی 100 میلی ثانیه طول می کشد>، این باعث می شود هر تست 20 برابر کندتر شود. + +
    + +
    نمونه کد + +
    + +### :clap: انجام درست مثال: قطع کردن یا رهگیری تماس‌های API + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +// واحد در حال آزمایش +export default function ProductsList() { + const [products, setProducts] = useState(false); + + const fetchProducts = async () => { + const products = await axios.get("api/products"); + setProducts(products); + }; + + useEffect(() => { + fetchProducts(); + }, []); + + return products ?
    {products}
    :
    No products
    ; +} + +// تست کردن +test("When no products exist, show the appropriate message", () => { + // مقدار دهی کردن + nock("api") + .get(`/products`) + .reply(404); + + // اجرا کردن + const { getByTestId } = render(); + + // مقایسه کردن + expect(getByTestId("no-products-message")).toBeTruthy(); +}); +``` + +
    + +
    + +## ⚪ ️ 3.7 تعداد بسیار کمی از تست های سرتاسری که کل سیستم را در بر می گیرد + +:white_check_mark: **انجام دادن:** اگرچه E2E (پایان به انتها) معمولاً به معنای آزمایش فقط UI با یک مرورگر واقعی است (نگاه کنید به [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)), برای دیگر آنها به معنای آزمایش هایی هستند که کل سیستم از جمله باطن واقعی را گسترش می دهند. تست‌های نوع دوم بسیار ارزشمند هستند، زیرا اشکالات یکپارچه‌سازی بین frontend و backend را پوشش می‌دهند که ممکن است به دلیل درک اشتباه از طرح مبادله رخ دهد. آنها همچنین یک روش کارآمد برای کشف مسائل یکپارچه سازی backend-to-backend هستند (مثلاً Microservice A پیام اشتباهی را به Microservice B ارسال می کند) و حتی برای تشخیص خرابی های استقرار - هیچ چارچوب Backend برای آزمایش E2E وجود ندارد که به اندازه UI دوستانه و بالغ باشد. چارچوب هایی مانند[Cypress](https://www.cypress.io/) و [Puppeteer](https://github.com/GoogleChrome/puppeteer). نقطه ضعف چنین آزمایش‌هایی هزینه بالای پیکربندی یک محیط با اجزای بسیار زیاد و بیشتر شکنندگی آنها است - با توجه به 50 میکروسرویس، حتی اگر یکی از آنها ناموفق باشد، کل E2E فقط از کار افتاده است. به همین دلیل، ما باید از این تکنیک کم استفاده کنیم و احتمالاً 1-10 مورد از آن‌ها را داشته باشیم و نه بیشتر. گفته می‌شود، حتی تعداد کمی از تست‌های E2E احتمالاً نوع مشکلاتی را که برای آنها هدف قرار گرفته‌اند - خطاهای استقرار و یکپارچه‌سازی را شناسایی می‌کنند. توصیه می‌شود آن‌ها را روی یک محیط صحنه‌سازی شبیه تولید اجرا کنید + +
    + +❌ **در غیر این صورت:** UI ممکن است برای آزمایش عملکرد خود سرمایه گذاری زیادی کند تا دیرتر متوجه شود که بار بازگشتی بازگشتی (شمایه داده ای که UI باید با آن کار کند) بسیار متفاوت از آنچه انتظار می رود است. + +
    + +## ⚪ ️ 3.8 با استفاده مجدد از اعتبارنامه ورود به سیستم، تست های E2E را افزایش دهید + +:white_check_mark: **انجام دادن:** در تست‌های E2E که شامل یک باطن واقعی است و برای فراخوانی‌های API به یک توکن کاربر معتبر متکی است، جداسازی آزمون در سطحی که کاربر ایجاد شده و در هر درخواستی به سیستم وارد شود، سودی ندارد. در عوض، قبل از شروع اجرای آزمایش‌ها فقط یک بار وارد شوید (یعنی قبل از همه قلاب)، توکن را در برخی از حافظه‌های محلی ذخیره کنید و در درخواست‌ها مجدداً از آن استفاده کنید. به نظر می رسد که این یکی از اصول اصلی آزمایش را نقض می کند - تست را بدون جفت شدن منابع مستقل نگه دارید. در حالی که این یک نگرانی معتبر است، عملکرد در تست‌های E2E یک نگرانی کلیدی است و ایجاد 1-3 درخواست API قبل از شروع هر آزمایش ممکن است منجر به زمان اجرای وحشتناک شود. استفاده مجدد از اعتبارنامه‌ها به این معنی نیست که آزمایش‌ها باید روی سوابق کاربر یکسانی عمل کنند - اگر به سوابق کاربر (مثلاً سابقه پرداخت‌های کاربر آزمایشی) تکیه می‌کنند، مطمئن شوید که آن سوابق را به عنوان بخشی از آزمایش ایجاد کرده‌اید و از اشتراک‌گذاری وجود آنها با سایر آزمایش‌ها اجتناب کنید. همچنین به یاد داشته باشید که Backend می‌تواند جعلی باشد - اگر آزمایش‌های شما بر روی frontend متمرکز شده‌اند، بهتر است آن را ایزوله کنید و API باطن را خرد کنید (نگاه کنید به [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)). + +
    + +❌ **در غیر این صورت:** با توجه به 200 مورد تست و با فرض ورود = 100ms = 20 ثانیه فقط برای ورود مجدد و دوباره + +
    + +
    نمونه کد + +
    + +### :clap: انجام درست آن مثال: قبل از همه وارد شوید و نه قبل از هر کدام + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +let authenticationToken; + +// قبل از اجرای همه آزمایشات اتفاق می افتد +before(() => { + cy.request('POST', 'http://localhost:3000/login', { + username: Cypress.env('username'), + password: Cypress.env('password'), + }) + .its('body') + .then((responseFromLogin) => { + authenticationToken = responseFromLogin.token; + }) +}) + +// قبل از هر آزمون اتفاق می افتد +beforeEach(setUser => { + cy.visit('/home', () => { + onBeforeLoad (win => { + win.localStorage.setItem('token', JSON.stringify(authenticationToken)) + }) + }) +}) +``` + +
    + +
    + +## ⚪ ️ 3.9 یک تست دود E2E داشته باشید که فقط در سراسر نقشه سایت حرکت می کند + +:white_check_mark: **انجام دادن:** برای نظارت بر تولید و بررسی سلامت زمان توسعه، یک تست E2E را اجرا کنید که از همه/بیشتر صفحات سایت بازدید می کند و مطمئن می شود که هیچ کس خراب نمی شود. این نوع تست بازگشت سرمایه زیادی را به ارمغان می آورد زیرا نوشتن و نگهداری آن بسیار آسان است، اما می تواند هر نوع خرابی از جمله مشکلات عملکردی، شبکه و استقرار را تشخیص دهد. سایر سبک‌های بررسی دود و سلامت عقل چندان قابل اعتماد و جامع نیستند - برخی از تیم‌های عملیاتی فقط صفحه اصلی (تولید) را پینگ می‌کنند یا توسعه‌دهندگانی که تست‌های ادغام زیادی را انجام می‌دهند که مشکلات بسته‌بندی و مرورگر را کشف نمی‌کنند. ناگفته نماند که تست دود جایگزین تست های عملکردی نمی شود، بلکه فقط به عنوان یک آشکارساز سریع دود عمل می کند. + +
    + +❌ **در غیر این صورت:** ممکن است همه چیز عالی به نظر برسد، همه آزمایش‌ها با موفقیت انجام می‌شوند، بررسی سلامت تولید نیز مثبت است، اما مؤلفه پرداخت مشکلی در بسته‌بندی داشت و فقط مسیر پرداخت / رندر نمی‌شود. + +
    + +
    نمونه کد + +
    + +### :clap: انجام درست مثال: smoke در تمام صفحات حرکت می کند + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +it("هنگام انجام تست smoke در تمام صفحات، باید همه آنها را با موفقیت بارگیری کنید", () => { + // نمونه ای با استفاده از Cypress است اما می توان آن را به راحتی اجرا کرد + // با استفاده از هر مجموعه E2E + cy.visit("https://mysite.com/home"); + cy.contains("Home"); + cy.visit("https://mysite.com/Login"); + cy.contains("Login"); + cy.visit("https://mysite.com/About"); + cy.contains("About"); +}); +``` + +
    + +
    + +## ⚪ ️ 3.10 آزمایش ها را به عنوان یک سند مشارکتی زنده در معرض دید قرار دهید + +:white_check_mark: **انجام دادن:** علاوه بر افزایش قابلیت اطمینان برنامه، آزمایش‌ها فرصت جذاب دیگری را به روی میز می‌آورند - به عنوان مستندات برنامه زنده ارائه می‌شوند. از آنجایی که تست‌ها ذاتاً با زبانی کمتر فنی و محصول/UX صحبت می‌کنند، با استفاده از ابزارهای مناسب می‌توانند به عنوان یک مصنوع ارتباطی عمل کنند که تا حد زیادی همه همتایان - توسعه‌دهندگان و مشتریانشان را همسو می‌کند. برای مثال، برخی از چارچوب‌ها امکان بیان جریان و انتظارات (یعنی طرح آزمایش‌ها) را با استفاده از زبانی قابل خواندن برای انسان فراهم می‌کنند تا هر ذینفع، از جمله مدیران محصول، بتوانند آزمایش‌هایی را که به‌تازگی به سند نیازمندی‌های زنده تبدیل شده‌اند، بخوانند، تأیید کنند و در آن همکاری کنند. این تکنیک همچنین به عنوان "آزمون پذیرش" نامیده می شود زیرا به مشتری اجازه می دهد معیارهای پذیرش خود را به زبان ساده تعریف کند. این هست [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) در خالص ترین شکل خود یکی از فریمورک های محبوبی که این امکان را فراهم می کند این است [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), مثال زیر را ببینید یک فرصت مشابه اما متفاوت دیگر, [StoryBook](https://storybook.js.org/), اجازه می دهد تا مؤلفه های رابط کاربری را به عنوان یک کاتالوگ گرافیکی در معرض دید قرار دهید، جایی که می توانید در حالت های مختلف هر مؤلفه قدم بزنید (مثلاً یک شبکه بدون فیلتر ارائه دهید، آن شبکه را با چندین ردیف یا با هیچ کدام و غیره ارائه کنید)، ببینید چگونه به نظر می رسد و چگونه است. برای راه اندازی آن حالت - این می تواند برای افراد محصول نیز جذاب باشد، اما بیشتر به عنوان سند زنده برای توسعه دهندگانی که آن اجزا را مصرف می کنند، عمل می کند. + +❌ **در غیر این صورت:** پس از سرمایه گذاری منابع برتر در آزمایش، فقط حیف است که از این سرمایه گذاری استفاده نکنید و ارزش زیادی کسب نکنید + +
    + +
    نمونه کد + +
    + +### :clap: انجام درست آن مثال: توصیف تست ها به زبان انسان با استفاده از cucumber-js + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") + +```text +اینگونه می توان تست ها را با استفاده از cucumber توصیف کرد: زبانی ساده که به هر کسی اجازه می دهد درک کند و همکاری کند. + +Feature: Twitter new tweet + + I want to tweet something in Twitter + + @focus + Scenario: Tweeting from the home page + Given I open Twitter home + Given I click on "New tweet" button + Given I type "Hello followers!" in the textbox + Given I click on "Submit" button + Then I see message "Tweet saved" +``` + +### :clap: انجام درست مثال: تجسم اجزای ما، حالت‌های مختلف و ورودی‌های آنها با استفاده از Storybook + +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 مشکلات بصری را با ابزارهای خودکار تشخیص دهید + +:white_check_mark: **انجام دادن:** ابزارهای خودکار را برای گرفتن اسکرین شات های رابط کاربری در هنگام ارائه تغییرات تنظیم کنید و مشکلات بصری مانند همپوشانی یا شکستن محتوا را شناسایی کنید. این تضمین می‌کند که نه تنها داده‌های مناسب آماده می‌شوند، بلکه کاربر نیز می‌تواند به راحتی آن‌ها را ببیند. این تکنیک به طور گسترده مورد استفاده قرار نگرفته است، طرز فکر تست ما به سمت تست‌های عملکردی گرایش دارد، اما تصاویر آن چیزی است که کاربر تجربه می‌کند و با انواع دستگاه‌های بسیار، نادیده گرفتن برخی از باگ‌های ناخوشایند UI بسیار آسان است. برخی از ابزارهای رایگان می توانند اصول اولیه را فراهم کنند - اسکرین شات ها را برای بازرسی چشم انسان تولید و ذخیره کنند. اگرچه این رویکرد ممکن است برای برنامه‌های کوچک کافی باشد، اما مانند هر آزمایش دستی دیگری که هر زمان که چیزی تغییر می‌کند به نیروی انسانی نیاز دارد، ناقص است. از سوی دیگر، به دلیل نداشتن تعریف واضح، تشخیص خودکار مسائل رابط کاربری بسیار چالش برانگیز است - اینجاست که میدان "رگرسیون بصری" به صدا در می آید و با مقایسه رابط کاربری قدیمی با آخرین تغییرات و تشخیص تفاوت ها، این معما را حل می کند. برخی از ابزارهای OSS/رایگان می توانند برخی از این قابلیت ها را ارائه دهند (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) اما ممکن است زمان راه اندازی قابل توجهی را شارژ کند. خط تجاری ابزار (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) با هموارسازی نصب و بسته‌بندی ویژگی‌های پیشرفته مانند رابط کاربری مدیریت، هشدار، ضبط هوشمند با حذف نویز بصری (مانند تبلیغات، انیمیشن‌ها) و حتی تجزیه و تحلیل علت اصلی تغییرات DOM/CSS که منجر به این مشکل شده است، یک قدم بیشتر است. + +
    + +❌ **در غیر این صورت:** صفحه محتوایی که محتوای عالی را نمایش می‌دهد چقدر خوب است (100٪ تست‌ها با موفقیت انجام شد)، فورا بارگیری می‌شود اما نیمی از منطقه محتوا پنهان است.? + +
    + +
    نمونه کد + +
    + +### :thumbsdown: مثال ضد الگو: یک رگرسیون بصری معمولی - محتوای درستی که بد ارائه می شود + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + +### :clap: انجام درست مثال: پیکربندی wraith برای گرفتن و مقایسه عکس های فوری UI + +![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") + +``` +​# Add as many domains as necessary. Key will act as a label​ + +domains: + english: "http://www.mysite.com"​ + +​# Type screen widths below, here are a couple of examples​ + +screen_widths: + + - 600​ + - 768​ + - 1024​ + - 1280​ + +​# Type page URL paths below, here are a couple of examples​ +paths: + about: + path: /about + selector: '.about'​ + subscribe: + selector: '.subscribe'​ + path: /subscribe +``` + +### :clap: انجام درست مثال: استفاده از Applitools برای مقایسه عکس فوری و سایر ویژگی های پیشرفته + +![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg "Using Applitools") ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +import * as todoPage from "../page-objects/todo-page"; + +describe("visual validation", () => { + before(() => todoPage.navigate()); + beforeEach(() => cy.eyesOpen({ appName: "TAU TodoMVC" })); + afterEach(() => cy.eyesClose()); + + it("should look good", () => { + cy.eyesCheckWindow("empty todo list"); + todoPage.addTodo("Clean room"); + todoPage.addTodo("Learn javascript"); + cy.eyesCheckWindow("two todos"); + todoPage.toggleTodo(0); + cy.eyesCheckWindow("mark as completed"); + }); +}); +``` + +
    + +

    + +# بخش 4️⃣: اندازه گیری اثربخشی آزمون + +

    + +## ⚪ ️ 4.1 پوشش کافی برای داشتن اعتماد به نفس داشته باشید، به نظر می رسد 80٪ عدد خوش شانس باشد + +:white_check_mark: **انجام دادن:** هدف از تست گرفتن اعتماد به نفس کافی برای حرکت سریع است، بدیهی است که هر چه کد بیشتر تست شود، تیم می تواند اعتماد بیشتری داشته باشد. پوشش معیاری است از تعداد خطوط کد (و شاخه ها، دستورات و غیره) که توسط آزمایش ها به دست می آیند. پس چقدر کافی است؟ 10-30٪ واضح است که برای درک درستی ساخت بسیار کم است، از طرف دیگر 100٪ بسیار گران است و ممکن است تمرکز شما را از مسیرهای مهم به گوشه های عجیب و غریب کد تغییر دهد. پاسخ طولانی این است که به عوامل زیادی مانند نوع برنامه بستگی دارد - اگر شما در حال ساخت نسل بعدی ایرباس A380 هستید، 100٪ ضروری است، برای یک وب سایت تصاویر کارتونی 50٪ ممکن است خیلی زیاد باشد. اگرچه اکثر علاقه مندان به تست ادعا می کنند که آستانه پوشش مناسب متنی است، اکثر آنها عدد 80٪ را نیز به عنوان یک قانون ذکر می کنند. ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) که احتمالاً باید اکثر برنامه ها را برآورده کند. + +نکات پیاده سازی: ممکن است بخواهید ادغام پیوسته (CI) خود را برای داشتن آستانه پوشش پیکربندی کنید. ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) و ساختنی را که مطابق با این استاندارد نیست متوقف کنید (همچنین می توان آستانه را برای هر جزء پیکربندی کرد، به مثال کد زیر مراجعه کنید). علاوه بر این، تشخیص کاهش پوشش ساخت را در نظر بگیرید (زمانی که یک کد جدید متعهد شده دارای پوشش کمتری است)— این باعث می‌شود توسعه‌دهندگان مقدار کد تست شده را افزایش دهند یا حداقل حفظ کنند. همه آنچه گفته شد، پوشش تنها یک معیار است، یک معیار مبتنی بر کمی، که برای نشان دادن استحکام آزمایش شما کافی نیست. و همچنین همانطور که در گلوله های بعدی نشان داده شده است می توان آن را فریب داد + +
    + +❌ **در غیر این صورت:** اعتماد به نفس و اعداد دست به دست هم می دهند، بدون اینکه واقعاً بدانید که بیشتر سیستم را آزمایش کرده اید - همچنین مقداری ترس وجود خواهد داشت و ترس سرعت شما را کاهش می دهد. + +
    + +
    نمونه کد + +
    + +### :clap: مثال: یک گزارش پوشش معمولی + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: انجام درست آن مثال: تنظیم پوشش برای هر جزء (با استفاده از Jest) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") + +![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest)") + +
    + +

    + +## ⚪ ️ 4.2 گزارش های پوشش را برای شناسایی مناطق آزمایش نشده و سایر موارد عجیب بازرسی کنید + +:white_check_mark: **انجام دادن:** برخی از مسائل دقیقاً زیر رادار پنهان می شوند و با استفاده از ابزارهای سنتی پیدا کردن آنها واقعاً سخت است. اینها واقعاً باگ نیستند، بلکه بیشتر رفتارهای شگفت انگیز برنامه هستند که ممکن است تأثیر شدیدی داشته باشند. به عنوان مثال، اغلب برخی از مناطق کد هرگز یا به ندرت فراخوانی نمی شوند - شما فکر می کنید که کلاس "PricingCalculator" همیشه قیمت محصول را تعیین می کند، اما به نظر می رسد که در واقع هرگز فراخوانی نمی شود، اگرچه ما 10000 محصول در DB داریم و تعداد زیادی فروش... پوشش کد گزارش‌ها به شما کمک می‌کنند متوجه شوید که آیا برنامه به‌گونه‌ای که شما فکر می‌کنید رفتار می‌کند یا خیر. به غیر از آن، همچنین می‌تواند مشخص کند که کدام نوع کد آزمایش نشده است - با اطلاع از اینکه 80 درصد کد آزمایش شده است، نمی‌گوید که آیا قسمت‌های حیاتی پوشش داده شده‌اند یا خیر. ایجاد گزارش آسان است - فقط برنامه خود را در مرحله تولید یا در حین آزمایش با ردیابی پوشش اجرا کنید و سپس گزارش‌های رنگارنگی را مشاهده کنید که نشان می‌دهد تعداد دفعات فراخوانی هر ناحیه کد مشخص می‌شود. اگر وقت خود را صرف نگاهی اجمالی به این داده ها کنید - ممکن است چند اشتباه پیدا کنید +
    + +❌ **در غیر این صورت:** اگر نمی‌دانید کدام بخش از کدتان آزمایش نشده است، نمی‌دانید این مشکلات از کجا می‌آیند. + +
    + +
    نمونه کد + +
    + +### :thumbsdown: مثال ضد الگو: این گزارش پوشش چه اشکالی دارد؟ + +بر اساس یک سناریوی واقعی که در آن ما استفاده از برنامه خود را در QA ردیابی کردیم و الگوهای ورود جالبی را پیدا کردیم (نکته: میزان خرابی های ورود به سیستم نامتناسب است، چیزی به وضوح اشتباه است. در نهایت مشخص شد که برخی از باگ های frontend مدام به سیستم وارد می شوند. API ورود باطن) + +![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report?") + +
    + +

    + +## ⚪ ️ 4.3 اندازه گیری پوشش منطقی با استفاده از تست جهش + +:white_check_mark: **انجام دادن:** معیار پوشش سنتی اغلب دروغ می گوید: ممکن است پوشش 100٪ کد را به شما نشان دهد، اما هیچ یک از توابع شما، حتی یک مورد، پاسخ مناسب را نشان نمی دهد. چطور؟ آن را به سادگی اندازه گیری می کند که از کدام خطوط کد تست بازدید کرده است، اما بررسی نمی کند که آیا آزمون ها واقعاً چیزی را آزمایش کرده اند - برای پاسخ درست اظهار شده است. مانند کسی که برای تجارت سفر می کند و مهر پاسپورت خود را نشان می دهد - این نشان دهنده انجام هیچ کاری نیست، فقط اینکه او از تعداد کمی از فرودگاه ها و هتل ها بازدید کرده است. + +آزمایش مبتنی بر جهش با اندازه‌گیری مقدار کدی که واقعاً تست شده است و نه فقط بازدید شده، به شما کمک می‌کند.. [Stryker](https://stryker-mutator.io/) یک کتابخانه جاوا اسکریپت برای آزمایش جهش است و پیاده سازی آن واقعاً منظم است: + +(1) به عمد کد را تغییر می دهد و "اشکالات گیاهی" را ایجاد می کند. برای مثال کد newOrder.price===0 به newOrder.price!=0 تبدیل می شود. این "اشکال" جهش نامیده می شود + +(2) تست ها را اجرا می کند، اگر همه موفق شوند، مشکل داریم - آزمایش ها به هدف خود یعنی کشف باگ ها عمل نکردند، جهش ها به اصطلاح زنده می مانند. اگر آزمایش ها شکست خوردند، عالی است، جهش ها کشته شدند. + +دانستن اینکه همه یا بیشتر جهش‌ها کشته شده‌اند، اطمینان بسیار بیشتری نسبت به پوشش سنتی می‌دهد و زمان راه‌اندازی مشابه است. +
    + +❌ **در غیر این صورت:** شما فریب خواهید خورد که فکر کنید پوشش 85٪ به این معنی است که تست شما اشکالات را در 85٪ از کد شما تشخیص می دهد. + +
    + +
    نمونه کد + +
    + +### :thumbsdown: مثال ضد الگو: 100% پوشش، 0% تست + +![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") + +```javascript +function addNewOrder(newOrder) { + logger.log(`Adding new order ${newOrder}`); + DB.save(newOrder); + Mailer.sendMail(newOrder.assignee, `A new order was places ${newOrder}`); + + return { approved: true }; +} + +it("addNewOrder را تست کنید، از چنین نام های آزمایشی استفاده نکنید", () => { + addNewOrder({ assignee: "John@mailer.com", price: 120 }); +}); //پوشش 100٪ کد را فعال می کند، اما چیزی را بررسی نمی کند +``` + +
    + +### :clap: انجام درست مثال: گزارش های Stryker، ابزاری برای آزمایش جهش، مقدار کدی را که آزمایش نشده است شناسایی و شمارش می کند (جهش) + +![alt text](assets/bp-20-yoni-goldberg-mutation-testing.jpeg "Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations)") + +
    + +

    + +## ⚪ ️4.4 جلوگیری از مشکلات کد تست با لینترهای تست + +:white_check_mark: **انجام دادن:** مجموعه ای از پلاگین های ESLint به طور خاص برای بازرسی الگوهای کد تست و کشف مشکلات ساخته شده است. مثلا, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) هنگامی که یک تست در سطح جهانی نوشته می شود (نه یک عبارت describe()) یا زمانی که تست ها [پرش کرد](https://mochajs.org/#inclusive-tests) که ممکن است به این باور نادرست منجر شود که همه آزمون ها قبول شده اند. به همین ترتیب، [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) برای مثال می تواند هشدار دهد زمانی که یک آزمون اصلاً ادعایی ندارد (چک نکردن چیزی) + +
    + +❌ **در غیر این صورت:** مشاهده 90% پوشش کد و 100% تست‌های سبز باعث می‌شود چهره شما لبخند بزرگی بزند فقط تا زمانی که متوجه شوید که بسیاری از تست‌ها برای هیچ چیزی ادعا نمی‌کنند و بسیاری از مجموعه‌های آزمایشی صرفاً نادیده گرفته شده‌اند. امیدوارم بر اساس این مشاهده نادرست چیزی را مستقر نکرده باشید + +
    +
    نمونه کد + +
    + +### :thumbsdown:مثال ضد الگو: یک مورد آزمایشی پر از خطا، خوشبختانه همه توسط Linters دستگیر شدند + +```javascript +describe("توضیحات خیلی کوتاه", () => { + const userToken = userService.getDefaultToken() // *error:no-setup-in-describe، به جای آن از هوک ها (به مقدار کم) استفاده کنید + it("کمی توضیحات", () => {});//* error: valid-test-description. باید شامل کلمه "Should" + حداقل 5 کلمه باشد +}); + +it.skip("نام تست", () => {// *error:no-skipped-tests, error:error:no-global-tests. تست ها را فقط در قسمت توصیف یا مجموعه قرار دهید + expect("somevalue"); // error:no-assert +}); + +it("نام تست", () => {// *error:no-identical-title. عناوین منحصر به فرد را به آزمون ها اختصاص دهید +}); +``` + +
    + +

    + +# بخش 5️⃣: CI و سایر معیارهای کیفیت + +

    + +## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues + +:white_check_mark: **انجام دادن:** لینترها یک ناهار رایگان هستند، با راه اندازی 5 دقیقه ای به صورت رایگان یک خلبان خودکار از کد شما محافظت می کند و هنگام تایپ مشکل مهمی را متوجه می شوید. روزهایی که پرزها در مورد لوازم آرایشی بودند (نه نیم کولن!) گذشته است. امروزه، Linters می تواند مشکلات شدیدی مانند خطاهایی که به درستی پرتاب نمی شوند و اطلاعات از دست می دهند، بگیرند. علاوه بر مجموعه قوانین اساسی شما (مانند [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) یا [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), شامل برخی از Linters تخصصی مانند [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) که می تواند تست ها را بدون ادعا کشف کند, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) می تواند وعده ها را بدون هیچ مشکلی کشف کند (کد شما هرگز ادامه نخواهد داشت), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) که می تواند عبارات regex مشتاق را کشف کند که ممکن است برای حملات DOS استفاده شود، و [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) زمانی که کد از روش‌های کتابخانه ابزاری استفاده می‌کند که بخشی از روش‌های هسته V8 مانند Lodash هستند، می‌تواند هشدار دهد.\_map(…) +
    + +❌ **در غیر این صورت:**یک روز بارانی را در نظر بگیرید که در آن تولید شما مدام خراب می‌شود اما گزارش‌ها ردیابی پشته خطا را نشان نمی‌دهند. چی شد؟ کد شما به اشتباه یک شی بدون خطا پرتاب کرد و رد پشته از بین رفت، دلیل خوبی برای ضربه زدن سر خود به دیوار آجری است. یک راه‌اندازی 5 دقیقه‌ای لینتر می‌تواند این اشتباه تایپی را شناسایی کرده و در روز شما صرفه‌جویی کند + +
    + +
    نمونه کد + +
    + +### :thumbsdown: مثال Anti-Pattern: شی Error اشتباه به اشتباه پرتاب شده است، هیچ stack-trace برای این خطا ظاهر نمی شود. خوشبختانه ESLint باگ تولید بعدی را پیدا می کند + +![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug") + +
    + +

    + +## ⚪ ️ 5.2 حلقه بازخورد را با توسعه دهنده-CI محلی کوتاه کنید + +:white_check_mark: **انجام دادن:** آیا از یک CI با بازرسی های کیفیت براق مانند آزمایش، پرده زدن، بررسی آسیب پذیری ها و غیره استفاده می کنید؟ به توسعه دهندگان کمک کنید تا این خط لوله را نیز به صورت محلی اجرا کنند تا بازخورد فوری دریافت کنند و زمان را کوتاه کنند [حلقه بازخورد](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). چرا؟ یک فرآیند تست کارآمد حلقه های تکراری و متعددی را تشکیل می دهد: (1) آزمایشات -> (2) بازخورد -> (3) بازساز. هرچه بازخورد سریعتر باشد، توسعه دهنده می تواند تکرارهای بهبود بیشتری را در هر ماژول انجام دهد و نتایج را کامل کند. در تلنگر، زمانی که بازخورد دیر می رسد، تکرارهای بهبود کمتری می تواند در یک روز بسته بندی شود، تیم ممکن است در حال حاضر به سمت موضوع/وظیفه/ماژول دیگری حرکت کند و ممکن است برای اصلاح آن ماژول آماده نباشد. + +در عمل، برخی از فروشندگان CI (مثال: [CLI محلی CircleCI](https://circleci.com/docs/2.0/local-cli/)) اجازه می دهد خط لوله را به صورت محلی اجرا کند. برخی از ابزارهای تجاری مانند [wallaby بینش های بسیار ارزشمند و آزمایشی را ارائه می دهد](https://wallabyjs.com/) به عنوان نمونه اولیه توسعه دهنده (بدون وابستگی). از طرف دیگر، می‌توانید اسکریپت npm را به package.json اضافه کنید که تمام دستورات کیفیت (مانند تست، پرز، آسیب‌پذیری‌ها) را اجرا می‌کند - از ابزارهایی مانند استفاده کنید. [همزمان](https://www.npmjs.com/package/concurrently) برای موازی سازی و کد خروج غیر صفر در صورت خرابی یکی از ابزارها. اکنون توسعه‌دهنده باید فقط یک دستور را فراخوانی کند - به عنوان مثال. "کیفیت اجرای npm" - برای دریافت بازخورد فوری. همچنین در صورت عدم موفقیت بررسی کیفیت با استفاده از githook، یک commit را لغو کنید ([هاسکی می تواند کمک کند](https://github.com/typicode/husky)) +
    + +❌ **در غیر این صورت:** هنگامی که نتایج کیفی یک روز پس از کد به دست می‌آیند، آزمایش به بخشی روان از توسعه تبدیل نمی‌شود، بلکه به یک مصنوع رسمی پس از واقعیت تبدیل می‌شود. + +
    + +
    نمونه های کد + +
    + +### :clap: درست انجام دادن مثال: اسکریپت‌های npm که بازرسی کیفیت کد را انجام می‌دهند، همه به‌صورت موازی اجرا می‌شوند یا زمانی که یک توسعه‌دهنده تلاش می‌کند کد جدیدی را وارد کند. + +```json +{ + "scripts": { + "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", + "inspect:lint": "eslint .", + "inspect:vulnerabilities": "npm audit", + "inspect:license": "license-checker --failOn GPLv2", + "inspect:complexity": "plato .", + "inspect:all": "concurrently -c \"bgBlue.bold,bgMagenta.bold,yellow\" \"npm:inspect:quick-testing\" \"npm:inspect:lint\" \"npm:inspect:vulnerabilities\" \"npm:inspect:license\"" + }, + "husky": { + "hooks": { + "precommit": "npm run inspect:all", + "prepush": "npm run inspect:all" + } + } +} +``` + +
    + +

    + +## ⚪ ️5.3 آزمایش e2e را روی یک آینه تولید واقعی انجام دهید + +:white_check_mark: **انجام دادن:** آزمایش انتها به انتها (e2e) چالش اصلی هر خط لوله CI است - ایجاد یک آینه تولید زودگذر یکسان در حال پرواز با تمام خدمات ابری مرتبط می تواند خسته کننده و گران باشد. یافتن بهترین سازش بازی شماست: [Docker-compose](https://serverless.com/) اجازه می دهد تا با استفاده از یک فایل متنی ساده، محیط ایزوله شده را با کانتینرهای یکسان ایجاد کنید، اما فناوری پشتیبان (به عنوان مثال شبکه، مدل استقرار) با تولیدات دنیای واقعی متفاوت است. می توانید آن را با آن ترکیب کنید [‘AWS محلی’](https://github.com/localstack/localstack) برای کار با تعدادی از خدمات واقعی AWS. اگه رفتی [serverless](https://serverless.com/) فریمورک های متعدد مانند بدون سرور و [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) فراخوانی محلی کد FaaS را امکان پذیر می کند. + +اکوسیستم عظیم Kubernetes هنوز یک ابزار مناسب استاندارد برای آینه‌کاری محلی و CI رسمی نکرده است، اگرچه ابزارهای جدید زیادی به طور مکرر راه‌اندازی می‌شوند. یک رویکرد اجرای «کوبرنت‌های کوچک‌شده» با استفاده از ابزارهایی مانند [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) که شبیه چیزهای واقعی هستند فقط با سربار کمتری عرضه می شوند. روش دیگر آزمایش بر روی یک "Kubernetes واقعی" از راه دور، برخی از ارائه دهندگان CI است (e.g. [Codefresh](https://codefresh.io/)) دارای ادغام بومی با محیط Kubernetes است و اجرای خط لوله CI را بر روی چیز واقعی آسان می کند، دیگران اجازه می دهند اسکریپت سفارشی در برابر Kubernetes از راه دور انجام شود. +
    + +❌ **در غیر این صورت:**استفاده از فناوری های مختلف برای تولید و آزمایش مستلزم حفظ دو مدل استقرار است و توسعه دهندگان و تیم عملیات را از هم جدا نگه می دارد. + +
    + +
    نمونه کد + +
    + +### :clap: مثال: یک خط لوله CI که خوشه Kubernetes را در حال پرواز تولید می کند ([اعتبار: پویا-محیط های Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) + +
    deploy:
    stage: deploy
    image: registry.gitlab.com/gitlab-examples/kubernetes-deploy
    script:
    - ./configureCluster.sh $KUBE_CA_PEM_FILE $KUBE_URL $KUBE_TOKEN
    - kubectl create ns $NAMESPACE
    - kubectl create secret -n $NAMESPACE docker-registry gitlab-registry --docker-server="$CI_REGISTRY" --docker-username="$CI_REGISTRY_USER" --docker-password="$CI_REGISTRY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL"
    - mkdir .generated
    - echo "$CI_BUILD_REF_NAME-$CI_BUILD_REF"
    - sed -e "s/TAG/$CI_BUILD_REF_NAME-$CI_BUILD_REF/g" templates/deals.yaml | tee ".generated/deals.yaml"
    - kubectl apply --namespace $NAMESPACE -f .generated/deals.yaml
    - kubectl apply --namespace $NAMESPACE -f templates/my-sock-shop.yaml
    environment:
    name: test-for-ci
    + +
    + +

    + +## ⚪ ️5.4 موازی کردن اجرای آزمایش + +:white_check_mark: **انجام دادن:** هنگامی که آزمایش به درستی انجام شود، دوست شما 24 ساعته و تقریباً فوری بازخورد ارائه می کند. در عمل، اجرای آزمایش 500 واحد محدود به CPU بر روی یک رشته ممکن است خیلی طول بکشد. خوشبختانه، دونده های آزمایشی مدرن و پلت فرم های CI (مانند [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) و [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) می تواند آزمون را در چندین فرآیند موازی کند و به بهبود قابل توجهی در زمان بازخورد دست یابد. برخی از فروشندگان CI نیز آزمایشات را در کانتینرها موازی می کنند (!) که حلقه بازخورد را حتی بیشتر کوتاه می کند. چه به صورت محلی بر روی چندین فرآیند، یا از طریق برخی CLI ابری با استفاده از چندین ماشین - تقاضای موازی کردن تست‌ها را مستقل نگه می‌دارد زیرا هر کدام ممکن است در فرآیندهای مختلف اجرا شوند. + +❌ **در غیر این صورت:** دریافت نتایج آزمون 1 ساعت پس از فشار دادن کد جدید، همانطور که قبلاً ویژگی‌های بعدی را کدنویسی کرده‌اید، یک دستور العمل عالی برای کاهش مرتبط کردن تست است. + +
    + +
    نمونه کد + +
    + +### :clap: انجام درست مثال: موازی موکا و جست به لطف آزمایش موازی سازی به راحتی از موکای سنتی پیشی می گیرند. ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) + +![alt text](assets/bp-24-yonigoldberg-jest-parallel.png "Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization (Credit: JavaScript Test-Runners Benchmark)") + +
    + +

    + +## ⚪ ️5.5 با استفاده از چک مجوز و سرقت ادبی از مسائل قانونی خودداری کنید + +:white_check_mark: **انجام دادن:** مسائل مربوط به مجوز و سرقت ادبی احتمالاً دغدغه اصلی شما در حال حاضر نیست، اما چرا این کادر را در 10 دقیقه تیک ندهید؟ یک دسته از بسته های npm مانند [بررسی مجوز](https://www.npmjs.com/package/license-checker) و [چک سرقت ادبی](https://www.npmjs.com/package/plagiarism-checker) (تجاری با طرح رایگان) را می توان به راحتی در خط لوله CI خود قرار داد و غم هایی مانند وابستگی ها را با مجوزهای محدود یا کدی که از Stack Overflow کپی شده است و ظاهراً برخی از حق چاپ را نقض می کند بررسی کرد. + +❌ **در غیر این صورت:** به طور ناخواسته، توسعه‌دهندگان ممکن است از بسته‌هایی با مجوزهای نامناسب استفاده کنند یا کد تجاری را کپی پیست کنند و با مشکلات قانونی مواجه شوند. + +
    + +
    نمونه کد + +
    + +### :clap: انجام درست آن مثال: + +```shell +# لایسنس چک را در محیط CI خود یا به صورت محلی نصب کنید +npm install -g license-checker + +# از آن بخواهید که تمام مجوزها را اسکن کند و اگر مجوز غیرمجاز پیدا کرد با کد خروجی غیر از 0 شکست بخورد. سیستم CI باید این شکست را بگیرد و ساخت را متوقف کند +license-checker --summary --failOn BSD +``` + +
    + +![alt text](assets/bp-25-nodejs-licsense.png) + +
    + +

    + +## ⚪ ️5.6 به طور مداوم وابستگی های آسیب پذیر را بررسی کنید + +:white_check_mark: **انجام دادن:** حتی معتبرترین وابستگی ها مانند Express آسیب پذیری های شناخته شده ای دارند. این می تواند به راحتی با استفاده از ابزارهای جامعه مانند [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), یا ابزارهای تجاری مانند [snyk](https://snyk.io/) (نسخه انجمن رایگان را نیز ارائه دهید). هر دو را می توان از CI شما در هر ساختنی فراخوانی کرد + +❌ **در غیر این صورت:** پاک نگه داشتن کد خود از آسیب‌پذیری‌ها بدون ابزار اختصاصی، مستلزم این است که دائماً انتشارات آنلاین در مورد تهدیدات جدید را دنبال کنید. کاملا خسته کننده + +
    + +
    نمونه کد + +
    + +### :clap: مقال: NPM Audit نتیجه + +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + +

    + +## ⚪ ️5.7 به‌روزرسانی‌های وابستگی را خودکار کنید + +:white_check_mark: **انجام دادن:** Yarn و npm آخرین معرفی package-lock.json یک چالش جدی را معرفی کرد (جاده جهنم با نیت خوب هموار شده است)——به طور پیش فرض اکنون بسته ها دیگر به روز رسانی نمی شوند. حتی تیمی که بسیاری از استقرارهای جدید را با "npm install" و "npm update" اجرا می کند، هیچ به روز رسانی جدیدی دریافت نخواهد کرد. این منجر به نسخه‌های بسته‌های وابسته به پایین‌تر و در بدترین حالت به کدهای آسیب‌پذیر می‌شود. اکنون تیم ها برای به روز رسانی دستی package.json یا استفاده از ابزارها به حسن نیت و حافظه توسعه دهندگان متکی هستند [مانند ncu](https://www.npmjs.com/package/npm-check-updates) به صورت دستی یک راه قابل اطمینان تر می تواند خودکار کردن فرآیند دریافت قابل اعتمادترین نسخه های وابستگی باشد، اگرچه هیچ راه حل گلوله نقره ای وجود ندارد، اما دو راه اتوماسیون ممکن وجود دارد: + +(1) CI می‌تواند در ساخت‌هایی که وابستگی‌های منسوخ دارند با استفاده از ابزارهایی مانند [‘npm outdated’](https://docs.npmjs.com/cli/outdated) یا ‘npm-check-updates (ncu)’ . انجام این کار توسعه دهندگان را وادار می کند تا وابستگی ها را به روز کنند. + +(2) از ابزارهای تجاری استفاده کنید که کد را اسکن می کنند و به طور خودکار درخواست های کشش را با وابستگی های به روز ارسال می کنند. یک سوال جالب باقی مانده این است که سیاست به‌روزرسانی وابستگی چگونه باید باشد— به‌روزرسانی در هر وصله سربار زیادی ایجاد می‌کند، به‌روزرسانی درست زمانی که یک اصلی منتشر می‌شود ممکن است به یک نسخه ناپایدار اشاره کند (بسیاری از بسته‌ها در همان روزهای اول پس از انتشار آسیب‌پذیر هستند., [را ببینید](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope حادثه). + +یک خط‌مشی به‌روزرسانی کارآمد ممکن است اجازه دهد تا مقداری «دوره واگذاری» وجود داشته باشد - اجازه دهید کد برای مدتی از آخرین @ و نسخه‌ها عقب بماند، قبل از اینکه نسخه محلی را منسوخ در نظر بگیرید (به عنوان مثال نسخه محلی 1.3.1 و نسخه مخزن 1.3.8 است). +
    + +❌ **در غیر این صورت:** تولید شما بسته هایی را اجرا می کند که به صراحت توسط نویسنده آنها به عنوان خطرناک برچسب گذاری شده است + +
    + +
    نمونه کد + +
    + +### :clap: مقال: [ncu](https://www.npmjs.com/package/npm-check-updates) می تواند به صورت دستی یا در یک خط لوله CI برای تشخیص میزان عقب ماندگی کد از آخرین نسخه ها استفاده شود + +![alt text](assets/bp-27-yoni-goldberg-npm.png "ncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions") + +
    + +

    + +## ⚪ ️ 5.8 نکات دیگر، غیر مرتبط با گره، CI + +:white_check_mark: **انجام دادن:** این پست بر روی توصیه‌های آزمایشی متمرکز شده است، یا حداقل می‌توان آن را با Node JS مثال زد. با این حال، این گلوله چند نکته غیر مرتبط با Node را که به خوبی شناخته شده هستند، گروه بندی می کند + +
    1. از یک نحو اعلانی استفاده کنید. این تنها گزینه برای اکثر فروشندگان است اما نسخه های قدیمی Jenkins امکان استفاده از کد یا UI را می دهد
    2. فروشنده ای را انتخاب کنید که از پشتیبانی Docker بومی برخوردار باشد
    3. زود شکست بخورید، ابتدا سریع ترین تست های خود را اجرا کنید. یک مرحله / نقطه عطف "تست دود" ایجاد کنید که چندین بازرسی سریع را گروه بندی می کند (مانند پرده زدن، تست های واحد) و بازخورد سریع را به committer کد ارائه می دهد.
    4. بررسی تمام مصنوعات ساختنی از جمله گزارش‌های آزمایش، گزارش‌های پوشش، گزارش‌های جهش، گزارش‌ها و غیره را آسان کنید.
    5. چندین خط لوله/شغل برای هر رویداد ایجاد کنید، از مراحل بین آنها دوباره استفاده کنید. به عنوان مثال، یک کار را برای commit های شاخه ویژگی و یک کار دیگر را برای PR اصلی پیکربندی کنید. به هر منطق اجازه استفاده مجدد را با استفاده از مراحل مشترک بدهید (اکثر فروشندگان مکانیزمی برای استفاده مجدد از کد ارائه می دهند)
    6. هرگز اسرار را در یک اعلامیه شغلی قرار ندهید، آنها را از یک فروشگاه مخفی یا از پیکربندی شغل بگیرید.
    7. به صراحت نسخه را در یک نسخه منتشر کنید یا حداقل اطمینان حاصل کنید که توسعه دهنده این کار را انجام داده است
    8. فقط یک بار بسازید و تمام بازرسی‌ها را روی یک مصنوع ساخت (مثلاً تصویر داکر) انجام دهید.
    9. در یک محیط زودگذر که حالت رانش بین ساخت‌ها را ندارد، تست کنید. ذخیره node_modules ممکن است تنها استثنا باشد
    +
    + +❌ **در غیر این صورت:** شما سالهای خرد را از دست خواهید داد + +

    + +## ⚪ ️ 5.9 ساخت ماتریس: همان مراحل CI را با استفاده از چندین نسخه Node اجرا کنید + +:white_check_mark: **انجام دادن:** بررسی کیفیت در مورد سرندیپیتی است، هرچه زمینه بیشتری را پوشش دهید، در تشخیص زودهنگام مشکلات شانس بیشتری خواهید داشت. هنگام توسعه بسته‌های قابل استفاده مجدد یا اجرای یک تولید چند مشتری با پیکربندی‌های مختلف و نسخه‌های Node، CI باید خط لوله آزمایش‌ها را روی همه جایگشت‌های پیکربندی‌ها اجرا کند. به عنوان مثال، با فرض اینکه ما از MySQL برای برخی از مشتریان و از Postgres برای برخی دیگر استفاده می‌کنیم - برخی از فروشندگان CI از ویژگی به نام «Matrix» پشتیبانی می‌کنند که امکان اجرای مجموعه آزمایشی را در برابر همه جایگشت‌های MySQL، Postgres و چندین نسخه Node مانند 8، 9 و 10 می‌دهد. این کار فقط با استفاده از پیکربندی بدون هیچ تلاش اضافی انجام می شود (با فرض اینکه تست یا هر گونه بررسی کیفیت دیگری داشته باشید). سایر CI که از Matrix پشتیبانی نمی کنند ممکن است افزونه ها یا ترفندهایی برای اجازه دادن به آن داشته باشند +
    + +❌ **در غیر این صورت:** بنابراین، پس از انجام این همه کار سخت در تست نوشتن، تنها به دلیل مشکلات پیکربندی، اجازه می‌دهیم باگ‌ها پنهان شوند.? + +
    + +
    نمونه کد + +
    + +### :clap: مثال: استفاده از تعریف ساخت تراویس (فروشنده CI) برای اجرای همان آزمایش روی چندین نسخه Node + +
    زبان: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    +
    + +

    + +# Team + +## Yoni Goldberg + +
    + +
    + +**Role:** Writer + +**About:** I'm an independent consultant who works with Fortune 500 companies and garage startups on polishing their JS & Node.js applications. More than any other topic I'm fascinated by and aims to master the art of testing. I'm also the author of [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices) + +**📗 Online Course:** Liked this guide and wish to take your testing skills to the extreme? Consider visiting my comprehensive course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +
    + +**Follow:** + +- [🐦 Twitter](https://twitter.com/goldbergyoni/) +- [📞 Contact](https://testjavascript.com/contact-2/) +- [✉️ Newsletter](https://testjavascript.com/newsletter//) + +
    +
    +
    + +## [Bruno Scheufler](https://github.com/BrunoScheufler) + +**Role:** Tech reviewer and advisor + +Took care to revise, improve, lint and polish all the texts + +**About:** full-stack web engineer, Node.js & GraphQL enthusiast + +
    +
    + +## [Ido Richter](https://github.com/idori) + +**Role:** Concept, design and great advice + +**About:** A savvy frontend developer, CSS expert and emojis freak + +## [Kyle Martin](https://github.com/js-kyle) + +**Role:** Helps keep this project running, and reviews security related practices + +**About:** Loves working on Node.js projects and web application security. diff --git a/readme-pt-br.md b/readme-pt-br.md index 894954dd..9d0e4075 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -31,6 +31,7 @@ Comece entendendo as práticas de teste onipresentes que são a base para qualqu ### Traduções - leia em seu próprio idioma * 🇨🇳[Chinese](readme-zh-CN.md) - cortesia de [Yves yao](https://github.com/yvesyao) * 🇰🇷[Korean](readme.kr.md) - cortesia de [Rain Byun](https://github.com/ragubyun) +* 🇺🇦[Ukrainian](readme-ua.md) - cortesia de [Serhii Shramko](https://github.com/Shramkoweb) * Deseja traduzir para o seu próprio idioma? abra uma issue 💜 @@ -1029,7 +1030,7 @@ test('When flagging to show only VIP, should display only VIP members', () => { const { getAllByTestId } = render(); // Assert - Mix UI & data in assertion - expect(getAllByTestId('user')).toEqual('[
  • John Doe
  • ]'); + expect(getAllByTestId('user')).toEqual('[
  • John Doe
  • ]'); }); ``` @@ -1065,7 +1066,7 @@ test('When flagging to show only VIP, should display only VIP members', () => { // the markup code (part of React component)

    - {value} + {value}

    ``` @@ -1319,7 +1320,7 @@ export default function ProductsList() { fetchProducts(); }, []); - return products ?
    {products}
    :
    No products
    + return products ?
    {products}
    :
    No products
    } // test @@ -1800,7 +1801,7 @@ Na prática alguns fornecedores de IC (exemplo: [CircleCI local CLI](https://cir

    -# ⚪ ️5.3 Realize testes e2eem um verdadeiro espelho de produção +# ⚪ ️5.3 Realize testes e2e em um verdadeiro espelho de produção :white_check_mark: **Faça:** Os testes de ponta a ponta (e2e) são o principal desafio de cada pipeline de IC—criar um espelho efêmero idêntico de produção em tempo real com todos os serviços em nuvem relacionados pode ser entediante e caro. Encontrar o melhor comprometimento é o seu jogo: [Docker-compose](https://serverless.com/) permite criar ambiente docker isolado com contêineres idênticos usando um único arquivo de texto sem formatação, mas as tecnologias de suporte (por exemplo. rede, modelo de implantação) é diferente das produções do mundo real. Você pode combiná-lo com [‘AWS Local’](https://github.com/localstack/localstack) para trabalhar com um esboço dos serviços reais da AWS. Se você usar [serverless](https://serverless.com/) vários frameworks como serverless e [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) permite a chamada local de códigos FaaS. diff --git a/readme-ru.md b/readme-ru.md new file mode 100644 index 00000000..239e63bc --- /dev/null +++ b/readme-ru.md @@ -0,0 +1,2190 @@ + + +
    + +# 👇 Почему это руководство выведет ваши навыки тестирования на новый уровень + +
    + +## 📗 46+ наилучших способов: исчерпывающих и информативных + +Данное руководство гарантирует надежность JavaScript и Node.JS от A до Я. В качестве источника в данном руководстве используется обобщенная информация, взятая из самых надежных книг, статей и блогов, которые можно найти на рынке в данный момент. + +## 🚢 Продвинутый уровень: Выходит далеко за пределы основ + +Отправляйтесь в путешествие, которое выходит далеко за пределы базовых практик тестирования и включает в себя такие продвинутые темы, как: тестирование в рабочей среде (TIP), мутационное тестирование (mutation testing), тестирование на основе свойств (property-based testing) и многие другие профессиональные подходы. После прочтения данного руководства, ваши навыки тестирования станут намного выше среднего. + +## 🌐 Full-stack: frontend, backend, CI и другое + +Начните с понимания общеиспользуемых способов тестирования, являющиеся основными для приложений любого уровня, а затем углубитесь в выбранную вами область: frontend/UI, backend, CI или всё вместе. + +
    + +## 🚀 Для отработки изученных в процессе чтения навыков тестирования Вы можете использовать наш [Node.js starter - Practica.js](https://github.com/practicajs/practica). Вы сможете использовать его как для создания нового шаблона, так и для практики с примерами кода. + +### Автор руководства - Yoni Goldberg + +- Консультант по вопросам JavaScript & Node.js +- 📗 [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) - Мой полный онлайн-курс, включающий более, чем [7 часов видео](https://www.testjavascript.com), 14 типов тестирования и 40+ практических занятий +- [Мой твиттер](https://twitter.com/goldbergyoni/) +- [Следующий workshop: Verona, Italy 🇮🇹, April 20th](https://2022.jsday.it/workshop/nodejs_testing.html) + +
    + +### Доступные переводы + +- 🇨🇳[Китайский](readme-zh-CN.md) - Переведено [Yves yao](https://github.com/yvesyao) +- 🇰🇷[Koрейский](readme.kr.md) - Переведено [Rain Byun](https://github.com/ragubyun) +- 🇵🇱[Польский](readme-pl.md) - Переведено [Michal Biesiada](https://github.com/mbiesiad) +- 🇪🇸[Испанский](readme-es.md) - Переведено [Miguel G. Sanguino](https://github.com/sanguino) +- 🇧🇷[Португальский](readme-pt-br.md) - Переведено [Iago Angelim Costa Cavalcante](https://github.com/iagocavalcante) , [Douglas Mariano Valero](https://github.com/DouglasMV) and [koooge](https://github.com/koooge) +- 🇫🇷[Французский](readme-fr.md) - Переведено [Mathilde El Mouktafi](https://github.com/mel-mouk) +- 🇯🇵[Японский (черновик)](https://github.com/yuichkun/javascript-testing-best-practices/blob/master/readme-jp.md) - Переведено of [Yuichi Yogo](https://github.com/yuichkun) and [ryo](https://github.com/kawamataryo) +- 🇹🇼[Традиционный китайский](readme-zh-TW.md) - Переведено [Yubin Hsu](https://github.com/yubinTW) +- 🇷🇺 [Русский](ссылка) - Переведено [Alex Popov](https://github.com/Saimon398) +- Хотите перевести на собственный язык? Переходите в Issues 💜 + +

    + +## `Содержание` + +#### [`Раздел 0: Золотое правило`](#section-0️⃣-the-golden-rule) + +Ключевое правило для написания качественных тестов (1 пункт) + +#### [`Раздел 1: Анатомия тестов`](#section-1-the-test-anatomy-1) + +Фундамент - структура чистых тестов (12 пунктов) + +#### [`Раздел 2: Backend`](#section-2️⃣-backend-testing) + +Разработка эффективных Backend and Microservices тестов (13 пунктов) + +#### [`Раздел 3: Frontend`](#section-3️⃣-frontend-testing) + +Написание тестов для UI, включая тестирование компонентов и E2E тесты (11 пунктов) + +#### [`Раздел 4: Измерение эффективности тестов`](#section-4️⃣-measuring-test-effectiveness) + +Измерение качества тестов (4 пункта) + +#### [`Раздел 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) + +Руководство для CI в мире JS (9 пунктов) + +

    + +# Раздел 0️⃣: Золотое правило + +
    + +## ⚪️ 0 Золотое правило: Как проектировать тесты + +:white_check_mark: **Сделать:** +Тесты - это не продакшн-код. Тестовый код должен быть коротким, плоским и простым, чтобы с ним было приятно работать. Он должен быть таким, чтобы взглянув на него, можно было сразу понять замысел разработчика. + +Во время разработки, наш разум полностью сфокусирован на продакшн-коде. Наш интеллект полностью погружен в работу и, как правило, введение дополнительной сложности может привести к "перегрузке". Если мы попытаемся добавить еще одну комплексную систему, это может привести к торможению целого рабочего процесса, что противоречит самой идее тестирования. На практике, многие команды разработчиков пренебрегают тестированием. + +Тестирование нужно рассматривать, как личного помощника, второго пилота, который за небольшую плату предоставляет бесценные услуги и пользу. Ученые утверждают, что у человека есть 2 типа мышления: тип 1 используется при выполнении простых задач, не требующих усилий, таких как вождение автомобиля по пустой дороге, и тип 2, предназначенный для сложных мыслительных процессов, как, например, решение математических уравнений. +Тесты должны быть написаны так, чтобы при просмотре на код использовался 1 тип мышления, и это было похоже на простое изменение HTML-документа, а не решение математического уравнения 2 × (17 × 24). + +Этого можно достичь путем тщательного выбора способов, инструментов и целей тестирования, которые будут одновременно и эффективными и "окупаемыми". Тестируйте только то, что необходимо. Старайтесь увеличить скорость разработки. Иногда даже стоит отказаться от некоторых тестов, чтобы сделать код более простым и гибким. + +![alt text](/assets/headspace.png "We have no head room for additional complexity") + +Большинство дальнейших советов являются производными от этого принципа. + +### Готовы? + +

    + +# Раздел 1: Анатомия тестов + +
    + +## ⚪ ️ 1.1 Каждое описание теста включает в себя 3 части + +:white_check_mark: **Сделать:** Отчет о тестировании должен предоставлять информацию о том, удовлетворяет ли текущая версия приложения требованиям человека, который часто незнаком с кодом или забыл его: тестировщик, DevOps-инженер, который разворачивает проект, а также Вы сами через 2 года. +Наилучший способ добиться этого, если тесты будут проводиться на уровне требований, а его описание состоять из 3-х частей: + +(1) Что именно тестируется? Например: ProductsService.addNewProduct method + +(2) При каких обстоятельствах? Например: no price is passed to the method + +(3) Какой ожидаемый результат? Например: the new product is not approved + +
    + +❌ **Иначе:** A deployment just failed, a test named “Add product” failed. Говорит ли это вам о том, в чем именно заключается сбой? +
    + +**👇 Обрати внимание:** Каждый пункт содержит примеры кода, а иногда и иллюстрацию к нему. Нажмите, чтобы открыть +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Описание теста содержит 3 части + +![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Using Mocha to illustrate the idea") + +```javascript +//1. Тестируемый блок +describe('Products Service', function() { + describe('Add new product', function() { + //2. сценарий and 3. ожидание + it('When no price is specified, then the product status is pending approval', ()=> { + const newProduct = new ProductService().add(...); + expect(newProduct.status).to.equal('pendingApproval'); + }); + }); +}); + +``` + +
    + +### :clap: Правильно: Описание теста содержит 3 части + +![alt text](/assets/bp-1-3-parts.jpeg "Описание теста содержит 3 части") + +
    + +
    +
    © Читать далее... + 1. Roy Osherove - Naming standards for unit tests +
    + +

    + +## ⚪ ️ 1.2 Разделите тесты по AAA-структуре + +:white_check_mark: **Сделать:** Разделите написанные тесты согласно 3 категориям: Arrange, Act & Assert (AAA). Следование данной структуре позволит человеку, анализирующему тесты, быстрее разобраться в стратегии тестирования. + +1. A - Организуйте (Arrange): В данную категорию входит настройки, которые приведут код к тому сценарию, который должен быть имитирован. Они включают в себя организация трестируемого блока, добавления записей в БД, mocking/stubbing и многое другое. + +2. A - Действуйте (Act): Выполните тестируемый блок кода. Как правило, занимает 1 строку кода. + +3. A - Утвердите (Assert): Убедитесь, что полученный результат соответствует ожидаемому. Как правило, занимает 1 строку кода. + +
    + +❌ **Иначе:** Вы не только тратите время на то, чтобы понять, как работает основной код, но и на то, как функционируют тесты, несмотря на то, что это должно быть простой задачей. +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Тест, оформленный по структуре AAA + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +describe("Customer classifier", () => { + test("When customer spent more than 500$, should be classified as premium", () => { + // Arrange + const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; + const DBStub = sinon + .stub(dataAccess, "getCustomer") + .reply({ id: 1, classification: "regular" }); + + // Act + const receivedClassification = + customerClassifier.classifyCustomer(customerToClassify); + + // Assert + expect(receivedClassification).toMatch("premium"); + }); +}); +``` + +
    + +### :thumbsdown: Неправильно: Разделение отсутствует, сплошной код, тяжело разобрать написанное + +```javascript +test("Should be classified as premium", () => { + const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; + const DBStub = sinon + .stub(dataAccess, "getCustomer") + .reply({ id: 1, classification: "regular" }); + const receivedClassification = + customerClassifier.classifyCustomer(customerToClassify); + expect(receivedClassification).toMatch("premium"); +}); +``` + +
    + +

    + +## ⚪ ️1.3 Опишите ожидания на языке продукта: используйте утверждения в стиле BDD + +:white_check_mark: **Сделать:** Написание тестов в декларативном стиле позволяет читателю мгновенно понять смысл происходящего, особо не напрягаясь. Когда вы пишете код в императивном стиле, наполненный условными конструкциями, необходимо прикладывать некоторые усилия, чтобы его разобрать. В этом случае необходимо писать код в стиле "человеческой" речи, применяя BDD-подход с использованием `expect` или `should` и избегая пользовательских конструкций. В случае, если Chai & Jest не содержат нужные assertions, которые часто повторяются при написании, то рассмотрите [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) или написание [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +
    + +❌ **Иначе:** При разработке будет написано меньше тестов, а ненужные тесты будут проигнорированы с помощью .skip(). + +
    + +
    Примеры кода
    + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +### :thumbsdown: Неправильно: Чтобы просмотреть реализацию тестов, разработчик должен просмотреть весь объёмный и императивный код + +```javascript +test("When asking for an admin, ensure only ordered admins in results", () => { + // предположим, что мы добавили здесь "admin1", "admin2" and "user1" + const allAdmins = getUsers({ adminOnly: true }); + + let admin1Found, + adming2Found = false; + + allAdmins.forEach((aSingleUser) => { + if (aSingleUser === "user1") { + assert.notEqual(aSingleUser, "user1", "A user was found and not admin"); + } + if (aSingleUser === "admin1") { + admin1Found = true; + } + if (aSingleUser === "admin2") { + admin2Found = true; + } + }); + + if (!admin1Found || !admin2Found) { + throw new Error("Not all admins were returned"); + } +}); +``` + +
    + +### :clap: Правильно: Просмотреть данный код в декларативном стиле не составляет труда + +```javascript +it("When asking for an admin, ensure only ordered admins in results", () => { + // предположим, что мы добавили здесь 2-х администраторов + const allAdmins = getUsers({ adminOnly: true }); + + expect(allAdmins) + .to.include.ordered.members(["admin1", "admin2"]) + .but.not.include.ordered.members(["user1"]); +}); +``` + +
    + +

    + +## ⚪ ️ 1.4 Придерживайтесь black-box тестирования: Тестируйте только public-методы + +:white_check_mark: **Сделать:** Тестирование внутренних компонентов сопровождается большими затратами. Если ваш код/API работает корректно, целесообразно ли будет потратить следующие 3 часа на написание тестов для внутренней реализации и потом все это поддерживать? Каждый раз, когда тестируется внешнее поведение, внутренняя реализация также проверяется неявным образом. Тесты могут упасть только при возникновении определенной проблемы (например, неправильный вывод). Такой подход также называют `поведенческое тестирование (behavioral testing)`. С другой стороны, если вы тестируете внутренние компоненты (white-box тестирование), ваш фокус может сместиться с планирования результата работы данного компонента на мелкие детали. Как следствие, тест может упасть из-за незначительных исправлений в коде, несмотря на то, что результат работы компонента будет удовлетворительны - это усложняет поддержку такого кода. +
    + +❌ **Иначе:** Ваши тесты начинают работать словно [мальчик, который кричал: "Волк!"](https://ru.wikipedia.org/wiki/Мальчик,_который_кричал:_«Волк!»), сигнализируя о ложных срабатываниях (например, A test fails because a private variable name was changed). Неудивительно, что люди вскоре начнут игнорировать CI-уведомления, пока в один прекрасный день не проигнорируют настоящую ошибку... + +
    +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: Тестирование внутренних компонентов без причины + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") + +```javascript +class ProductService { + // этот метод используется только внутри + // изменение имени приведет к падению тестов + calculateVATAdd(priceWithoutVAT) { + return { finalPrice: priceWithoutVAT * 1.2 }; + // изменение формата результата или ключа выше приведет падению тестов + } + // public method + getPrice(productId) { + const desiredProduct = DB.getProduct(productId); + finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice; + return finalPrice; + } +} + +it("White-box test: When the internal methods get 0 vat, it return 0 response", async () => { + // нет никаких требований, чтобы пользователи могли рассчитать НДС, только показать конечную цену. Тем не менее, мы ошибочно настаиваем на этом, чтобы протестировать внутреннее устройство класса + expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0); +}); +``` + +
    + +

    + +## ⚪ ️ ️1.5 Выбирайте правильные имитации: предпочитайте stubs и spies вместо mocks + +:white_check_mark: **Сделать:** Имитации, которые используются в тестах, можно назвать злом во благо. Они связаны с внутренними компонентами, но некоторые из них приносят огромную пользу ([Читайте здесь напоминание о двойниках тестов: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). + +Прежде чем использовать объекты-имитации, вы должны спросить себя: используется ли он для тестирования функциональности, которая требуется в процессе разработки?. Если нет, то это появление предпосылок white-box тестирования. + +Например, если вы хотите протестировать корректную работу вашего приложения, в то время, как платежный сервис не работает, то можете поставить заглушку `(stub)` на платежный сервис и вернуть несколько 'No Response', чтобы убедится в том, что тестируемый модуль возвращает правильное значение. Такой подход проверяет поведение/ответ/результат работы нашего приложения при определённых сценариях. Также можно использовать `spy` для подтверждения того, что письмо было отправлено в ситуациях, когда сервис упал. Такая проверка поведения также может быть указана в техническом задании (“Send an email if payment couldn’t be saved”). C другой стороны, мокинг платежного сервиса и дальнейший его вызов с корректными типами данных говорит о том, что ваш тест предназначен для проверки внутренней реализации, которая не имеет отношения к функциональности приложения и может часто подвергаться изменениям. +
    + +❌ **Иначе:** Любой рефакторинг кода требует поиска всех `mocks` в коде и последующего обновления. Тесты начинают вредить, нежели, чем приносить пользу + +
    + +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: Мокинг используется для тестирования внутренней реализации + +![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg "Examples with Sinon") + +```javascript +it("When a valid product is about to be deleted, ensure data access DAL was called once, with the right product and right config", async () => { + // предположим, что мы уже добавили продукт + const dataAccessMock = sinon.mock(DAL); + // тестирование внутренних компонентов является нашей главной целью, а не просто побочным эффектом + dataAccessMock + .expects("deleteProduct") + .once() + .withArgs(DBConfig, theProductWeJustAdded, true, false); + new ProductService().deletePrice(theProductWeJustAdded); + dataAccessMock.verify(); +}); +``` + +
    + +### :clap: Правильно: spies сосредоточены на проверке требований, но в качестве побочного эффекта неизбежно затрагивают внутренние компоненты + +```javascript +it("When a valid product is about to be deleted, ensure an email is sent", async () => { + // предположим, что мы уже добавили продукт + const spy = sinon.spy(Emailer.prototype, "sendEmail"); + new ProductService().deletePrice(theProductWeJustAdded); + // мы имеем дело с внутренними компонентами, но как побочный эффект тестирования + expect(spy.calledOnce).to.be.true; +}); +``` + +
    + +

    + +## 📗 Хочешь изучить данные подходы на видео? + +### Переходи по ссылке на мой онлайн-курс [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +

    + +## ⚪ ️1.6 Не используй "foo". Используй реалистичные входные данные + +:white_check_mark: **Делать:** Часто производственные ошибки обнаруживаются при очень специфических и неожиданных входных данных - чем реалистичнее тестовые данные, тем больше шансов обнаружить ошибки на ранней стадии. Используйте специальные библиотеки, такие как [Chance](https://github.com/chancejs/chancejs) или [Faker](https://www.npmjs.com/package/faker), для генерации псевдореальных данных, напоминающих по разнообразию и форме производственные данные. Например, такие библиотеки могут генерировать реалистичные телефонные номера, имена пользователей, кредитные карты, названия компаний и даже текст "lorem ipsum". Вы также можете создать несколько тестов (поверх модульных тестов, а не в качестве замены), которые рандомизируют поддельные данные, чтобы растянуть тестируемый модуль или даже импортировать реальные данные из производственной среды. Хотите перейти на новый уровень? Смотрите следующий пункт (тестирование на основе свойств). +
    + +❌ **Иначе:** Все тесты при разработке могут ложно показать зеленый цвет при использовании данных типа "Foo". Однако, все может упасть, если на вход попадет строка вида "@3e2ddsf . ##' 1 fdsfds . fds432 AAAA". + +
    + +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: Набор тестов, который проходит из-за нереалистичных данных + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +const addProduct = (name, price) => { + const productNameRegexNoSpace = /^\S*$/; //no white-space allowed + + if (!productNameRegexNoSpace.test(name)) return false; //this path never reached due to dull input + + // здесь какая-то логика + return true; +}; + +test("Wrong: When adding new product with valid properties, get successful confirmation", async () => { + // строка "Foo", которая используется во всех тестах, никогда не вызывает ложного результата + const addProductResult = addProduct("Foo", 5); + expect(addProductResult).toBe(true); + // positive-false: операция прошла успешно, так как мы не пробовали использовать длинное название продукта, включающее пробелы +}); +``` + +
    + +### :clap: Правильно: Генерация реалистичных входных данных + +```javascript +it("Better: When adding new valid product, get successful confirmation", async () => { + const addProductResult = addProduct( + faker.commerce.productName(), + faker.random.number() + ); + // случайно сгенерированные входные данные: {'Sleek Cotton Computer', 85481} + expect(addProductResult).to.be.true; + // тест провалился: данные, которые мы подали на вход, сработали не так, как планировалось + // мы обнаружили ошибку +}); +``` + +
    + +

    + +## ⚪ ️ 1.7 Тестирование множества комбинаций входных данных с помощью тестирования на основе свойств + +:white_check_mark: **Сделать:** Обычно для тестирования выбирается несколько комбинаций входных данных. Даже когда они напоминают реальные данные (смотри пункт [‘1.6’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), мы охватываем только несколько наборов входных данных (method(‘’, true, 1), method(“string” , false , 0)), Однако, в продакшене, API вызываемый с 5 параметрами, может быть вызван с тысячами различных перестановок, одна из которых может уронить весь процесс ([см. Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). Представьте, что вы бы могли написать тест, который автоматически генерирует 1000 перестановок различных входных данных и отслеживает те данные, на которых код падает? Тестирование на основе свойств позволяет это сделать: отправляя на вход множество комбинации различных данных, вы увеличиваете вероятность случайного обнаружения ошибки. Например, при наличии метода addNewProduct(id, name, isDiscount) поддерживающие библиотеки будут вызывать этот метод со многими комбинациями (number, string, boolean), например (1, "iPhone", false), (2, "Galaxy", true). Такой тип тестирования может быть организован, используя разные тестовые фреймворки (Mocha, Jest, etc) с различными библиотеками [js-verify](https://github.com/jsverify/jsverify) или [testcheck](https://github.com/leebyron/testcheck-js) (имеет документацию лучше). Обновление: Nicolas Dubien предлагает ниже такую библиотеку, как [checkout fast-check](https://github.com/dubzzz/fast-check#readme), которая имеет дополнительные возможности и также активно поддерживается. +
    + +❌ **Иначе:** Неосознанно вы отправляете на вход различные данные, с которыми код, как правило, работает. К сожалению, это снижает эффективность тестирования, как инструмента для выявления ошибок. + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Тестирование множества входных данных с помощью библиотеки “fast-check” + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +import fc from "fast-check"; + +describe("Product service", () => { + describe("Adding new", () => { + // запуститься 100 раз с разными свойствами + it("Add new product with random yet valid properties, always successful", () => + fc.assert( + fc.property(fc.integer(), fc.string(), (id, name) => { + expect(addNewProduct(id, name).status).toEqual("approved"); + }) + )); + }); +}); +``` + +
    + +

    + +## ⚪ ️ 1.8 При необходимости используйте только короткие и последовательные snapshots + +:white_check_mark: **Сделать:** Если существует необходимость в [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), используйте короткие снимки (i.e. 3-7 lines), которые являются частью теста ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)), а не во внешних файлах. Соблюдение этого правила позволит вашим тестам оставаться понятными и надежными. + +С другой стороны, руководства по "классическим снимкам" призывают хранить большие файлы (например, разметку рендеринга компонента, результат API JSON) на каком-то внешнем носителе и каждый раз при выполнении теста сравнивать полученный результат с сохраненной версией. Это может привести к тому, что, тест будет привязан к огромному количеству данных (например, 1000 строк), о которых разработчик никогда не задумывался. Почему так нельзя делать? аким образом, существует 1000 причин, по которым ваш тест может не пройти - достаточно изменения одной строки, чтобы снимок стал недействительным, а это, скорее всего, будет происходить часто. Как часто? Каждый пробел, комментарий или незначительное изменение HTML/CSS. Мало того, что название теста не дает представления о том, что пошло не так, поскольку он просто проверяет, что 1000 срок кода остались неизменны, так еще и вводит разработчика в заблуждение, заставляя принимать за желаемую истину документ, который он не сможет проверить. Все это является признаками теста, который стремиться захватить сразу многое. + +Стоит отметить, что иногда, такие большие снимки приемлемы - когда проверяется схема, а не данные (извлечение значений и фокус на определенном поле) или при редком изменении документа. +
    + +❌ **Иначе:** UI тест провалился. Код кажется правильным, а на экране отображаются идеальные пиксели. Так что же случилось? Ваше snapshot-тестирование просто обнаружило разницу между исходным документом и текущим полученным - в разметку был добавлен 1 пробел... + +
    + +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: Тестируем невидимые 2000 строк кода + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +it("TestJavaScript.com is renderd correctly", () => { + // Arrange + + // Act + const receivedPage = renderer + .create( + + {" "} + Test JavaScript{" "} + + ) + .toJSON(); + + // Assert + expect(receivedPage).toMatchSnapshot(); + // теперь мы неявно поддерживаем документ длиной в 2000 строк + // каждый дополнительный перенос строки или комментарий приведет к падению этого теста +}); +``` + +
    + +### :clap: Правильно: Здесь все на виду + +```javascript +it("When visiting TestJavaScript.com home page, a menu is displayed", () => { + // Arrange + + // Act + const receivedPage = renderer + .create( + + {" "} + Test JavaScript{" "} + + ) + .toJSON(); + + // Assert + + const menu = receivedPage.content.menu; + expect(menu).toMatchInlineSnapshot(` +
      +
    • Home
    • +
    • About
    • +
    • Contact
    • +
    +`); +}); +``` + +
    + +

    + +## ⚪ ️Копируйте только необходимый код + +:white_check_mark: **Сделать:** Включайте в тест только необходимое, что влияет на результат теста, но не более того. В качестве примера, рассмотрим тест, который должен учитывать 100 строк JSON. Тащить за собой столько строк в каждый тест - утомительно. Если извлекать строки за пределы transferFactory.getJSON(), то тест станет неясным, так как без данных трудно соотнести результат теста и причину (почему он должен вернуть статус 400?). В книге x-unit patterns, такой паттерн называется "таинственный гость" - что-то скрытое повлияло на результат наших тестов, но мы не знаем, что именно. +Для улучшения ситуации, мы можем извлечь повторяющиеся детали, оставляя только то, что имеет значение для теста. Как, например, в данной ситуации: transferFactory.getJSON({sender: undefined}). Здесь видно, что пустое поле отправителя является той самой причиной, которая приведет к ошибке валидации. +
    + +❌ **Иначе:** Копирование огромного количества строк JSON приведет к тому, что ваши тесты станут нечитаемыми и трудно поддерживаемыми. + +
    + +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: Тяжело понять причину ошибки, так как она скрыта от глаз пользователя в большом количестве строк JSON + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +test("When no credit, then the transfer is declined", async () => { + // Arrange + const transferRequest = testHelpers.factorMoneyTransfer(); // вернемся к 200 строкам JSON; + const transferServiceUnderTest = new TransferService(); + + // Act + const transferResponse = await transferServiceUnderTest.transfer( + transferRequest + ); + + // Assert + expect(transferResponse.status).toBe(409); + // Почему мы ждем, что тест упадет, ведь все выглядит корректным 🤔? +}); +``` + +
    + +### :clap: Правильно: В данном случае тест выявляет то, что является причиной конечного результата + +```javascript +test("When no credit, then the transfer is declined ", async () => { + // Arrange + const transferRequest = testHelpers.factorMoneyTransfer({ + userCredit: 100, + transferAmount: 200, + }); // очевидно, что здесь недостаток средств + const transferServiceUnderTest = new TransferService({ + disallowOvercharge: true, + }); + + // Act + const transferResponse = await transferServiceUnderTest.transfer( + transferRequest + ); + + // Assert + expect(transferResponse.status).toBe(409); // Очевидно, что если у пользователя не хватает средств, то все упадет +}); +``` + +
    + +

    + +## ⚪ ️ 1.10 Не отлавливайте ошибки - ожидайте их + +:white_check_mark: **Сделать:** При попытке отловить, что некоторый ввод данных приводит к ошибке, может показаться правильным использование конструкции try-catch-finally и утверждать, что было введено catch. В результате, получается неудобный и объемный тест (пример ниже), который скрывает саму идею теста и получения результата. + +Более элегантной альтернативой является использование однострочного матчера Chai: expect(method).to.throw (или в Jest: expect(method).toThrow()). Обязательно убедитесь, что исключение содержит свойство, которое указывает на тип ошибки, иначе, просто получив общую ошибку, пользователю будет показано сообщение, которое его разочарует. +
    + +❌ **Иначе:** Из отчетов о тестировании (например, отчетов CI) будет сложно сделать вывод о том, что пошло не так + +
    + +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: Объемный тест, который пытается проверить ошибку через try-catch + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +it("When no product name, it throws error 400", async () => { + let errorWeExceptFor = null; + try { + const result = await addNewProduct({}); + } catch (error) { + expect(error.code).to.equal("InvalidInput"); + errorWeExceptFor = error; + } + expect(errorWeExceptFor).not.to.be.null; + // если это утверждение не сработает, то результаты тестов покажут, + // что какое-то значение равно null, а об отсутствующем исключении не будет ни слова +}); +``` + +
    + +### :clap: Правильно: Тест, который может легко понять даже QA или product-менеджер + +```javascript +it("When no product name, it throws error 400", async () => { + await expect(addNewProduct({})) + .to.eventually.throw(AppError) + .with.property("code", "InvalidInput"); +}); +``` + +
    + +

    + +## ⚪ ️ 1.11 Маркируй свои тесты + +:white_check_mark: **Сделать:** Разные тесты должны запускаться по-разному: quick smoke, IO-less, тесты должны запускаться, когда разработчик сохраняет код или делает коммит. Сквозные тесты запускаются при попытке нового pull-request и многое другое. Этого можно достичь, если помечать тесты ключевыми словами #cold, #api, #sanity, чтобы вы могли искать их с помощью вашей системы тестирования и вызывать сразу нужное количество определенных тестов. Например, вот как можно вызвать группу тестов с помощью Mocha: mocha — grep ‘sanity’. +
    + +❌ **Иначе:** Запуск всех существующих тестов, включая тесты, которые выполняют десятки запросов к БД, каждый раз когда разработчик вносит небольшое изменение, может быть медленным и отвлекать разработчиков от тестирования + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Пометка тестов как '#cold-test' позволяет программе выполнять только быстрые тесты (cold===быстрые тесты, которые не делают IO и могут выполняться часто, даже пока разработчик набирает текст). + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +// это быстрый тест, который помечен соотвествующим образом, +// чтобы пользователь или CI могли часто его запускать +describe("Order service", function () { + describe("Add new order #cold-test #sanity", function () { + test("Scenario - no currency was supplied. Expectation - Use the default currency #sanity", function () { + // здесь логика + }); + }); +}); +``` + +
    + +

    + +## ⚪ ️ 1.12 Структурируйте тесты, как минимум, на 2 уровня + +:white_check_mark: **Сделать:** Придайте набору тестов некую структуру, чтобы любой, кто посмотрит на них, мог легко понять, что происходит (тесты - лучшая документация). Общим методом для этого является размещение как минимум двух блоков "describe" над тестами: первый - для названия тестируемого блока, а второй - для дополнительного разделения тестов, например, сценария или пользовательских категорий (см. примеры кода и скриншот ниже). +Данное разделение также улучшить финальные отчеты по тестированию. Читатель сможет легко разобраться с категориями тестов, найти нужный раздел и понять, где могла возникнуть ошибка. Более того, разработчику будет легче ориентироваться в случае большого количества тестов. Существует несколько способов придать тестам структуру, которое можно найти здесь [given-when-then](https://github.com/searls/jasmine-given) и здесь [RITE](https://github.com/ericelliott/riteway) + +
    + +❌ **Иначе:** При просмотре отчета с длинным и плоским списком тестов приходиться бегло просматривать длинные тексты, чтобы понять основные сценарии и выяснить причину неудачи определенных тестов. Рассмотрим следующий случай: если при наборе в 100 тестов 7 из них окажутся неудачными, то придется прочитать их описание, чтобы понять, как они друг с другом связаны. Однако, если существует разделение на структуры, то причина падения тестов, может быть общей для них и разработчик быстро сделает вывод о том, что стало причиной или, по крайне мере, где она находится. +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Разделение на структуры набора тестов приводит к удобному отчету + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +// тестируемый блок +describe("Transfer service", () => { + // сценарий + describe("When no credit", () => { + // ожидание + test("Then the response status should decline", () => {}); + + // ожидание + test("Then it should send email to admin", () => {}); + }); +}); +``` + +![alt text](assets/hierarchical-report.png) + +
    + +### :thumbsdown: Неправильно: Плоский список тестов усложняет поиск ошибки + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Mocha") + +```javascript +test("Then the response status should decline", () => {}); + +test("Then it should send email", () => {}); + +test("Then there should not be a new transfer record", () => {}); +``` + +![alt text](assets/flat-report.png) + +
    + +
    + +

    + +## ⚪ ️1.13 Дополнительные общие правила по написанию тестов + +:white_check_mark: **Сделать:** Эта заметка посвящена советам по тестированию, которые связаны с Node JS или, по крайней мере, могут быть проиллюстрированы на его примере. Однако в этом пункте сгруппировано несколько советов, не связанных с Node, которые хорошо известны + +Изучайте и практикуйте [принципы TDD](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — они являются очень ценным инструментов для многих разработчиков, однако не пугайтесь, если они вам не подойдут. Рассмотрите возможность написания тестов до начала разработки в стиле [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), убедитесь, что каждый тест проверяет ровно один смысловой элемент и после того, как вы найдете ошибку, перед тем как ее исправить - напишите тест, который обнаружит эту ошибку в будущем. Пусть каждый тест упадет хотя бы один раз, прежде его цвет станет зеленым. Начните разработку модуля с написания простого и быстрого кода, удовлетворяющего тесту, а после постепенно рефакторите и доведите его до уровня продакшн-кода. Избегайте любой зависимости от окружения (файловые пути, ОС и другое). +
    + +❌ **Иначе:** Мудрость, которая копилась десятилетиями, пройдет мимо вас + +

    + +# Раздел 2️⃣: Тестирование Backend + +## ⚪ ️2.1 Увеличьте разнообразие своих тестов. Не ограничивайте себя только юнит-тестами и пирамидой + +:white_check_mark: **Сделать:** [Пирамида тестирования](https://martinfowler.com/bliki/TestPyramid.html), несмотря на то, что ей уже более 10 лет, является отличной и актуальной моделью, которая предлагает 3 типа тестирования и влияет на стратегию тестирования большинства разработчиков. В то же время, появились методы тестирования, которые скрываются в тени данной пирамиды. Принимая во внимание все кардинальные изменения, которые наблюдались в течение последних 10 лет (микросервисы, облака, бессерверные технологии), встает вопрос: возможно ли применить одну модель тестирования для всех типов приложений? Или может быть лучше рассмотреть возможность внедрения новых практик? + +Не поймите меня неправильно, в 2019 году пирамида тестирования, TDD и юнит-тесты по-прежнему являются мощной техникой и, вероятно, лучше всего подходят для многих приложений. Только, как и любая другая модель, несмотря на свою ценность, [иногда она дает сбои](https://en.wikipedia.org/wiki/All_models_are_wrong). Например, рассмотрим IoT-приложение, которое получает множество событий типа Kafka/RabbitMQ, которые затем попадают в хранилище данных и в конечном итоге запрашиваются аналитическим пользовательским интерфейсом. Действительно ли мы должны тратить 50% бюджета на тестирование на написание модульных тестов для приложения, которое ориентировано на интеграцию и почти не имеет логики. По мере увеличения разнообразия типов приложений (боты, криптовалюты, Alexa-skills) также растут шансы найти такие сценарии, в которых пирамида тестирования не является идеальным решением. + +Пришло время увеличить свое портфолио тестировщика и познакомиться еще большим количеством подходов к написанию тестов (следующие пункты содержат определенные идеи), начать использовать модели тестирования, подобные пирамиде и также научится сопоставлять типы тестирования с реальными проблемами, с которыми вы можете столкнуться ("Эй, наш API сломан, давайте напишем тестирование контрактов, ориентированное на потребителя!"). Необходимо также научиться диверсифицировать свои тесты, как инвестор, который создает портфель на основе анализа рисков - оценить, где могут возникнуть проблемы, и подобрать превентивные меры для снижения этих потенциальных рисков. + +Предостережение: спор о TDD в мире программного обеспечения принимает типичный облик ложной дихотомии: одни проповедуют его повсеместное использование, другие считают, что это дьявол. Каждый, кто абсолютно уверен в чем то одном - ошибается :] +
    + +❌ **Иначе:** Вы можете пропустить такие инструменты, как Fuzz, Lint и мутации, которые могут принести пользу за 10 минут. + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Синди Шридхаран предлагает большое разнообразие подходов к тестированию в своем замечательном посте ‘Testing Microservices — the same way’. + +![alt text](assets/bp-12-rich-testing.jpeg "Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the sane way’") + +☺️Пример: [YouTube: “Beyond Unit Tests: 5 Shiny Node.JS Test Types (2018)” (Yoni Goldberg)](https://www.youtube.com/watch?v=-2zP494wdUY&feature=youtu.be) + +
    + +![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "A test name that constitutes 3 parts") + +
    + +

    + +## ⚪ ️2.2 Тестирование компонентов может стать вашим лучшим другом + +:white_check_mark: **Сделать:** Каждый модульный тест покрывает небольшую часть приложения. Покрыть его целиком является дорогим удовольствием, в то время как сквозное тестирование легко покрывает большую часть приложения, но является нестабильным и медленным. Почему бы не применить сбалансированный подход, и не писать тесты, которые по размеру больше, чем модульные, но меньше, чем сквозное тестирование. Компонентное тестирование - это то, что вобрало в себя лучшее из двух перечисленных подходов. Одно сочетает в себе разумную производительность и возможность применения паттернов TDD, а также реалистичное и большое покрытие. + +Компонентные тесты фокусируются на "единице" микросервиса, они работают с API, не имитируют ничего, что принадлежит самому микросервису (например, реальную БД или, по крайней мере, ее версию в памяти), но затыкают все, что является внешним, например, вызовы других микросервисов. Поступая таким образом, мы тестируем то, что развертываем, подходим к приложению от внешнего к внутреннему и обретаем большую уверенность за разумное время. + +[У нас есть полное руководство, которое посвящено исключительно написанию компонентных тестов правильным способом](https://github.com/testjavascript/nodejs-integration-tests-best-practices) + +
    + +❌ **Иначе:** Вы можете потратить огромное количество времени на написание модульных тестов и обнаружить, что покрытие системы составляет всего 20%. + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Supertest позволяет приближаться к Express API в процессе (с высокой скоростью и широким охватом уровней) + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) allows approaching Express API in-process (fast and cover many layers)") + +
    + +

    + +## ⚪ ️2.3 Убедитесь, что новые релизы не нарушают API, используя контрактные тесты + +:white_check_mark: **Сделать:** Итак, у вашего микросервиса есть несколько клиентов, и вы запускаете несколько версий сервиса из соображений совместимости (чтобы все были довольны). Затем вы изменяете какое-то поле и "бум!", какой-то важный клиент, который полагается на это поле, возмущен. Это и есть Catch-22 в мире интеграции: Для серверной стороны очень сложно учесть все многочисленные ожидания клиентов— С другой стороны, клиенты не могут провести никакого тестирования, потому что сервер контролирует даты выпуска. Существует целый ряд методов, которые могут смягчить проблему контрактов, некоторые из них просты, другие более функциональны и требуют более сложного обучения. +При простом подходе, API предоставляется вместе с npm-пакетом с типизацией (JSDoc, TypeScript). Потребители данного пакета могут получить библиотеку и воспользоваться преимуществами автодополнения (IntelliSense) и валидация во время разработки. Более сложный подход включает в себя [PACT](https://docs.pact.io/), который был создан для формализации этого процесса с помощью очень разрушительного подхода - не сервер определяет план тестирования для себя, а клиент определяет тесты для... сервера! PACT может записывать ожидания клиента и помещать их в общее место, "broker", так что сервер может извлекать эти ожидания и запускать на каждой сборке, используя библиотеку PACT, чтобы обнаружить нарушенные контракты - ожидания клиента, которые не выполнены. Таким образом, все несоответствия API сервера и клиента будут обнаружены на ранних стадиях сборки и могут уберечь разработчика от больших проблем. +
    + +❌ **Иначе:** Альтернативой является ручное тестирование или развертывание + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: + +![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") + +![alt text](assets/bp-14-testing-best-practices-contract-flow.png) + +
    + +

    + +## ⚪ ️ 2.4 Тестируйте middlewares изолированно + +:white_check_mark: **Сделать:** Большинство разработчиков пренебрегают тестированием Middleware, потому что они являются небольшой частью системы и требуют живого сервера Express. Обе причины ошибочны, так как Middleware, несмотря на свой размер, имеют огромное влияние на все или большинство запросов и могут быть легко протестированы, как чистые функции. Для тестирования функции middleware нужно просто вызвать ее и "шпионить" ([например, с помощью Sinon](https://www.npmjs.com/package/sinon)) за взаимодействием с объектами {req,res}, чтобы убедиться, что функция отработала корректно. Библиотека [node-mock-http](https://www.npmjs.com/package/node-mocks-http) идет еще дальше и анализирует объекты {req,res}, а также следит за их поведением. Например, он может утверждать, соответствует ли статус http, который был установлен на объекте res, ожидаемому (см. пример ниже). +
    + +❌ **Иначе:** Ошибка в middleware Express === ошибка большинства/всех запросов + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Тестирование middleware изолированно без выполнения сетевых запросов и включения Express полностью + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +// middleware, которое мы хотим протестировать +const unitUnderTest = require("./middleware"); +const httpMocks = require("node-mocks-http"); +// синтаксис в Jest, равный describe() и it() в Mocha +test("A request without authentication header, should return http status 403", () => { + const request = httpMocks.createRequest({ + method: "GET", + url: "/user/42", + headers: { + authentication: "", + }, + }); + const response = httpMocks.createResponse(); + unitUnderTest(request, response); + expect(response.statusCode).toBe(403); +}); +``` + +
    + +

    + +## ⚪ ️2.5 Измерение и рефакторинг с использованием инструментов статистического анализа + +:white_check_mark: **Сделать:** Использование инструментов статического анализа помогает объективно улучшить качество кода и сохранить его работоспособность. Вы можете добавить инструменты статистического анализа в сборку CI, чтобы отследить "запахи" вашего кода на этапе сборки. Основными преимуществами статического анализа перед обычным линтингом являются возможность проверки качества в контексте нескольких файлов (например, обнаружение дубликатов), выполнение расширенного анализа (например, сложности кода) и отслеживание истории и прогресса проблем кода. Вы можете использовать следующие сервисы: [SonarQube](https://www.sonarqube.org/) (4,900+ [stars](https://github.com/SonarSource/sonarqube)) и [Code Climate](https://codeclimate.com/) (2,000+ [stars](https://github.com/codeclimate/codeclimate)) + +Благодарность: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + +❌ **Иначе:** При низком качестве кода ошибки и производительность всегда будут проблемой, которую не смогут исправить ни новые библиотеки, ни современный функционал. + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: CodeClimate - это инструмент, который предоставляет комплексные методы анализа + +![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg "Examples with CodeClimate") + +![alt text](assets/bp-16-yoni-goldberg-quality.png "CodeClimate, a commercial tool that can identify complex methods:") + +
    + +

    + +## ⚪ ️ 2.6 Check your readiness for Node-related chaos + +:white_check_mark: **Сделать:** Странно, но большинство тестов направлено на проверку логики и данных, в то время как иногда наибольшей проблемой (и это правда трудно исправить) может стать вопросы, касающиеся инфраструктуры. Например, вы когда-нибудь тестировали перегрузку памяти, падение сервера? Ваша система отслеживания понимает, когда ваш API становится в половину медленнее? Для того, чтобы протестировать и исправить такие ситуации Netflix придумали [Chaos engineering](https://principlesofchaos.org/). Его целью является осведомленность для тестирования устойчивости приложения к chaos-проблемам, а также различные фреймворки, чтобы это можно было протестировать. Например, один из его известных инструментов, [the chaos monkey](https://github.com/Netflix/chaosmonkey), случайным образом кладет серверы, чтобы убедиться, что наш сервис все еще может обслуживать пользователей и не полагается на один сервер (есть также версия для Kubernetes, [kube-monkey](https://github.com/asobti/kube-monkey)). Все эти инструменты работают на уровне хостинга или платформы, но что, если вы хотите протестировать чистейший Node-хаос. Например, вы хотите проверить, как ваш Node справляется с ошибками, которые были не пойманы, ошибками промисов, перегрузкой v8 при максимально допустимых 1.7GB. Вдруг вы хотите проверить, остается ли ваш UX удовлетворительных при частых блокировках цикла событий? Для решения данной проблемы я написал [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha), который генерирует все возможные виды хаоса, связанных с Node. +
    + +❌ **Иначе:** От этого не спрятаться. Закон Мерфи будет преследовать вас везде. + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Генерация всевозможных вариантов Node-хаоса для проверки устойчивости приложения + +![alt text](assets/bp-17-yoni-goldberg-chaos-monkey-nodejs.png "Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to chaos") + +
    + +
    + +## ⚪ ️2.7 Избегайте использования глобальных фикстур и seeds. Для каждого теста должны быть свои собственные данные + +:white_check_mark: **Сделать:** Согласно золотому правилу (пункт 0), каждый тест должен использовать свой собственный набор строк в БД, чтобы избежать перекрытия данных. На деле же, данное правило часто нарушается тестировщиками, которые загружают данные в БД перед запуском тестов (фикстуры), чтобы улучшить производительность. Хотя производительность является реальной причиной для беспокойства - она далеко не главная (см. пункт "Компонентное тестирование"). Сложность тестирования - гораздо более болезненная проблема, решение которой в большинстве случаев определяется другими соображениями. На практике, нужно сделать так, чтобы каждый тест явным образом добавляет нужные ему записи в БД и работал только с ними. Если производительность становится критической проблемой - компромиссом может быть реализация тестов, которые не изменяют данные (например, запросы). +
    + +❌ **Иначе:** Какие-то тесты не сработали, развертывание проекта прервано и разработчики тратят время на выяснение ошибки. А есть ли она? Кажется, что нет, ведь два теста просто изменили одни и те же seed-данные. + +
    + +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: тесты не являются независимыми и полагаются на некий глобальный хук для получения глобальных данных + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +before(async () => { + // добавление данных о сайтах и администраторах в БД. Где они сейчас находятся? Снаружи, во внешнем JSON или фреймворке + await DB.AddSeedDataFromJson("seed.json"); +}); +it("When updating site name, get successful confirmation", async () => { + // я знаю, что название сайта "portal" существует - я видел его в seed-файлах + const siteToUpdate = await SiteService.getSiteByName("Portal"); + const updateNameResult = await SiteService.changeName( + siteToUpdate, + "newName" + ); + expect(updateNameResult).to.be(true); +}); +it("When querying by site name, get the right site", async () => { + // я знаю, что название сайта "portal" существует - я видел его в seed-файлах + const siteToCheck = await SiteService.getSiteByName("Portal"); + expect(siteToCheck.name).to.be.equal("Portal"); // Неудача! Предыдущий тест меняет название :[ +}); +``` + +
    + +### :clap: Правильно: Каждый тест действует на своем собственном наборе данных, что позволяет оставаться в рамках теста + +```javascript +it("When updating site name, get successful confirmation", async () => { + // тест добавляет новые записи и работает только с ними + const siteUnderTest = await SiteService.addSite({ + name: "siteForUpdateTest", + }); + const updateNameResult = await SiteService.changeName( + siteUnderTest, + "newName" + ); + expect(updateNameResult).to.be(true); +}); +``` + +
    + +
    + +## ⚪ ️2.8 Выберите четкую стратегию очистки данных: After-all (рекомендуется) или after-each + +:white_check_mark: **Сделать:** Время, когда тесты начинают удаление данных, определяет способ их написания. Наиболее жизнеспособные варианты: after-all и after-each. При выборе второго варианта, удаление данных гарантирует полную очистку и создает преимущества для разработчика. В начале теста не существует других записей. Можно быть уверенным, какие данные запрашиваются. Иногда даже возникает соблазн посчитать строки при проверке assertions. Однако, существуют и серьезные недостатки. При работе в мульти-процессном режиме тесты могут мешать друг другу. Пока процесс-1 очищает таблицы, процесс-2 в тот же момент запрашивает данные и падает (потому что БД была внезапно удалена процессом-1). Кроме того, сложнее исправить проблемы в неудачных тестах - при посещении БД не будет обнаружено никаких записей. + +Второй вариант - это after-all - удаление данных после завершения всех тестов (или даже ежедневно!). Такой подход означает, что одна и та же БД с существующими записями служит для всех процессов и тестов. Чтобы не наступать друг другу на пятки, тесты должны работать с конкретными записями, которые они добавили. Нуждаетесь в проверке, какая запись была добавлена? Предположите, что есть еще тысячи записей, и сделайте запрос к записи, которая была добавлена явно. Нужно проверить, что запись удалена? Нельзя предполагать, что таблица пуста - проверьте, что этой конкретной записи там нет. Такая техника дает несколько преимуществ: она работает мульти-процессном режиме, когда разработчик хочет понять, что произошло - данные есть и не удалены. Также это увеличивает шанс найти ошибки так как БД полна записей. [Смотрите полную таблицу сравнений здесь](https://github.com/testjavascript/nodejs-integration-tests-best-practices/blob/master/graphics/db-clean-options.png). +
    . + +❌ **Иначе:** При отсутствии разделения записей или удаления - тесты будут наступать на пятки сами себе. Использование транзакций работает только в реляционных БД и, при появлении внутренних транзакций, может стать сложнее. + +
    + +
    Примеры кода + +
    + +### :clap: After-all: Необязательно удалять данные после каждого запуска. Чем больше данных у нас есть во время выполнения тестов, тем больше это похоже на продакшн. + +```javascript +// after-all очистка (рекомендуется) +// global-teardown.js +module.exports = async () => { + // ... + if (Math.ceil(Math.random() * 10) === 10) { + await new OrderRepository().cleanup(); + } +}; +``` + +
    + +
    + +## ⚪ ️2.9 Изолируйте компонент с помощью HTTP-перехватчика + +:white_check_mark: **Сделать:** Изолируйте тестируемый компонент, перехватывая любой исходящий HTTP-запрос и предоставляя желаемый ответ, чтобы HTTP API не пострадал. Nock - отличный инструмент для этой задачи, поскольку он предоставляет удобный синтаксис для определения поведения внешних сервисов. Изоляция необходима для предотвращения шума и снижения производительности, но в основном для моделирования различных сценариев и ответов - хороший симулятор полета не рисует чистое голубое небо, а приносит спокойные бури. Это усиливается в микросервисной архитектуре, где фокус всегда должен быть сосредоточен на одном компоненте без привлечения остальных. Хотя можно имитировать поведение внешнего сервиса с помощью тестов-двойников (mocking), предпочтительнее не трогать развернутый код и действовать на сетевом уровне, чтобы тесты были чисто "черным ящиком". Недостатком изоляции является невозможность обнаружения изменений в компоненте коллаборатора и недопонимания между двумя сервисами - обязательно компенсируйте это с помощью нескольких контрактных или E2E-тестов. +
    + +❌ **Иначе:** Некоторые сервисы предоставляют фейк-версию, которая может быть развернута локально, обычно с помощью Docker-Это сделает настройку легче и увеличит производительность. Однако, это не решит проблему моделирования различных responses. Некоторые сервисы предоставляют "песочницу" таким образом, что реальный сервис работает, но без побочных эффектов. Такой вариант не поможет моделировать различные сценарии и снизить шум. + +
    + +
    Примеры кода + +
    + +### :clap: Предотвращение сетевых обращений к внешним компонентам позволяет моделировать сценарии и минимизировать шум + +````javascript +// перехват запросов к стороннему API и возврат заранее определенного ответа +beforeEach(() => { + nock('http://localhost/user/').get(`/1`).reply(200, { + id: 1, + name: 'John', + }); +});``` +```` + +
    + +
    + +## ⚪ ️2.10 Тестируйте схему ответа в основном при наличии автоматически генерируемых полей + +:white_check_mark: **Сделать:** Если невозможно подтвердить наличие определенных данных, проверяйте наличие и типы обязательных полей. Иногда ответ может содержать важные поля с динамическими данными, которые невозможно предугадать при написании теста, такие как даты. В случае, когда точно известно, что поля не будут иметь значения null, а также будут содержать правильные типы, то все равно нужно обязательно это проверить. Большинство библиотек поддерживают проверку типов. Если ответ - небольшой, то можно выполнить проверку в рамках одного утверждения (см. пример кода). Другой вариант - проверка полного ответа по OpenAPI (Swagger). Большинство фреймворков для тестирования имеют расширения, которые проверяют ответы API, согласно их документации. + +
    + +❌ **Иначе:** Несмотря на то, что вызывающий код или API полагается на некоторое поле с динамическими данными (например, ID или дата), оно может не вернуться в ответе + +
    + +
    Примеры кода + +
    + +### :clap: Проверка, что поля с динамическим значением существуют и имеют правильный тип + +```javascript +test("When adding a new valid order, Then should get back approval with 200 response", async () => { + // ... + // Assert + expect(receivedAPIResponse).toMatchObject({ + status: 200, + data: { + id: expect.any(Number), // Any number satisfies this test + mode: "approved", + }, + }); +}); +``` + +
    + +
    + +## ⚪ ️2.12 Проверка крайних случаев интеграции + +:white_check_mark: **Сделать:** При проверке интеграций нельзя ограничиваться только проверками либо "happy", либо "sad" вариантов. Помимо проверки на ошибки (например, HTTP 500), нужно так же смотреть на аномалии на уровне сети или скорость ответов таймера. Это демонстрирует то, что ваш код устойчив и может обрабатывать различные сетевые сценарии. Надежные перехватчики могут легко имитировать различное поведение сетевого взаимодействия. Он даже может понять, когда стандартное значение тайм-аута HTTP-клиента больше, чем смоделированное время ответа, и сразу же бросить исключение по тайм-ауту, не дожидаясь ответа. + +
    + +❌ **Иначе:** Все тесты пройдут, однако, продакшн не сможет корректно сообщить об ошибке, когда со стороны будут приходить исключения + +
    + +
    Примеры кода + +
    + +### :clap: Подтверждение, что при сбоях в сети, автоматический выключатель может спасти ситуацию + +```javascript +test("When users service replies with 503 once and retry mechanism is applied, then an order is added successfully", async () => { + // Arrange + nock.removeInterceptor(userServiceNock.interceptors[0]); + nock("http://localhost/user/") + .get("/1") + .reply(503, undefined, { "Retry-After": 100 }); + nock("http://localhost/user/").get("/1").reply(200); + const orderToAdd = { + userId: 1, + productId: 2, + mode: "approved", + }; + + // Act + const response = await axiosAPIClient.post("/order", orderToAdd); + + // Assert + expect(response.status).toBe(200); +}); +``` + +
    + +
    + +## ⚪ ️2.13 Тестируйте не менее 5 потенциально возможных результатов + +:white_check_mark: **Сделать:** При разработке тестов, продумайте, как минимум, охватить 5 потоков вывода. Когда ваш тест вызывает какой-то действие (например, вызов API), возникает ситуация, когда нужно протестировать результат этого действия. Важно понимать, что нас не интересует, как все работает. Наше внимание сосредоточено на конечном результате, на том, что может повлиять на пользователя. Конечные результаты можно разделить на следующие категории: + +• Ответ - Тест вызывает действие (например, через API) и получает ответ. Теперь он занимается проверкой корректности данных ответа, схемы и статуса HTTP. + +• Новое состояние - После вызова действия некоторые **общедоступные** данные, вероятно, будут изменены. + +• Внешние вызовы - После вызова действия приложение может вызвать внешний компонент через HTTP или любой другой транспорт. Например, вызов для отправки SMS, электронной почты или списания средств с кредитной карты. + +• Очереди сообщений - Результатом потока может быть сообщение в очереди. + +• Наблюдаемость - Некоторые вещи необходимо отслеживать, например, ошибки или значимые бизнес-события. Когда транзакция терпит неудачу, мы ожидаем не только правильного ответа, но и корректной обработки ошибок и правильного протоколирования/метрики. Эта информация поступает непосредственно к очень важному пользователю - оперативному пользователю (т.е. производственному SRE/админу). + + + +

    + +# Раздел 3️⃣: Тестирование Frontend + +## ⚪ ️ 3.1 Отделите пользовательский интерфейс от функциональности + +:white_check_mark: **Сделать:** Когда вы фокусируетесь на тестировании логики компонентов, детали пользовательского интерфейса становятся шумом, который необходимо удалить, чтобы ваши тесты могли сосредоточиться на чистых данных. Извлекайте нужные данные из разметки абстрактным способом, не слишком связанным с графической реализацией, тестируйте только чистые данные (в отличие от графических деталей HTML/CSS) и отключайте анимацию, которая замедляет работу. У вас может возникнуть соблазн избежать рендеринга и тестировать только заднюю часть пользовательского интерфейса (например, сервисы, действия, магазин), но это приведет к фиктивным тестам, которые не похожи на реальность и не выявят случаи, когда нужные данные даже не попадают в пользовательский интерфейс. + +
    + +❌ **Иначе:** Чистые расчетные данные вашего теста могут быть готовы за 10 мс, но тогда весь тест будет длиться 500 мс (100 тестов = 1 мин) из-за не имеющей отношения к делу анимации. + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Разделение пользовательского интерфейса + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +test("When users-list is flagged to show only VIP, should display only VIP members", () => { + // Arrange + const allUsers = [ + { id: 1, name: "Yoni Goldberg", vip: false }, + { id: 2, name: "John Doe", vip: true }, + ]; + + // Act + const { getAllByTestId } = render( + + ); + + // Assert - сперва извлеките данные из UI + const allRenderedUsers = getAllByTestId("user").map( + (uiElement) => uiElement.textContent + ); + const allRealVIPUsers = allUsers + .filter((user) => user.vip) + .map((user) => user.name); + expect(allRenderedUsers).toEqual(allRealVIPUsers); // сравнение данных +}); +``` + +
    + +### :thumbsdown: Неправильно: Совместное тестирование компонентов UI и данных + +```javascript +test("When flagging to show only VIP, should display only VIP members", () => { + // Arrange + const allUsers = [ + { id: 1, name: "Yoni Goldberg", vip: false }, + { id: 2, name: "John Doe", vip: true }, + ]; + + // Act + const { getAllByTestId } = render( + + ); + + // Assert - использование UI и данных в assertion + expect(getAllByTestId("user")).toEqual( + '[
  • John Doe
  • ]' + ); +}); +``` + +
    + +

    + +## ⚪ ️ 3.2 Запрашивать элементы HTML на основе атрибутов, которые вряд ли изменятся + +:white_check_mark: **Сделать:** Запрашивайте элементы HTML на основе атрибутов, которые, скорее всего, переживут графические изменения, в отличие от селекторов CSS и, например, меток формы. Если заданный элемент не имеет таких атрибутов, создайте специальный тестовый атрибут, например 'test-id-submit-button'. Такой путь не только гарантирует, что ваши функциональные/логические тесты никогда не сломаются из-за изменений внешнего вида, но и даст понять, что данный элемент и атрибут используются тестами и не должны быть удалены. + +
    + +❌ **Иначе:** Вы хотите протестировать функциональность входа в систему, которая охватывает множество компонентов, логики и сервисов, все настроено идеально - stubs, spies, вызовы Ajax изолированы. Все кажется идеальным. Затем тест не проходит, потому что дизайнер изменил CSS-класс div с "thick-border" на "thin-border". + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Запрос элемента с использованием специального атрибута для тестирования + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") + +```html +// код разметки (часть компонента React) +

    + + {value} + + +

    +``` + +```javascript +// в данном примере используется react-testing-library +test("Whenever no data is passed to metric, show 0 as default", () => { + // Arrange + const metricValue = undefined; + + // Act + const { getByTestId } = render(); + + expect(getByTestId("errorsLabel").text()).toBe("0"); +}); +``` + +
    + +### :thumbsdown: Неправильно: Надежда на CSS-атрибуты + +```html + +{value} + +``` + +```javascript +// в данном примере используется enzyme +test("Whenever no data is passed, error metric shows zero", () => { + // ... + + expect(wrapper.find("[className='d-flex-column']").text()).toBe("0"); +}); +``` + +
    + +
    + +## ⚪ ️ 3.3 По возможности тестируйте с реалистичным компонентом, после рендеринга + +:white_check_mark: **Сделать:** При любом разумном размере нужно тестировать компонент снаружи, как это делают пользователи. Полностью рендерите UI и проверяйте, что он ведет себя, как ожидается. Избегайте мокинга и частичного рендеринга - такой подход может привести к невыявленным ошибкам из-за недостатка деталей и сделать поддержку сложнее, так как тесты начинают влиять на внутренние компоненты (смотри пункт ['Favour blackbox testing'](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-14-stick-to-black-box-testing-test-only-public-methods)). Если один из дочерних компонентов значительно замедляет работу (например, анимация) или усложняет настройку - подумайте о явной замене его на фейковый. + +Учитывая все вышесказанное, следует сказать: эта техника работает для небольших/средних компонентов, которые содержат разумное количество дочерних компонентов. Полный рендеринг компонента со слишком большим количеством дочерних компонентов повлечет за собой трудности в анализе (анализ первопричины) и может стать слишком медленным. В таких случаях пишите несколько тестов на один большой родительский компонент и больше тестов на дочерние компоненты. + +
    + +❌ **Иначе:** Если залезть во внутренности компонента, вызывая его приватные методы, и проверить - вам придется рефакторить все тесты в случае изменения реализации компонента. У вас действительно есть возможности для такого уровня сопровождения? + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Полностью реалистичные компоненты после рендеринга + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Enzyme-blue.svg "Examples with Enzyme") + +```javascript +class Calendar extends React.Component { + static defaultProps = { showFilters: false }; + + render() { + return ( +
    + A filters panel with a button to hide/show filters + +
    + ); + } +} + +// в примерах используются React & Enzyme +test("Realistic approach: When clicked to show filters, filters are displayed", () => { + // Arrange + const wrapper = mount(); + + // Act + wrapper.find("button").simulate("click"); + + // Assert + expect(wrapper.text().includes("Choose Filter")); + // так пользователь будет обращаться к этому элементу +}); +``` + +### :thumbsdown: Неправильно: Мокинг данных и частичный рендеринг + +```javascript +test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { + // Arrange + const wrapper = shallow( + + ); + + // Act + wrapper.find("filtersPanel").instance().showFilters(); + // обратитесь к внутренним компонентам, не затрагивая UI и вызовите метод (white-box) + + // Assert + expect(wrapper.find("Filter").props()).toEqual({ title: "Choose Filter" }); + // что если мы изменим имя свойства или ничего не передадим +}); +``` + +
    + +
    + +## ⚪ ️ 3.4 Используйте встроенную по фреймворки поддержку асинхронного кода. Постарайтесь ускорить работу. + +:white_check_mark: **Сделать:** Во многих случаях время завершения тестируемого блока просто неизвестно (например, анимация приостанавливает появление элемента) - в этом случае предпочитайте детерминированные методы, которые предоставляют большинство платформ. Некоторые библиотеки позволяют ожидать выполнение операций (например, [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), другие предоставляют API для ожидания, например [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Иногда более элегантным способом является заглушка, использованная вместо медленно выполняющегося компонента, например, API, а затем, когда момент получения ответа станет детерминированным, компонент можно отрендерить явным способом. Если существует зависимость от какого-то "спящего" компонента, то может оказаться полезным [поторопить часы](https://jestjs.io/docs/en/timer-mocks). Спящий компонент - это паттерн, которого следует избегать, поскольку он заставляет ваш тест быть медленным (при слишком коротком периоде ожидания). Когда спящий режим неизбежен, а поддержка со стороны фреймворка для тестировании отсутствует, некоторые библиотеки npm, такие как [wait-for-expect](https://www.npmjs.com/package/wait-for-expect), могут помочь с полудетерминированным решением. +
    + +❌ **Иначе:** При длительном спящем режиме тесты будут работать на порядок медленнее. Попытка "заснуть" на небольшое количество времени приведет к тому, что тест будет падать, каждый раз, когда тестируемый модуль не отреагировал вовремя. Таким образом, все сводится к компромиссу между хрупкостью и плохой производительностью. +
    + +
    Примеры кода + +
    + +### :clap: Правильно: E2E API, который разрешается только после выполнения асинхронных операций (Cypress) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +// используем Cypress +cy.get("#show-products").click(); // перейдите по ссылке +cy.wait("@products"); // дождитесь появления маршрута +// эта строка будет выполнена только тогда, когда маршрут будет готов +``` + +### :clap: Правильно: Библиотека для тестирования, которая ожидает элементы DOM + +```javascript +// @testing-library/dom +test("movie title appears", async () => { + // элемент изначально отсуствует... + + // ожидайте появления + await wait(() => { + expect(getByText("the lion king")).toBeInTheDocument(); + }); + + // дождаться появления и вернуть элемент + const movie = await waitForElement(() => getByText("the lion king")); +}); +``` + +### :thumbsdown: Неправильно: Пользовательский код + +```javascript +test("movie title appears", async () => { + // элемент изначально отсуствует... + + // пользовательская логика (внимание: упрощенная, без timeout) + const interval = setInterval(() => { + const found = getByText("the lion king"); + if (found) { + clearInterval(interval); + expect(getByText("the lion king")).toBeInTheDocument(); + } + }, 100); + + // дождаться появления и вернуть элемент + const movie = await waitForElement(() => getByText("the lion king")); +}); +``` + +
    + +
    + +## ⚪ ️ 3.5 Наблюдайте за тем, как контент передается по сети + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") + +✅ **Сделать:** Примените какой-нибудь активный монитор, который обеспечит оптимизацию загрузки страницы а сети - это включает любые проблемы UX, такие как медленная загрузка страницы. Существует большое количество инструментов для проверки, таких как: [pingdom](https://www.pingdom.com/), AWS CloudWatch, [gcp StackDriver](https://cloud.google.com/monitoring/uptime-checks/). Они могут быть легко настроены для наблюдения за тем, жив ли сервер и отвечает ли он в рамках SLA. Это лишь поверхностный обзор того, что может пойти не так, поэтому предпочтительнее выбирать инструменты, специализирующиеся на фронтенде (например, [lighthouse](https://developers.google.com/web/tools/lighthouse/), [pagespeed](https://developers.google.com/speed/pagespeed/insights/)), которые выполняют более качественный анализ. В центре внимания должны быть метрики, которые непосредственно влияют на UX, такие как время загрузки страницы, [meaningful paint](https://scotch.io/courses/10-web-performance-audit-tips-for-your-next-billion-users-in-2018/fmp-first-meaningful-paint), [время, пока страница не станет интерактивной (TTI)](https://calibreapp.com/blog/time-to-interactive/). Кроме того, можно также следить за техническими причинами, такими как cжатие контента, время до первого байта, оптимизация изображений, обеспечение разумного размера DOM, SSL и многие другие. Желательно придерживаться этого правила, как во время разработки, так и в рамках CI и, самое главное, постоянно на продакшн-серверах/CDN. +
    + +❌ **Иначе:** Будет обидно осознавать, что после такой тщательной проработки пользовательского интерфейса, 100% прохождения функциональных тестов и сложной комплектации - UX ужасен и медлителен из-за неправильной настройки CDN. + +
    + +
    Примеры кода + +### :clap: Правильно: Использование Lighthouse для проверки загрузки страницы + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + +
    + +
    + +## ⚪ ️ 3.6 Заглушите нестабильные и медленные ресурсы, как backend API + +:white_check_mark: **Сделать:** При написании основных тестов (не E2E-тестов) избегайте привлечения ресурсов, которые находятся вне вашей ответственности и контроля, таких как бэкенд API, и используйте вместо них заглушки (т.е. test double). Вместо реальных сетевых вызовов API, используйте какую-нибудь библиотеку test double (например, [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble) и т.д.) для создания заглушки ответа API. Основным преимуществом является предотвращение флейкинга - тестовые или staging API по определению не очень стабильны и время от времени будут проваливать ваши тесты, хотя компонент ведет себя просто отлично (production env не был предназначен для тестирования и обычно ограничивает запросы). Это позволит смоделировать различное поведение API, которое должно определять поведение вашего компонента, например, когда данные не найдены или когда API выдает ошибку. И последнее, но не менее важное: сетевые вызовы значительно замедлят работу тестов + +
    + +❌ **Иначе:** Средний тест длится не более нескольких мс, типичный вызов API длится 100 мс>, это делает каждый тест в ~20 раз медленнее. + +
    + +
    Примеры кода + +
    + +### :clap: Doing It Right Example: Stubbing or intercepting API calls + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +// Тестируемый блок +export default function ProductsList() { + const [products, setProducts] = useState(false); + + const fetchProducts = async () => { + const products = await axios.get("api/products"); + setProducts(products); + }; + + useEffect(() => { + fetchProducts(); + }, []); + + return products ? ( +
    {products}
    + ) : ( +
    No products
    + ); +} + +// тест +test("When no products exist, show the appropriate message", () => { + // Arrange + nock("api").get(`/products`).reply(404); + + // Act + const { getByTestId } = render(); + + // Assert + expect(getByTestId("no-products-message")).toBeTruthy(); +}); +``` + +
    + +
    + +## ⚪ ️ 3.7 Используйте мало сквозных тестов, которые охватывают всю систему + +:white_check_mark: **Сделать:** Несмотря на то, что E2E (end-to-end) обычно означает тестирование только пользовательского интерфейса с помощью реального браузера (см. [пункт 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)), для других - это означает тесты, которые охватывают всю систему, включая реальный backend. Последний тип тестов является очень ценным, так как он покрывает ошибки интеграции между frontend и backend, которые могут возникнуть из-за неправильного понимания схемы обмена. Они также являются эффективным методом обнаружения проблем интеграции между бэкендами (например, микросервис A посылает неверное сообщение микросервису B) и даже выявления сбоев развертывания - не существует бэкенд-фреймворков для тестирования E2E, которые были бы настолько же дружественными и зрелыми, как UI-фреймворки, такие как [Cypress](https://www.cypress.io/) и [Puppeteer](https://github.com/GoogleChrome/puppeteer). Недостатком таких тестов является высокая стоимость настройки среды с таким количеством компонентов, а также их хрупкость - при наличии 50 микросервисов, даже если один из них откажет, то весь E2E просто не работает. По этой причине мы должны использовать данный подход очень экономно и, вероятно, иметь 1-10 таких тестов и не более. Тем не менее, даже небольшое количество тестов E2E, скорее всего, выявит те проблемы, на которые они нацелены - ошибки развертывания и интеграции. Рекомендуется запускать такие тесты на продакшене. + +
    + +❌ **Иначе:** Пользовательский интерфейс может хорошо справляться с тестированием своей функциональности, но поздно понять, что схема данных, с которой должен работать UI, отличается от ожидаемой. + +
    + +## ⚪ ️ 3.8 Ускорение тестов E2E за счет использования повторного использования данных для входа в систему + +:white_check_mark: **Сделать:** В тестах E2E, которые задействуют реальный backend и полагаются на валидный пользовательский токен для вызовов API, не нужно изолировать тест до уровня, на котором пользователь создается и регистрируется при каждом запросе. Вместо этого, авторизуйтесь только один раз перед началом выполнения теста (т.е. before-all hook), сохраните токен в каком-нибудь локальном хранилище и используйте его повторно во всех запросах. Может казаться, что это нарушает один из основных принципов тестирования - сохранять автономность теста без привязки к ресурсам. Хотя это обоснованное беспокойство, в тестах E2E производительность является ключевым фактором, и создание 1-3 запросов API перед запуском каждого отдельного теста может привести к большому времени выполнения. Повторное использование учетных данных не означает, что тесты должны работать с одними и теми же пользовательскими записями - если вы полагаетесь на пользовательские записи (например, история платежей пользователя), убедитесь, что эти записи создаются в рамках теста, и не делитесь их существованием с другими тестами. Также помните, что на backend может стоять заглушка - если ваши тесты сосредоточены на фронтенде, возможно, лучше изолировать его и заглушить API бэкенда (см. [пункт 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)). + +
    + +❌ **Иначе:** При наличии 200 тестов и, предполагая, что каждый login = 100 ms, что в сумме каждый раз дает 20 секунд только для входа в систему + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Вход в систему before-all, а не before-each + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +let authenticationToken; + +// происходит до выполнения ВСЕХ тестов +before(() => { + cy.request('POST', 'http://localhost:3000/login', { + username: Cypress.env('username'), + password: Cypress.env('password'), + }) + .its('body') + .then((responseFromLogin) => { + authenticationToken = responseFromLogin.token; + }) +}) + +// происходит перед КАЖДЫМ тестом +beforeEach(setUser => () { + cy.visit('/home', { + onBeforeLoad (win) { + win.localStorage.setItem('token', JSON.stringify(authenticationToken)) + }, + }) +}) + +``` + +
    + +
    + +## ⚪ ️ 3.9 Проведите один E2E smoke-тест, который просто бегает по карте сайта + +:white_check_mark: **Сделать:** Для того, чтобы протестировать код в продакшн-среде, запустите еще один тест E2E, который посещает большинство или все станицы сайта и гарантирует, что все работает исправно. Данный вид теста приносит большую пользу, так как его легко написать и поддерживать, в то время, как он может обнаружить любую проблему, включая сетевые сбои и проблемы при развертывании. Другие виды smoke-тестов не такие надёжные и информативные - иногда просто пингуют домашнюю страницу или запускают множество интеграционных тестов, которые не находят проблем. Очевидно, что данный вид тестов не может полностью заменить функциональные тесты. + +
    + +❌ **Иначе:** Все может казаться идеальным, все тесты пройдены, однако, у компонента Payment возникли проблемы с упаковкой или не маршрут может не рендериться + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Запуск smoke-теста по всей странице + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +it("When doing smoke testing over all page, should load them all successfully", () => { + // пример с использованием Cypress, но может быть реализован + // с другим набором E2E + cy.visit("https://mysite.com/home"); + cy.contains("Home"); + cy.visit("https://mysite.com/Login"); + cy.contains("Login"); + cy.visit("https://mysite.com/About"); + cy.contains("About"); +}); +``` + +
    + +
    + +## ⚪ ️ 3.10 Expose the tests as a live collaborative document + +:white_check_mark: **Сделать:** Помимо повышения надежности приложения, тесты также предоставляют возможность служить в качестве документации для кода. Поскольку тесты говорят на менее техническом языке, а скорее на языке UX, при использовании правильных инструментов они могут служить в качестве определенного коммуникатора, который выравнивает всех участников проекта - разработчиков и клиентов. Некоторые фреймворки, позволяют выразить план тестов на "человекоподобном" языке так, что любая заинтересованная сторона, включая менеджеров продукта, также может читать и понимать тесты, которые превратились в документацию. Этот подход также называют "acceptance test", поскольку она позволяет заказчику выразить свои требования к продукту на простом языке. Это [BDD (behavior-driven testing)] (https://en.wikipedia.org/wiki/Behavior-driven_development) в чистом виде. Одним из популярных фреймворков, позволяющих это сделать, является [Cucumber](https://github.com/cucumber/cucumber-js), см. пример ниже. Еще одна похожая, но другая возможность, [StoryBook](https://storybook.js.org/), позволяет представить компоненты пользовательского интерфейса в виде графического каталога, где можно пройтись по различным состояниям каждого компонента (например, отобразить сетку без фильтров, отобразить сетку с несколькими рядами или без них и т.д.), посмотреть, как это выглядит, и как вызвать это состояние - это может понравиться и product-менеджерам, но в основном это документация для разработчиков, которые используют эти компоненты. + +❌ **Иначе:** После огромного количества усилий, потраченного на разработку тестов, будет жаль не использовать его возможности + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Описание тестов на человекоподобном языке при помощи cucumber-js + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") + +```javascript +// так можно описывать тесты с помощью Cucumber: простым языком, который любой может понять + +Feature: Twitter new tweet + + I want to tweet something in Twitter + + @focus + Scenario: Tweeting from the home page + Given I open Twitter home + Given I click on "New tweet" button + Given I type "Hello followers!" in the textbox + Given I click on "Submit" button + Then I see message "Tweet saved" + +``` + +### :clap: Правильно: Визуализация компонентов и их состояний с помощью Storybook + +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 Поиск визуальных проблем с помощью автоматизированных инструментов + +:white_check_mark: **Сделать:** Настройте автоматизированные инструменты для создания скриншотов пользовательского интерфейса при представлении изменений и обнаружения визуальных проблем, таких как перекрытие или разрыв контента. Это гарантирует, что не только правильные данные будут подготовлены, но и пользователь сможет удобно их посмотреть. Эта техника не получила широкого распространения, так как мы привыкли к функциональным тестам, но именно визуальные тесты являются тем, что испытывает пользователь, а с таким количеством типов устройств очень легко упустить из виду какую-нибудь неприятную ошибку пользовательского интерфейса. Некоторые бесплатные инструменты могут обеспечить основы - генерировать и сохранять скриншоты для визуальной проверки. Хотя такой подход может быть оправданным для небольших приложений, он несовершенен, как и любое другое ручное тестирование, требующее человеческого труда при каждом изменении. С другой стороны, довольно сложно обнаружить проблемы пользовательского интерфейса автоматически из-за отсутствия четкого определения - именно здесь вступает в дело область "визуальной регрессии", которая решает эту головоломку, сравнивая старый пользовательский интерфейс с последними изменениями и обнаруживая различия. Некоторые бесплатные инструменты могут предоставить некоторые из этих функций (например, [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>), но могут потребовать значительного времени на настройку. Коммерческая линейка инструментов (например, [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) делает шаг вперед, сглаживая установку и предоставляет расширенные возможности, такие как управление пользовательским интерфейсом, оповещение, интеллектуальный захват путем устранения "визуального шума" (например, рекламы, анимации) и даже анализ первопричины изменений DOM/CSS, которые привели к проблеме. + +
    + +❌ **Иначе:** Является ли хорошей страница с контентом, которая все отображает, 100% тестов проходит, быстро загружается, но половина области контента скрыта? + +
    + +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: Типичная визуальная регрессия - контент, который плохо облуживается + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + +### :clap: Правильно: Настройка для получения и сравнения снимков UI + +![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") + +``` + # Добавьте столько доменов, сколько нужно. Ключ действует, как метка + +domains: + english: "http://www.mysite.com" + + # Введите ширину экрана ниже, вот несколько примеров + +screen_widths: + + - 600 + - 768 + - 1024 + - 1280 + + # Введите URL страницы, вот несколько примеров: + about: + path: /about + selector: '.about' + subscribe: + selector: '.subscribe' + path: /subscribe +``` + +### :clap: Правильно: Использование Applitools для получения результата сравнения снимков и других возможностей + +![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg "Using Applitools") ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +import * as todoPage from "../page-objects/todo-page"; + +describe("visual validation", () => { + before(() => todoPage.navigate()); + beforeEach(() => cy.eyesOpen({ appName: "TAU TodoMVC" })); + afterEach(() => cy.eyesClose()); + + it("should look good", () => { + cy.eyesCheckWindow("empty todo list"); + todoPage.addTodo("Clean room"); + todoPage.addTodo("Learn javascript"); + cy.eyesCheckWindow("two todos"); + todoPage.toggleTodo(0); + cy.eyesCheckWindow("mark as completed"); + }); +}); +``` + +
    + +

    + +# Раздел 4️⃣: Измерение эффективности тестов + +

    + +## ⚪ ️ 4.1 Получите достаточное покрытие тестами. Около 80% будет вполне достаточно + +:white_check_mark: **Сделать:** Цель тестирования - получить достаточно уверенность для того, чтобы двигаться дальше. Очевидно, что чем больше кода протестировано, тем больше уверенность, что можно идти вперед. Покрытие - это мера того, сколько строк кода охвачена тестами. Возникает вопрос: сколько достаточно? Очевидно, что 10-30% слишком мало, чтобы получить хоть какое-то представление о корректности сборки, с другой стороны, 100% - это очень дорого и может сместить фокус с критических путей на очень "экзотические уголки кода". Ответ заключается в том, что это зависит от многих факторов, таких как, например, тип приложения. Если вы создаете следующее поколения Airbus A380, то 100% должно быть обязательным условием, а если сайт с картинками - 50% может быть слишком много. Хотя большинство энтузиастов тестирования утверждает, что правильный порог покрытия зависит от контекста, большинство из них также упоминают число 80% ([Fowler: "в верхних 80-х или 90-х"] (https://martinfowler.com/bliki/TestCoverage.html)), которое предположительно должно удовлетворять большинству приложений. + +Советы: Можно настроить непрерывную интеграцию (CI) на порог покрытия ([ссылка на Jest](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) и останавливать сборку, которая не соответствует этому стандарту (также можно настроить порог для каждого компонента, см. пример кода ниже). +В дополнение к этому, рассмотрите возможность обнаружения снижения покрытия сборки (когда только что зафиксированный код имеет меньшее покрытие) - это подтолкнет разработчиков к увеличению или, по крайней мере, сохранению количества тестируемого кода. При всем этом, покрытие - это только одна из мер, основанная на количественных показателях, которой недостаточно для определения надежности вашего тестирования. Помимо всего прочего, его можно обмануть, как будет продемонстрировано в следующих пунктах. + +
    + +❌ **Иначе:** Уверенность и цифры идут рука об руку, не зная, что вы протестировали большую часть системы - будет присутствовать сомнение, которое будет вас тормозить. + +
    + +
    Примеры кода + +
    + +### :clap: Пример: Отчет о покрытии тестами + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: Правильно: Настройка покрытия каждого компонента, используя Jest + +![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") + +![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest)") + +
    + +

    + +## ⚪ ️ 4.2 Просмотрите отчеты о покрытии, чтобы найти непротестированные участи кода + +:white_check_mark: **Сделать:** Некоторые проблемы с кодом иногда остаются незамеченными. Их действительно трудно обнаружить даже с помощью традиционных инструментов. Они не всегда являются ошибками, а иногда это скорее неожиданное поведение приложения, которое может иметь серьезные последствия. Например, часто некоторые области кода редко или почти никогда не вызываются - вы думали, что класс 'PricingCalculator' всегда устанавливает цену продукта, но оказалось, что он фактически никогда не вызывается, хотя у нас 10000 продуктов в БД и много продаж... Отчеты о покрытии кода помогут вам понять, ведет ли приложение себя так, как вы считаете. Кроме того, они могут показать, какие типы кода не протестированы - если вам сообщают, что 80% кода протестировано, это еще не говорит о том, покрыты ли критические части. Для генерации отчетов нужно просто запустить приложения либо в продакшн, либо во время тестирования, а затем их просмотреть. Они показывают, как часто вызывается, каждая область кода и, если вы потратите время на их изучение, то сможете обнаружить некоторые проблемы с кодом. +
    + +❌ **Иначе:** Если вы не знаете, какая часть кода у вас не протестирована, то вы не сможете узнать, откуда могут возникнуть проблемы + +
    + +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: Что не так с этим отчетом? + +На основе реального сценария, когда мы отслеживали использование нашего приложения в QA и обнаружили интересные закономерности входа в систему (подсказка: количество отказов входа непропорционально, что-то явно не так. В итоге выяснилось, что какая-то ошибка во фронтенде постоянно бьет по API входа в бэкенд). + +![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report?") + +
    + +

    + +## ⚪ ️ 4.3 Измерение покрытия с помощью mutation-тестирования + +:white_check_mark: **Сделать:** Традиционная метрика Coverage часто лжет: Она может показать вам 100% покрытие кода, но ни одна из ваших функций, даже одна, не возвращает правильный ответ. Как так? Она просто измеряет, в каких строках кода побывал тест, но не проверяет, действительно ли тесты что-то тестировали. Как человек, который путешествует по делам и показывает свои штампы в паспорте - это не доказывает ничего кроме того, что он посетил несколько аэропортов и отелей. + +Тестирование на основе мутаций призвано помочь в этом, измеряя количество кода, который был действительно ПРОВЕРЕН, а не просто ПОСМОТРЕН. [Stryker](https://stryker-mutator.io/) - это JavaScript-библиотека для мутационного тестирования, и ее реализация действительно хороша: + +((1) он специально изменяет код и "закладывает ошибки". Например, код newOrder.price===0 становится newOrder.price!=0. Такие "ошибки" называются мутациями. + +(2) он запускает тесты, если все они успешны, то у нас проблема - тесты не выполнили свою задачу по обнаружению ошибок, мутации выжили. Если тесты провалились, то отлично, мутации были устранены. + +Знание того, что все или большинство мутаций были устранены, дает гораздо большую уверенность, чем традиционное покрытие, а время настройки почти не отличается +
    + +❌ **Иначе:** Вы будете введены в заблуждение, полагая, что 85% покрытие означает, что ваш тест обнаружит ошибки в 85% кода. + +
    + +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: 100% покрытие, 0% протестировано + +![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") + +```javascript +function addNewOrder(newOrder) { + logger.log(`Adding new order ${newOrder}`); + DB.save(newOrder); + Mailer.sendMail(newOrder.assignee, `A new order was places ${newOrder}`); + + return { approved: true }; +} + +it("Test addNewOrder, don't use such test names", () => { + addNewOrder({ assignee: "John@mailer.com", price: 120 }); +}); // Запускает 100% покрытие кода, но ничего не проверяет +``` + +
    + +### :clap: Правильно: Stryker reports, инструмент для тестирования мутаций, обнаруживает и подсчитывает количество кода, который не прошел проверку (Мутации) + +![alt text](assets/bp-20-yoni-goldberg-mutation-testing.jpeg "Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations)") + +
    + +

    + +## ⚪ ️4.4 Поиск проблем с тестами при помощи Test Linters + +:white_check_mark: **Сделать:** Набор плагинов ESLint был создан специально для проверки шаблонов тестового кода и обнаружения проблем. Например, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) предупредит, когда тест написан на глобальном уровне (не дочерний элемент describe()) или когда тесты [пропущены](https://mochajs.org/#inclusive-tests) что может привести к ложному убеждению, что все тесты пройдены. Аналогично, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) может, например, предупредить, когда тест вообще ничего не проверяет. + +
    + +❌ **Иначе:** При виде 90% покрытия кода и 100% зеленых тестов на вашем лице появится широкая улыбка только до тех пор, пока вы не поймете, что многие тесты ничего не проверяют, а многие тестовые наборы были просто пропущены. Надеюсь, вы ничего не развернули, основываясь на этом ошибочном наблюдении. + +
    +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: Пример теста, который полный ошибок и все они пойманы линтерами + +```javascript +describe("Too short description", () => { + const userToken = userService.getDefaultToken(); // ошибка:no-setup-in-describe, вместо этого используйте hooks (экономно) + it("Some description", () => {}); // ошибка: valid-test-description. Должно включать "Should" + не менее 5 слов +}); + +it.skip("Test name", () => { + // ошибка:no-skipped-tests, ошибка:no-global-tests. Поместите тесты только в describe или suite + expect("somevalue"); // ошибка:no-assert +}); + +it("Test name", () => { + // ошибка:no-identical-title. Присваивайте тестам уникальные названия +}); +``` + +
    + +

    + +# Раздел 5️⃣: CI and другие показатели качества + +

    + +## ⚪ ️ 5.1 Расширяйте линтеры и останавливайте сборки, в которых есть проблемы линтинга + +:white_check_mark: **Сделать:** Линтинг - это бесплатный обед, за 5 минут настройки вы получаете бесплатный автопилот, охраняющий ваш код и отлавливающий существенные проблемы по мере набора текста. Прошли те времена, когда линтинг сводился к косметике (никаких полутонов!). Сегодня линтеры могут отлавливать серьезные проблемы, такие как неправильно брошенные ошибки и потеря информации. Помимо основного набора правил (например, [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) или [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), включите несколько специализированных линтеров, например [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect), который может обнаружить тесты, которые ничего не проверяют, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) может обнаружить промисы без разрешения (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) который может обнаружить регулярные выражения, которые могут быть использованы для DOS атак, и [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore), способный предупредить, когда код использует методы библиотеки утилит, которые являются частью методов ядра V8, например Lodash.\_map(...). +
    + +❌ **Иначе:** Подумайте о том дне, когда ваш код упал, но ваш лог не отображает трассировку стека ошибок. Что произошло? Ваш код по ошибке выбросил объект, не являющийся ошибкой, и трассировка стека была потеряна - хорошая причина для того, чтобы биться головой о кирпичную стену. 5-минутная настройка линтера может обнаружить эту ошибку и спасти ваш день + +
    + +
    Примеры кода + +
    + +### :thumbsdown: Неправильно: Ошибочно выбрасывается неправильный объект Error, для этой ошибки не появляется стек-трейс. К счастью, ESLint ее отлавливает + +![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug") + +
    + +

    + +## ⚪ ️ 5.2 Сократите цикл обратной связи с локальным разработчиком-CI + +:white_check_mark: **Сделать:** Внедрение CI с блестящими проверками качества, такими как тестирование, линтинг, проверка на уязвимости и другое. Помогите разработчикам запустить этот конвейер также локально, чтобы получить мгновенную обратную связь и сократить [петлю обратной связи](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Почему? Эффективный процесс тестирования состоит из множества итеративных циклов: (1) пробное тестирование -> (2) обратная связь -> (3) рефакторинг. Чем быстрее обратная связь, тем больше итераций улучшения разработчик может провести в каждом модуле и улучшить результаты. С другой стороны, когда обратная связь приходит с запозданием, разработчик может перейти уже к другой теме или задаче и он будет не готов к доработке предыдущего. + +Некоторые разработчики CI (пример: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) позволяют запускать конвейер локально: коммерческие сервисы, такие как [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) в качестве прототипа для разработчиков. В качестве альтернативы можно просто добавить в package.json скрипт npm, запускающий все команды на проверку качества (например, test, lint, vulnerabilities), используйте инструменты вроде [concurrently](https://www.npmjs.com/package/concurrently) для распараллеливания и ненулевого кода выхода в случае сбоя одного из инструментов. Теперь разработчику достаточно вызвать одну команду - например, 'npm run quality' - чтобы получить мгновенную обратную связь. Также существует возможность прерывания коммита, если проверка качества не удалась, с помощью githook ([husky can help](https://github.com/typicode/husky)). +
    + +❌ **Иначе:** Когда результаты приходят на следующей день после кода, тестирование не становится неотъемлемой частью разработки + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Скрипты npm, которые выполняют проверку качества кода, запускаются параллельно по требованию или когда разработчик пытается запушить новый код. + +```javascript +"scripts": { + "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", + "inspect:lint": "eslint .", + "inspect:vulnerabilities": "npm audit", + "inspect:license": "license-checker --failOn GPLv2", + "inspect:complexity": "plato .", + + "inspect:all": "concurrently -c \"bgBlue.bold,bgMagenta.bold,yellow\" \"npm:inspect:quick-testing\" \"npm:inspect:lint\" \"npm:inspect:vulnerabilities\" \"npm:inspect:license\"" + }, + "husky": { + "hooks": { + "precommit": "npm run inspect:all", + "prepush": "npm run inspect:all" + } +} + +``` + +
    + +

    + +## ⚪ ️5.3 Выполните e2e тестирование в настоящей продакшн-среде + +:white_check_mark: **Сделать:** Конечное тестирование (e2e) является основной проблемой любого CI-конвейера - создание похожего продакшн-зеркала со всеми сопутствующими облачными сервисами может быть утомительным. Ваша цель - это поиск наилучшего компромисса: [Docker-compose](https://serverless.com/) позволяет создать изолированное окружение с идентичными контейнерами при помощи одного текстового файла, однако технология поддержки отличается от реальной продакшн-среды. Вы можете комбинировать его с ['AWS Local'](https://github.com/localstack/localstack) для работы с заглушкой реальных сервисов AWS. Если вы пошли по пути [serverless](https://serverless.com/), то несколько фреймворков типа serverless и [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) позволяют локально вызывать код FaaS. +
    + +❌ **Иначе:** Использование разных технологий для тестирования требует поддержки 2-х моделей развертывания, разделяя разработчиков и OC. + +
    + +
    Примеры кода + +
    + +### :clap: Пример: CI-конвейер, создающий кластер Kubernetes ([Благодарность: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) + +
    deploy:
    stage: deploy
    image: registry.gitlab.com/gitlab-examples/kubernetes-deploy
    script:
    - ./configureCluster.sh $KUBE_CA_PEM_FILE $KUBE_URL $KUBE_TOKEN
    - kubectl create ns $NAMESPACE
    - kubectl create secret -n $NAMESPACE docker-registry gitlab-registry --docker-server="$CI_REGISTRY" --docker-username="$CI_REGISTRY_USER" --docker-password="$CI_REGISTRY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL"
    - mkdir .generated
    - echo "$CI_BUILD_REF_NAME-$CI_BUILD_REF"
    - sed -e "s/TAG/$CI_BUILD_REF_NAME-$CI_BUILD_REF/g" templates/deals.yaml | tee ".generated/deals.yaml"
    - kubectl apply --namespace $NAMESPACE -f .generated/deals.yaml
    - kubectl apply --namespace $NAMESPACE -f templates/my-sock-shop.yaml
    environment:
    name: test-for-ci
    + +
    + +

    + +## ⚪ ️5.4 Параллельное выполнение тестов + +:white_check_mark: **Сделать:** Если все сделано правильно, тестирование - это ваш друг 24/7, обеспечивающий практически мгновенную обратную связь. На практике выполнение 500 юнит-тестов на одном процессоре в один поток может занять слишком много времени. К счастью, современные программы для запуска тестов и CI-платформы (такие как [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) и [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) могут распараллелить тест на несколько процессов и добиться значительного улучшения времени обратной связи. Некоторые поставщики CI также распараллеливают тесты по контейнерам (!), что еще больше сокращает цикл обратной связи. Будь то локально на нескольких процессах или через облачный CLI с использованием нескольких машин - распараллеливание требует сохранения автономности тестов, поскольку каждый из них может выполняться на разных процессах. + +❌ **Иначе:** Получение результатов тестирования через час после внедрения нового кода, когда вы уже пишете следующие функции - отличный рецепт для того, чтобы сделать тестирование менее актуальным + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: Mocha parallel & Jest легко обгоняют традиционную Mocha благодаря распараллеливанию тестирования ([Благодарность: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) + +![alt text](assets/bp-24-yonigoldberg-jest-parallel.png "Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization (Credit: JavaScript Test-Runners Benchmark)") + +
    + +

    + +## ⚪ ️5.5 Держитесь подальше от проблем, связанных с правовым полем, используя проверку на лицензию и антиплагиат + +:white_check_mark: **Сделать:** Вопросы лицензирования и плагиата, вероятно, не являются сейчас вашей главной заботой, но почему бы не поставить галочку и в этой графе за 10 минут? Куча пакетов npm, таких как [проверка лицензии](https://www.npmjs.com/package/license-checker) и [проверка на плагиат](https://www.npmjs.com/package/plagiarism-checker) (коммерческий и бесплатный план), могут быть легко встроены в ваш CI-конвейер и проверены на наличие таких проблем, как зависимости с ограничительными лицензиями или код, который был скопирован из Stack Overflow и, очевидно, нарушает авторские права. + +❌ **Иначе:** Непреднамеренно разработчики могут использовать пакеты с несоответствующими лицензиями или копировать коммерческий код и столкнуться с юридическими проблемами + +
    + +
    Примеры кода + +
    + +### :clap: Правильно: + +```javascript +// установите license-checker в CI или локально +npm install -g license-checker + +// попросите его просканировать все лицензии и выдать ошибку с кодом выхода, отличным от 0, в случае обнаружения неавторизованной лицензии. Система CI должна уловить этот сбой и остановить сборку +license-checker --summary --failOn BSD + +``` + +
    + +![alt text](assets/bp-25-nodejs-licsense.png) + +
    + +

    + +## ⚪ ️5.6 Постоянная проверка на наличие уязвимых зависимостей + +:white_check_mark: **Сделать:** Даже самые авторитетные зависимости, такие как Express, имеют известные уязвимости. Это можно легко устранить с помощью инструментов сообщества, таких как [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), или коммерческих инструментов, таких как [snyk](https://snyk.io/) (предлагается также бесплатная версия для сообщества). Оба инструмента могут быть вызваны из вашего CI при каждой сборке + +❌ **Иначе:** Для поддержания кода чистым от уязвимостей без специальных инструментов придется постоянно следить за публикациями в Интернете о новых угрозах. Довольно утомительно + +
    + +
    Примеры кода + +
    + +### :clap: Example: Результат NPM Audit + +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + +

    + +## ⚪ ️5.7 Автоматизируйте обновление зависимостей + +:white_check_mark: **Сделать:** Последнее внедрение в Yarn и npm package-lock.json создало серьезную проблему (дорога в ад вымощена благими намерениями) - по умолчанию пакеты больше не получают обновлений. Разработчик, выполняющий множество свежих развертываний с помощью 'npm install' и 'npm update', не получит никаких новых обновлений. Это приводит в лучшем случае к некачественным версиям зависимых пакетов, а в худшем - к уязвимому коду. Сейчас команды разработчиков полагаются на добрую волю и память разработчиков, чтобы вручную обновлять package.json или использовать инструменты [например, ncu](https://www.npmjs.com/package/npm-check-updates) вручную. +Более надежным способом может быть автоматизация процесса получения наиболее надежных версий зависимых пакетов, хотя пока не существует идеальных решений, есть два возможных пути автоматизации: + +(1) CI может отклонять сборки с устаревшими зависимостями - с помощью таких инструментов, как ['npm outdated'](https://docs.npmjs.com/cli/outdated) или 'npm-check-updates (ncu)'. Это заставит разработчиков обновить зависимости. + +(2) Использовать коммерческие инструменты, которые сканируют код и автоматически отправляют запросы на обновление зависимостей. Остается один интересный вопрос: какой должна быть политика обновления зависимостей - обновление при каждом патче создает слишком много накладных расходов, обновление сразу после выхода мажора может указывать на нестабильную версию (многие пакеты обнаруживают уязвимость в первые же дни после выхода, [см. инцидент с eslint-scope](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/)). + +Эффективная политика обновления может допускать некоторый "период" - пусть код отстает от @latest на некоторое время и версии, прежде чем считать локальную копию устаревшей (например, локальная версия - 1.3.1, а версия хранилища - 1.3.8). +
    + +❌ **Иначе:** Будут запускаться зависимости, которые были отмечены, как рискованные + +
    + +
    Примеры кода + +
    + +### :clap: Пример: [ncu](https://www.npmjs.com/package/npm-check-updates) можно использовать вручную или в рамках конвейера CI для определения степени отставания кода от последних версий + +![alt text](assets/bp-27-yoni-goldberg-npm.png "ncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions") + +
    + +

    + +## ⚪ ️ 5.8 Другие, не связанные с Node, советы по CI + +:white_check_mark: **Сделать:** Эта заметка посвящена советам по тестированию, которые связаны с Node JS или, по крайней мере, могут быть проиллюстрированы на его примере. Однако в этом пункте сгруппировано несколько советов, не связанных с Node, которые хорошо известны + +
    1. Используйте декларативный синтаксис. Это единственный вариант для большинства поставщиков, но старые версии Jenkins позволяют использовать код или UI
    2. Выбирайте продукт, который имеет встроенную поддержку Docker
    3. Запускайте сначала самые быстрые тесты. Создайте шаг/этап "Smoke testing", который группирует несколько быстрых проверок (например, линтинг, модульные тесты) и обеспечивает быструю обратную связь с коммиттером кода.
    4. Упростить просмотр сборки, включая отчеты о тестировании, отчеты о покрытии, отчеты о мутациях, журналы и т.д.
    5. Создайте несколько заданий для каждого события, повторно используя шаги между ними. Например, настройте одно задание для коммитов ветки функций и другое - для PR мастера. Пусть каждый из них повторно использует логику, используя общие шаги (большинство продуктов предоставляют некоторые механизмы для повторного использования кода).
    6. Никогда не вставляйте секреты в объявление задания, берите их из хранилища секретов или из конфигурации задания
    7. Явно повышайте версию в сборке релиза или, по крайней мере, убедитесь, что разработчик это сделал
    8. Сборка только один раз и выполнение всех проверок над единственным артефактом сборки (например, образом Docker)
    9. Тестируйте в эфемерной среде, которая не переносит состояние между сборками. Кэширование модулей node_modules может быть единственным исключением
    +
    + +❌ **Иначе:** Вы можете многое упустить + +

    + +## ⚪ ️ 5.9 Матрица сборки: Выполнение одних и тех же шагов CI с использованием нескольких версий Node + +:white_check_mark: **Сделать:** Проверка качества - это случайность, чем больше места вы охватываете тем больше шанс обнаружить проблемы на ранней стадии. При разработке многократно используемых пакетов или работе с несколькими клиентами с различными конфигурации и версиями Node, CI должен выполнят конвейер тестов для различных перестановок этих конфигураций. Например, если мы используем MySQL для одних клиентов и Postgres для других, некоторые поставщики СI поддёргивают функцию 'matrix', которая позволяет запустить конвейер тестов для всех вариантов MySQL, Postgres и нескольких версий Node (8, 9, 10). Это делается только с помощью конфигурации без каких-либо дополнительных усилий (при условии, что у вас есть тестирование или любые другие проверки качества). Другие CI, не поддерживающие Matrix, могут иметь расширения для этого. +
    + +❌ **Иначе:** Так неужели после всей этой тяжелой работы по написанию тестов мы позволим ошибкам прокрасться только из-за проблем с конфигурацией? + +
    + +
    Примеры кода + +
    + +### :clap: Пример: Использование Travis (содержит CI) для запуска одного и того же теста на нескольких версиях Node + +
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    +
    + +

    + +# Команда + +## Yoni Goldberg + +
    + +
    + +**Роль:** Автор + +**Информация:** Я независимый консультант, который работает с компаниями Fortune 500 и гаражными стартапами над совершенствованием их JS и Node.js приложений. Больше других тем я увлекаюсь и стремлюсь овладеть искусством тестирования. Я также являюсь автором книги [Node.js Best Practices] (https://github.com/goldbergyoni/nodebestpractices). + +**📗 Онлайн-курс:** Понравилось это руководство и вы хотите довести свои навыки тестирования до совершенства? Посетите мой комплексный курс [Testing Node.js & JavaScript From A To Z] (https://www.testjavascript.com). + +
    + +**Follow:** + +- [🐦 Twitter](https://twitter.com/goldbergyoni/) +- [📞 Contact](https://testjavascript.com/contact-2/) +- [✉️ Newsletter](https://testjavascript.com/newsletter//) + +
    +
    +
    + +## [Bruno Scheufler](https://github.com/BrunoScheufler) + +**Роль:** Консультант и технический рецензент + +Пересмотреть, улучшить, и отшлифовать все тексты. + +**Информация:** full-stack web-разработчик, Node.js & GraphQL любитель + +
    +
    + +## [Ido Richter](https://github.com/idori) + +**Роль:** Концепция, дизайн и отличные советы + +**Информация:** Мастерский frontend-разработчик, CSS-эксперт и любитель эмоджи + +## [Kyle Martin](https://github.com/js-kyle) + +**Роль:** Помогает поддерживать проект в рабочем состоянии и анализирует методы, связанные с безопасностью + +**About:** Любит работать с Node.js проектами и безопасностью веб-приложений. + +## Contributors ✨ + +Thanks goes to these wonderful people who have contributed to this repository! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Scott Davis

    🖋

    Adrien REDON

    🖋

    Stefano Magni

    🖋

    Yeoh Joer

    🖋

    Jhonny Moreira

    🖋

    Ian Germann

    🖋

    Hafez

    🖋

    Ruxandra Fediuc

    🖋

    Jack

    🖋

    Peter Carrero

    🖋

    Huhgawz

    🖋

    Haakon Borch

    🖋

    Jaime Mendoza

    🖋

    Cameron Dunford

    🖋

    John Gee

    🖋

    Aurelijus Rožėnas

    🖋

    Aaron

    🖋

    Tom Nagle

    🖋

    Yves yao

    🖋

    Userbit

    🖋

    Glaucia Lemos

    🚧

    koooge

    🖋

    Michal

    🖋

    roywalker

    🖋

    dangen

    🖋

    biesiadamich

    🖋

    Yanlin Jiang

    🖋

    sanguino

    🖋

    Morgan

    🖋

    Lukas Bischof

    ⚠️ 🖋

    JuanMa Ruiz

    🖋

    Luís Ângelo Rodrigues Jr.

    🖋

    José Fernández

    🖋

    Alejandro Gutierrez Barcenilla

    🖋

    Jason

    🖋

    Otavio Araujo

    ⚠️ 🖋

    Alex Ivanov

    🖋

    Yiqiao Xu

    🖋

    YuBin, Hsu

    🌍 💻
    + + + + + diff --git a/readme-ua.md b/readme-ua.md new file mode 100644 index 00000000..1f76d0e4 --- /dev/null +++ b/readme-ua.md @@ -0,0 +1,2135 @@ + + +# 👇 Чому цей посібник може вивести ваші навички тестування на новий рівень + +
    + +## 📗 50+ найкращих практик: надзвичайно комплексний і вичерпний + +Це посібник із надійності JavaScript і Node.js від А до Я. Він узагальнює та курує для вас десятки найкращих публікацій у блогах, книг та інструментів, які може запропонувати ринок + +## 🚢 Просунутий: виходить на 10 000 миль за межі основ + +Вирушайте в подорож, яка виходить далеко за межі основ і переходить до складних тем, як тестування у виробництві, мутаційне тестування, тестування на основі властивостей та багато інших стратегічних і професійних інструментів. Якщо ви прочитаєте кожне слово в цьому посібнику, ваші навички тестування, швидше за все, будуть набагато вище середнього + +## 🌐 Full-stack: front, backend, CI, та інше + +Почніть із розуміння практик тестування, які є основою для будь-якого рівня програми. Потім заглибтеся в обрану область: frontend + +
    + +### Написав Yoni Goldberg + +- JavaScript & Node.js консультант +- 📗 [Тестування Node.js і JavaScript від А до Я](https://www.testjavascript.com) - Мій комплексний онлайн-курс із більш ніж [7 годинами відео](https://www.testjavascript.com), 14 типів тестів і більше 40 кращих практик +- [Слідкуйте за мною в Twitter](https://twitter.com/goldbergyoni/) + +
    + +### Переклади - читайте своєю мовою + +- 🇨🇳[Chinese](readme-zh-CN.md) - Завдяки [Yves yao](https://github.com/yvesyao) +- 🇰🇷[Korean](readme.kr.md) - Завдяки [Rain Byun](https://github.com/ragubyun) +- 🇵🇱[Polish](readme-pl.md) - Завдяки [Michal Biesiada](https://github.com/mbiesiad) +- 🇪🇸[Spanish](readme-es.md) - Завдяки [Miguel G. Sanguino](https://github.com/sanguino) +- 🇧🇷[Portuguese-BR](readme-pt-br.md) - Завдяки [Iago Angelim Costa Cavalcante](https://github.com/iagocavalcante) , [Douglas Mariano Valero](https://github.com/DouglasMV) and [koooge](https://github.com/koooge) +- 🇫🇷[French](readme-fr.md) - Завдяки [Mathilde El Mouktafi](https://github.com/mel-mouk) +- 🇯🇵[Japanese (draft)](https://github.com/yuichkun/javascript-testing-best-practices/blob/master/readme-jp.md) - Завдяки [Yuichi Yogo](https://github.com/yuichkun) and [ryo](https://github.com/kawamataryo) +- 🇹🇼[Traditional Chinese](readme-zh-TW.md) - Завдяки [Yubin Hsu](https://github.com/yubinTW) +- 🇺🇦[Ukrainian](readme-ua.md) - Завдяки [Serhii Shramko](https://github.com/Shramkoweb) +- Хочете перекласти на свою рідну мову? будь ласка, відкрийте issue 💜 + +

    + +## `Зміст` + +#### [`Розділ 0: Золоте правило`](#section-0️⃣-the-golden-rule) + +Одна порада, яка надихає всіх інших (1 спеціальний пункт) + +#### [`Розділ 1: Анатомія тесту`](#section-1-the-test-anatomy-1) + +Основа - структурування чистих тестів (12 пункт) + +#### [`Розділ 2: Backend`](#section-2️⃣-backend-testing) + +Ефективне написання backend-тестів і тестів мікросервісів (13 пунктів) + +#### [`Розділ 3: Frontend`](#section-3️⃣-frontend-testing) + +Написання тестів для веб-інтерфейсу, включаючи тести компонентів і E2E (11 пунктів) + +#### [`Розділ 4: Вимірювання ефективності тестів`](#section-4️⃣-measuring-test-effectiveness) + +Спостереження за сторожем - вимірювання якості тесту (4 пункти) + +#### [`Розділ 5: Безперервна інтеграція`](#section-5️⃣-ci-and-other-quality-measures) + +Рекомендації щодо CI у світі JS (9 пунктів) + +

    + +# Розділ 0️⃣: Золоте правило + +
    + +## ⚪️ 0 Золоте правило: дизайн для ощадливого тестування + +:white_check_mark: **Роби:** +Тестовий код – це не робочий код. Розробіть його таким, щоб він був коротким, надзвичайно простим, зрозумілим і приємним для роботи. Треба подивитися на тест і миттєво зрозуміти намір. + +Бачиш, наші уми вже зайняті основною роботою – production-кодом. Немає «простору» для додаткової складності. Якщо ми спробуємо втиснути в наш бідолашний мозок ще одну систему [sus](https://en.wikipedia.org/wiki/System_usability_scale), це сповільнить роботу команди, яка працює проти того, чому ми проводимо тестування. Практично тут, багато команд просто відмовляються від тестування. + +Випробування — це можливість для чогось іншого — доброзичливого помічника, другого пілота, який дає велику цінність за невеликі інвестиції. Наука говорить нам, що у нас є дві системи мозку: система 1 використовується для легкої діяльності, як-от водіння автомобіля по порожній дорозі, і система 2, яка призначена для складних і усвідомлених операцій, таких як розвʼязування математичного рівняння. Розробіть свій тест для системи 1. Дивлячись на тестовий код, це повинно _відчуватися_ таким же легким, як зміна HTML-документа, а не розвʼязування 2X(17 × 24). + +Цього можна досягти шляхом вибіркового вибору методів, інструментів і тестових цілей, які є економічно ефективними та забезпечують [високу рентабельність інвестицій](https://en.wikipedia.org/wiki/Return_on_investment). Тестуйте лише стільки, скільки потрібно, намагайтеся підтримувати його спритність, інколи навіть варто відмовитися від деяких тестів і поміняти надійність на швидкість і простоту. + +![alt text](/assets/headspace.png "У нас немає місця для додаткової складності") + +Більшість наведених нижче порад є похідними від цього принципу. + +### Готові почати? + +

    + +# Розділ 1: Анатомія тесту + +
    + +## ⚪ ️ 1.1 Додайте 3 частини до кожної назви тесту + +:white_check_mark: **Роби:** У звіті про тестування має бути вказано, чи задовольняє поточна версія програми вимоги до людей, які не обов’язково знайомі з кодом: тестувальника, інженера DevOps, який розгортає, і майбутнього вас через два роки. Цього можна досягти найкраще, якщо тести складатимуться на рівні вимог і включатимуть 3 частини: + +(1) Що перевіряється? Наприклад, метод ProductsService.addNewProduct + +(2) За яких обставин і сценарію? Наприклад, у метод не передається ціна + +(3) Який результат очікується? Наприклад, новий продукт не затверджено + +
    + +❌ **Інакше:** Розгортання щойно не вдалось, тест із назвою «Add product» не вдався. Чи це говорить вам про те, що саме несправно? + +
    + +**👇 Примітка:** Кожен пункт має приклади коду, а інколи також зображення. Натисніть, щоб розгорнути +
    + +
    Приклади коду + +
    + +### :clap: Роби це правильно. Приклад: ім’я тесту, яке складається з 3 частин + +![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Використання Mocha для ілюстрації ідеї") + +```javascript +//1. блок тестування +describe('Products Service', function() { + describe('Add new product', function() { + //2. сценарій і 3. очікування + it('When no price is specified, then the product status is pending approval', ()=> { + const newProduct = new ProductService().add(...); + expect(newProduct.status).to.equal('pendingApproval'); + }); + }); +}); + +``` + +
    + +### :clap: Роби це правильно Приклад: назва тесту, що складається з 3 частин + +![alt text](/assets/bp-1-3-parts.jpeg "Назва тесту, яка складається з 3 частин") + +
    + +
    +
    © Credits & read-more + 1. Roy Osherove - Naming standards for unit tests +
    + +

    + +## ⚪ ️ 1.2 Структурні тести за схемою ААА + +:white_check_mark: **Роби:** Структуруйте свої тести за допомогою 3 добре відокремлених розділів «Упорядкуйте, дійте та затверджуйте» (AAA). Дотримання цієї структури гарантує, що читач не витрачатиме мозок-CPU на розуміння плану тестування: + +1st A - Arrange(Упорядкувати): Весь код налаштування, щоб привести систему до сценарію, який має імітувати тест. Це може включати створення екземпляра тестового конструктора блоку, додавання записів БД, mocking/stubbing обʼєктів та будь-який інший код підготовки + +2nd A - Act(Дія): Виконайте тестовий блок. Зазвичай 1 рядок коду + +3rd A - Assert(Стверджування): Переконайтеся, що отримане значення відповідає очікуванням. Зазвичай 1 рядок коду + +
    + +❌ **Інакше:** Ви не лише витрачаєте години на розуміння основного коду, але те, що мало бути найпростішою частиною дня (тестування), напружує ваш мозок + +
    + +
    Приклади коду + +
    + +### :clap: Роби це правильно. Приклад: тест, структурований за шаблоном AAA + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Приклади з Mocha") + +```javascript +describe("Customer classifier", () => { + test("When customer spent more than 500$, should be classified as premium", () => { + //Arrange + const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; + const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); + + //Act + const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); + + //Assert + expect(receivedClassification).toMatch("premium"); + }); +}); +``` + +
    + +### :thumbsdown: Приклад антишаблону: немає поділу, одна група, важче інтерпретувати + +```javascript +test("Should be classified as premium", () => { + const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; + const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); + const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); + expect(receivedClassification).toMatch("premium"); +}); +``` + +
    + +

    + +## ⚪ ️1.3 Опишіть очікування мовою продукту: використовуйте твердження в стилі BDD + +:white_check_mark: **Роби:** Написання ваших тестів у декларативному стилі дає змогу читачеві миттєво отримати переваги, не витрачаючи на це зусилля мозку. Коли ви пишете імперативний код, наповнений умовною логікою, читач змушений докладати більше зусиль мозку. У такому випадку закодуйте очікування мовою, схожою на людську, у декларативному стилі BDD, використовуючи `expect` або `should` і не використовуючи спеціальний код. Якщо Chai & Jest не містить потрібного твердження, і воно дуже повторюване, подумайте над [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) або створіть [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +
    + +❌ **Інакше:** Команда буде писати менше тестів і прикрашати набридливі .skip() + +
    + +
    Приклади коду
    + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Приклади з Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") + +### :thumbsdown: Приклад антишаблону: читач повинен проглянути довгий код і імперативний код, щоб зрозуміти суть + +```javascript +test("When asking for an admin, ensure only ordered admins in results", () => { + // припускаючи, що ми додали сюди двох адміністраторів «admin1», «admin2» і юзера «user1» + const allAdmins = getUsers({ adminOnly: true }); + + let admin1Found, + adming2Found = false; + + allAdmins.forEach(aSingleUser => { + if (aSingleUser === "user1") { + assert.notEqual(aSingleUser, "user1", "A user was found and not admin"); + } + if (aSingleUser === "admin1") { + admin1Found = true; + } + if (aSingleUser === "admin2") { + admin2Found = true; + } + }); + + if (!admin1Found || !admin2Found) { + throw new Error("Not all admins were returned"); + } +}); +``` + +
    + +### :clap: Роби це правильно. Приклад. Перегляд наступного декларативного тесту – легкий вітер + +```javascript +it("When asking for an admin, ensure only ordered admins in results", () => { + // припускаючи, що ми додали сюди двох адміністраторів «admin1», «admin2» і юзера «user1» + const allAdmins = getUsers({ adminOnly: true }); + + expect(allAdmins) + .to.include.ordered.members(["admin1", "admin2"]) + .but.not.include.ordered.members(["user1"]); +}); +``` + +
    + +

    + +## ⚪ ️ 1.4 Дотримуйтесь тестування за допомогою чорної скриньки: перевіряйте лише публічні методи + +:white_check_mark: **Роби:** Тестування внутрішніх компонентів приносить величезні накладні витрати майже за безцінь. Якщо ваш код/API дає належні результати, чи варто вам справді витратити наступні 3 години на те, щоб перевірити, ЯК він працює всередині, а потім підтримувати ці крихкі тести? Кожного разу, коли перевіряється загальнодоступна поведінка, приватна реалізація також неявно перевіряється, і ваші тести не працюватимуть, лише якщо є певна проблема (наприклад, неправильний вихід). Цей підхід також називають «поведінковим тестуванням». З іншого боку, якщо ви тестуєте внутрішні елементи (підхід білого ящика) — ваш фокус переходить від планування результату компонента до дрібних деталей, і ваш тест може бути невдалим через незначні переписування коду, хоча результати хороші — це значно збільшує технічне обслуговування +
    + +❌ **Інакше:** Ваші тести поводяться як [хлопчик, який кричав вовк](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): вигуки хибнопозитивних криків (наприклад, Тест провалився через те, що ім’я приватної змінної було змінено). Не дивно, що незабаром люди почнуть ігнорувати сповіщення CI, поки одного дня не проігнорують справжню помилку... + +
    +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: тестовий приклад тестує внутрішні компоненти без вагомої причини + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Приклади з Mocha & Chai") + +```javascript +class ProductService { + // цей метод використовується тільки внутрішньо + // зміна цієї назви призведе до невдачі тестів + calculateVATAdd(priceWithoutVAT) { + return { finalPrice: priceWithoutVAT * 1.2 }; + // Зміна формату результату або назви ключа вище призведе до невдачі тестів + } + // публічний метод + getPrice(productId) { + const desiredProduct = DB.getProduct(productId); + finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice; + return finalPrice; + } +} + +it("White-box test: When the internal methods get 0 vat, it return 0 response", async () => { + // Немає вимоги дозволяти користувачам розраховувати ПДВ, відображати лише остаточну ціну. Тим не менш, ми хибно наполягаємо тут на перевірці внутрішніх елементів класу + expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0); +}); +``` + +
    + +

    + +## ⚪ ️ ️1.5 Вибирайте правильні тестові дублі: уникайте mocks на користь stubs і spies + +:white_check_mark: **Роби:** Подвійні тести є необхідним злом, тому що вони пов'язані з внутрішніми елементами програми, але деякі з них мають величезну цінність ([Прочитайте тут нагадування: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). + +Перш ніж використовувати подвійні тести, поставте дуже просте запитання: чи використовую я його для тестування функціональності, яка відображається або може з’явитися в документі з вимогами? Якщо ні, це запах тестування білої коробки. + +Наприклад, якщо ви хочете перевірити, чи ваша програма поводиться належним чином, коли платіжна служба не працює, ви можете заблокувати платіжну службу та запустити деякий повернення «Немає відповіді», щоб переконатися, що одиниця, що тестується, повертає правильне значення. Це перевіряє поведінку/відповідь/результат нашої програми за певних сценаріїв. Ви також можете використовувати шпигуна, щоб підтвердити, що електронний лист було надіслано, коли ця служба не працює — це знову перевірка поведінки, яка, ймовірно, з’явиться в документі вимог («Надіслати електронний лист, якщо платіж не вдалося зберегти»). З іншого боку, якщо ви знущаєтеся над платіжною службою та переконаєтесь, що її викликали з правильними типами JavaScript — тоді ваш тест зосереджений на внутрішніх речах, які не мають нічого спільного з функціями програми та, ймовірно, часто змінюватимуться. +
    + +❌ **Інакше:** Будь-яке переписування коду вимагає пошуку всіх макетів у коді та відповідного оновлення. Тести стають тягарем, а не корисним другом + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: Mocks імітує внутрішню реалізацію + +![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg "Приклади з Sinon") + +```javascript +it("When a valid product is about to be deleted, ensure data access DAL was called once, with the right product and right config", async () => { + // Припустимо, ми вже додали продукт + const dataAccessMock = sinon.mock(DAL); + // ПОГАНО: тестування внутрощів насправді є нашою головною метою, а не лише побічним ефектом + dataAccessMock + .expects("deleteProduct") + .once() + .withArgs(DBConfig, theProductWeJustAdded, true, false); + new ProductService().deletePrice(theProductWeJustAdded); + dataAccessMock.verify(); +}); +``` + +
    + +### :clap:Роби це правильно. Приклад: (spy) шпигуни зосереджені на перевірці вимог, але як побічний ефект неминуче торкаються внутрішніх органів + +```javascript +it("When a valid product is about to be deleted, ensure an email is sent", async () => { + // Припустимо, ми вже додали продукт + const spy = sinon.spy(Emailer.prototype, "sendEmail"); + new ProductService().deletePrice(theProductWeJustAdded); + // добре: ми маємо справу з внутрішніми кодом? Так, але як побічний ефект тестування вимог (надсилання електронного листа) + expect(spy.calledOnce).to.be.true; +}); +``` + +
    + +

    + +## 📗 Хочете навчитися всім цим практикам із живим відео? + +### Відвідайте мій онлайн-курс [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +

    + +## ⚪ ️1.6 Використовуйте реалістичні вхідні дані + +:white_check_mark: **Роби:** Часто production помилки виявляються під час дуже специфічних і несподіваних вхідних даних — ««чим реалістичнішими є тестові вхідні дані, тим більші шанси виявити помилки на ранній стадії. Використовуйте спеціальні бібліотеки, як-от [Chance](https://github.com/chancejs/chancejs) або [Faker](https://www.npmjs.com/package/faker), щоб генерувати псевдореальні дані. Наприклад, такі бібліотеки можуть створювати реалістичні номери телефонів, імена користувачів, кредитні картки, назви компаній і навіть текст «lorem ipsum». Ви також можете створити деякі тести (на додаток до модульних тестів, а не як заміну), які рандомізують дані фальсифікаторів, щоб розширити тестований блок або навіть імпортувати реальні дані з вашого робочого середовища. Хочете перейти на новий рівень? Дивіться наступний пункт (тестування на основі властивостей). +
    + +❌ **Інакше:** Усе ваше тестування розробки хибно показуватиме зелений колір, якщо ви використовуєте синтетичні вхідні дані, як-от «Foo», але тоді production може стати червоним, коли хакер передасть неприємний рядок, як-от «@3e2ddsf». ##’ 1 fdsfds . fds432 AAAA” + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: набір тестів, який проходить через нереалістичні дані + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") + +```javascript +const addProduct = (name, price) => { + const productNameRegexNoSpace = /^\S*$/; // white-space не допускаються + + if (!productNameRegexNoSpace.test(name)) return false; // цей шлях не досягнуто через рінній вихід + + // Логіка тут + return true; +}; + +test("Wrong: When adding new product with valid properties, get successful confirmation", async () => { + // Рядок "Foo", який використовується в усіх тестах, ніколи не викликає хибний результат + const addProductResult = addProduct("Foo", 5); + expect(addProductResult).toBe(true); + // Позитивний-хибний: операція вдалася, тому що ми ніколи не намагалися використати + // назву продукту з пробілами +}); +``` + +
    + +### :clap:Роби це правильно. Приклад: рандомізація реалістичного введення + +```javascript +it("Better: When adding new valid product, get successful confirmation", async () => { + const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); + // Згенеровані випадкові данні: {'Sleek Cotton Computer', 85481} + expect(addProductResult).to.be.true; + // Тест провалився, випадковий вхід ініціював певний шлях, який ми не планували. + // Ми рано виявили помилку! +}); +``` + +
    + +

    + +## ⚪ ️ 1.7 Перевірте багато вхідних комбінацій за допомогою тестування на основі властивостей + +:white_check_mark: **Роби:** Зазвичай ми обираємо кілька вхідних зразків для кожного тесту. Навіть якщо формат введення нагадує реальні дані (дивись [‘Don’t foo’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), ми розглядаємо лише кілька комбінацій вводу (method(‘’, true, 1), method(“string” , false , 0)), Однак production API, який викликається з 5 параметрами, може бути викликаний із тисячами різних перестановок, одна з яких може призвести до зупинки нашого процесу ([дивись Fuzz тестування](https://en.wikipedia.org/wiki/Fuzzing)). Що, якби ви могли написати один тест, який автоматично надсилає 1000 перестановок різних вхідних даних і виявляє, для якого вхідного сигналу наш код не повертає правильну відповідь? Тестування на основі властивостей — це техніка, яка робить саме це: надсилаючи всі можливі комбінації вхідних даних до вашого тестованого пристрою, це збільшує ймовірність виявлення помилки. Наприклад, задано метод — addNewProduct(id, name, isDiscount) — бібліотеки, що підтримують, викличуть цей метод із багатьма комбінаціями (число, рядок, логічний вираз), наприклад (1, «iPhone», false), (2, «Galaxy» », правда). Ви можете запустити тестування на основі властивостей за допомогою улюбленого засобу виконання тестів (Mocha, Jest тощо), використовуючи такі бібліотеки, як [js-verify](https://github.com/jsverify/jsverify) чи [testcheck](https://github.com/leebyron/testcheck-js) (значно краща документація). Оновлення: Nicolas Dubien пропонує в коментарях нижче [checkout fast-check](https://github.com/dubzzz/fast-check#readme) який, здається, пропонує деякі додаткові функції та також активно підтримується +
    + +❌ **Інакше:** Несвідомо ви обираєте тестові вхідні дані, які охоплюють лише шляхи коду, які добре працюють. На жаль, це знижує ефективність тестування як засобу виявлення помилок + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Тестування багатьох вхідних перестановок за допомогою “fast-check” + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") + +```javascript +import fc from "fast-check"; + +describe("Product service", () => { + describe("Adding new", () => { + // буде виконано 100 разів з різними випадковими властивостями + it("Add new product with random yet valid properties, always successful", () => + fc.assert( + fc.property(fc.integer(), fc.string(), (id, name) => { + expect(addNewProduct(id, name).status).toEqual("approved"); + }) + )); + }); +}); +``` + +
    + +

    + +## ⚪ ️ 1.8 За потреби використовуйте лише короткі та вбудовані знімки + +:white_check_mark: **Роби:** Коли є потреба в [снепшот тестуванні](https://jestjs.io/docs/en/snapshot-testing), використовуйте лише короткі та цілеспрямовані снепшоти (3-7 рядків) які входять до складу тесту ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) а не в зовнішніх файлах. Дотримуючись цієї вказівки, ваші тести залишатимуться зрозумілими та менш крихкими. + +З іншого боку, навчальні посібники та інструменти «класичних знімків» заохочують зберігати великі файли (наприклад, розмітку візуалізації компонентів, результат JSON API) на зовнішньому носії та забезпечувати порівняння отриманого результату зі збереженою версією під час кожного тестового запуску. Це, наприклад, може неявно поєднати наш тест із 1000 рядками з 3000 значеннями даних, які автор тесту ніколи не читав і не міркував. Чому це неправильно? Таким чином, є 1000 причин невдачі вашого тесту - достатньо змінити один рядок, щоб знімок став недійсним, і це, ймовірно, станеться часто. Як часто? для кожного пробілу, коментаря або незначної зміни CSS/HTML. Крім того, назва тесту не дасть підказки про помилку, оскільки вона лише перевіряє, чи не змінилися 1000 рядків, а також заохочує автора тесту прийняти за бажаний істинний довгий документ, який він не міг перевірити та перевірити. Усе це симптоми незрозумілого та жадібного випробування, яке не є зосередженим і спрямоване на досягнення занадто багато + +Варто зазначити, що є кілька випадків, коли довгі та зовнішні снепшоти є прийнятними - коли стверджується на схемі, а не на даних (вилучення значень і фокусування на полях) або коли отриманий документ рідко змінюється
    + +❌ **Інакше:** UI тести впали. Код виглядає правильним, екран відображає ідеально все, що сталося? Ваше тестування снепшотів щойно виявило різницю між вихідним документом і поточним отриманим документом – до розмітки додано один пробіл... + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: Поєднання нашого тесту з невидимими 2000 рядками коду + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") + +```javascript +it("TestJavaScript.com is renderd correctly", () => { + //Arrange + + //Act + const receivedPage = renderer + .create( Test JavaScript ) + .toJSON(); + + //Assert + expect(receivedPage).toMatchSnapshot(); + //Тепер ми неявно підтримуємо документ довжиною 2000 рядків + //кожен додатковий розрив рядка або коментар - порушить цей тест +}); +``` + +
    + +### :clap: Приклад правильного виконання: Очікування помітні та цілеспрямовані + +```javascript +it("When visiting TestJavaScript.com home page, a menu is displayed", () => { + //Arrange + + //Act + const receivedPage = renderer + .create( Test JavaScript ) + .toJSON(); + + //Assert + + const menu = receivedPage.content.menu; + expect(menu).toMatchInlineSnapshot(` +
      +
    • Home
    • +
    • About
    • +
    • Contact
    • +
    +`); +}); +``` + +
    + +

    + +## ⚪ ️Скопіюйте код, але тільки необхідний + +:white_check_mark: **Роби:** Включіть усі необхідні деталі, які впливають на результат тесту, але не більше того. Як приклад, розглянемо тест, який має розраховувати 100 рядків вхідного JSON - Вставляти це в кожен тест утомливо. Якщо витягти його за межі transferFactory.getJSON(), тест залишиться невизначеним - Без даних важко співвіднести результат тесту з причиною («чому він має повертати статус 400?»). Класичні книжкові шаблони x-unit назвали цей шаблон «таємничим гостем» - Щось невидиме вплинуло на наші результати тестування, ми не знаємо, що саме. Ми можемо досягти кращих результатів, витягнувши повторювані довгі частини назовні І чітко зазначивши, які конкретні деталі мають значення для тесту. Переходячи до наведеного вище прикладу, тест може передавати параметри, які підкреслюють важливість: transferFactory.getJSON({sender: undefined}). У цьому прикладі читач повинен негайно зробити висновок, що порожнє поле відправника є причиною, чому тест повинен очікувати помилку перевірки або будь-який інший подібний адекватний результат. +
    + +❌ **Інакше:** Копіювання 500 рядків JSON зробить ваші тести непридатними для обслуговування та читання. Перенесення всього назовні закінчиться нечіткими тестами, які важко зрозуміти + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: Помилка тесту незрозуміла, оскільки вся причина зовнішня і ховається у величезному JSON + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Приклади з Mocha") + +```javascript +test("When no credit, then the transfer is declined", async() => { + // Arrange + const transferRequest = testHelpers.factorMoneyTransfer() // повертає 200 рядків JSON; + const transferServiceUnderTest = new TransferService(); + + // Act + const transferResponse = await transferServiceUnderTest.transfer(transferRequest); + + // Assert + expect(transferResponse.status).toBe(409);// Але чому ми очікуємо невдачі: у тесті все здається абсолютно дійсним 🤔 + }); +``` + +
    + +### :clap: Приклад правильного виконання: Тест підкреслює, що є причиною результату тесту + +```javascript + +test("When no credit, then the transfer is declined ", async() => { + // Arrange + const transferRequest = testHelpers.factorMoneyTransfer({userCredit:100, transferAmount:200}) // очевидно, брак кредитів + const transferServiceUnderTest = new TransferService({disallowOvercharge:true}); + + // Act + const transferResponse = await transferServiceUnderTest.transfer(transferRequest); + + // Assert + expect(transferResponse.status).toBe(409); // Очевидно, що якщо у користувача немає кредиту, тест впаде + }); + ``` + +
    + +

    + +## ⚪ ️ 1.10 Не ловіть помилки, очікуйте їх + +:white_check_mark: **Роби:** Під час спроби стверджувати, що якийсь вхід викликає помилку, може виглядати правильним використання try-catch-finally і підтверджує, що було введено пропозицію catch. Результатом є незручний і багатослівний тестовий приклад (приклад нижче), який приховує простий намір тесту та очікування результату + +Більш елегантною альтернативою є використання однорядкового виділеного твердження Chai: expect(method).to.throw (або Jest: expect(method).toThrow()). Абсолютно обовʼязково також переконатися, що виняток містить властивість, яка повідомляє тип помилки, інакше, враховуючи лише загальну помилку і покаже користувачеві невтішне повідомлення +
    + +❌ **Інакше:** Буде складно зробити висновок зі звітів про випробування (наприклад, звітів CI), що пішло не так + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: Довгий тестовий приклад, який намагається підтвердити існування помилки за допомогою try-catch + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Приклади з Mocha") + +```javascript +it("When no product name, it throws error 400", async () => { + let errorWeExceptFor = null; + try { + const result = await addNewProduct({}); + } catch (error) { + expect(error.code).to.equal("InvalidInput"); + errorWeExceptFor = error; + } + expect(errorWeExceptFor).not.to.be.null; + // якщо це твердження не виконується, відображатимуться лише результати тестів/звіти + // якщо якесь значення дорівнює нулю, не буде жодного слова про відсутній виняток +}); +``` + +
    + +### :clap: Приклад правильного виконання: Очікування, зрозумілі людині, які можуть бути легко зрозумілі, можливо, навіть спеціалістам QA або PM + +```javascript +it("When no product name, it throws error 400", async () => { + await expect(addNewProduct({})) + .to.eventually.throw(AppError) + .with.property("code", "InvalidInput"); +}); +``` + +
    + +

    + +## ⚪ ️ 1.11 Позначте свої тести + +:white_check_mark: **Роби:** Різні тести повинні виконуватися за різними сценаріями: quick smoke, без IO, тести мають запускатися, коли розробник зберігає або фіксує файл, повні наскрізні тести (e2e) зазвичай запускаються, коли надсилається новий запит на отримання тощо. Цього можна досягти позначаючи тести ключовими словами, як-от #cold #api #sanity, щоб ви могли працювати зі своїм пакетом тестування та викликати потрібну підмножину. Наприклад, ось як ви можете викликати лише групу перевірки розумності за допомогою Mocha: mocha — grep ‘sanity’ +
    + +❌ **Інакше:** Виконання всіх тестів, у тому числі тестів, які виконують десятки запитів до БД, щоразу, коли розробник вносить невеликі зміни, може бути надзвичайно повільним і утримувати розробників від виконання тестівВиконання всіх тестів, у тому числі тестів, які виконують десятки запитів до БД, щоразу, коли розробник вносить невеликі зміни, може бути надзвичайно повільним і утримувати розробників від виконання тестів + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Позначення тестів як «#cold-test» дозволяє тестувальнику виконувати лише швидкі тести (Cold===швидкі тести, які не виконують введення-виведення (IO) та можуть виконуватися часто, навіть коли розробник вводить текст) + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") + +```javascript +// цей тест швидкий (без БД), і ми позначаємо його відповідним чином +// тепер користувач/CI може запускати його часто +describe("Order service", function() { + describe("Add new order #cold-test #sanity", function() { + test("Scenario - no currency was supplied. Expectation - Use the default currency #sanity", function() { + // Логіка тут + }); + }); +}); +``` + +
    + +

    + +## ⚪ ️ 1.12 Класифікуйте тести принаймні за 2 рівнями + +:white_check_mark: **Роби:** Застосуйте певну структуру до свого набору тестів, щоб випадковий відвідувач міг легко зрозуміти вимоги (тести — найкраща документація) і різні сценарії, які тестуються. Звичайним методом для цього є розміщення принаймні 2 блоків «describe» над вашими тестами: 1-й призначений для назви тестованого блоку, а 2-й для додаткового рівня категоризації, як-от сценарій або спеціальні категорії (дивись Приклади коду). Це також значно покращить звіти про тести: читач легко визначить категорії тестів, заглибиться в потрібний розділ і порівнює невдалі тести. Крім того, розробнику стане набагато легше орієнтуватися в коді набору з багатьма тестами. Існує кілька альтернативних структур для набору тестів, які ви можете розглянути, наприклад [given-when-then](https://github.com/searls/jasmine-given) і [RITE](https://github.com/ericelliott/riteway) + +
    + +❌ **Інакше:** Дивлячись на звіт із плоским і довгим списком тестів, читач повинен побіжно прочитати довгі тексти, щоб зробити висновок про основні сценарії та співвіднести загальність невдалих тестів. Розглянемо наступний випадок: коли 7/100 тестів не вдаються, перегляд плоского списку потребує прочитання тексту невдалих тестів, щоб побачити, як вони пов’язані один з одним. Однак в ієрархічному звіті всі вони можуть входити до одного потоку або категорії, і читач швидко зрозуміє, що або принаймні де є основна причина збою + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Структурування набору з назвою блоку, що тестується, і сценаріями призведе до зручного звіту, який показано нижче + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") + +```javascript +// Тестуєма одиниця +describe("Transfer service", () => { + // Сценарій + describe("When no credit", () => { + // Очікування + test("Then the response status should decline", () => {}); + + // Очікування + test("Then it should send email to admin", () => {}); + }); +}); +``` + +![alt text](assets/hierarchical-report.png) + +
    + +### :thumbsdown: Приклад антишаблону: Плоский список тестів ускладнить читачеві ідентифікацію історій користувачів і співвіднесення невдалих тестів + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Mocha") + +```javascript +test("Then the response status should decline", () => {}); + +test("Then it should send email", () => {}); + +test("Then there should not be a new transfer record", () => {}); +``` + +![alt text](assets/flat-report.png) + +
    + +
    + +

    + +## ⚪ ️1.13 Інша загальна гігієна тестування + +:white_check_mark: **Роби:** Ця публікація зосереджена на порадах щодо тестування, які пов’язані з Node JS або, принаймні, можуть бути представлені на прикладі Node JS. Однак у цьому розділі згруповано кілька добре відомих порад, не повʼязаних з Node + +Навчайтеся і практикуйтеся [TDD принципи](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — вони надзвичайно цінні для багатьох, але не лякайтеся, якщо вони не відповідають вашому стилю, ви не єдині. Розгляньте можливість написання тестів перед кодом у [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), переконайтеся, що кожен тест перевіряє саме одну річ, якщо ви знайдете помилку — перед виправленням напишіть тест, який виявить цю помилку в майбутньому, дайте кожному тесту принаймні один раз помилитися, перш ніж стати зеленим, запустіть модуль, написавши швидкий і спрощений код, який задовольняє тест - потім поступово рефакторинг і переведіть його до рівня виробництва, уникайте будь-якої залежності від середовища (шляхи, ОС тощо) +
    + +❌ **Інакше:** Ви пропустите перлини мудрості, які збиралися десятиліттями + +

    + +# Section 2️⃣: Backend тестування + +## ⚪ ️2.1 Збагачуйте своє портфоліо тестування: подивіться за межі модульних тестів і піраміди + +:white_check_mark: **Роби:** [Піраміда тестування](https://martinfowler.com/bliki/TestPyramid.html), Хоча їй понад 10 років, це чудова та релевантна модель, яка пропонує три типи тестування та впливає на стратегію тестування більшості розробників. Водночас з’явилося більше кількох блискучих нових методів тестування, які ховаються в тіні піраміди тестування. Враховуючи всі драматичні зміни, які ми спостерігаємо за останні 10 років (мікросервіси, хмарні сервіси, безсерверний доступ), чи можливо, що одна досить стара модель підійде для *всіх* типів програм? Чи не варто світу тестування розглянути можливість вітати нові методи тестування? + +Не зрозумійте мене неправильно, у 2019 році піраміда тестування, TDD і модульні тести все ще залишаються потужною технікою та, мабуть, найкраще підходять для багатьох програм. Тільки, як і будь-яка інша модель, попри її корисність, [інколи вона може помилятися](https://en.wikipedia.org/wiki/All_models_are_wrong). Наприклад, розглянемо IoT-додаток, який приймає багато подій у шину повідомлень, як-от Kafka/RabbitMQ, які потім надходять у якесь сховище даних і в кінцевому підсумку запитуються деяким інтерфейсом користувача аналітики. Чи справді ми повинні витрачати 50% нашого бюджету на тестування на написання модульних тестів для програми, яка орієнтована на інтеграцію та майже не містить логіки? Зі збільшенням різноманітності типів додатків (ботів, крипто, навичок Alexa) зростає ймовірність знайти сценарії, де піраміда тестування не найкраще підходить. + +Настав час збагатити ваше портфоліо тестування та ознайомитися з більшою кількістю типів тестування (наступні пункти пропонують кілька ідей), моделями розуму, як-от піраміда тестування, а також зіставити типи тестування з реальними проблемами, з якими ви стикаєтесь («Гей, наш API зламано, напишімо тестування контрактів, орієнтоване на споживача!'), диверсифікуйте свої тести, як інвестор, який створює портфель на основі аналізу ризиків — оцініть, де можуть виникнути проблеми, і підберіть деякі запобіжні заходи для помʼякшення цих потенційних ризиків + +Застереження: аргумент TDD у світі програмного забезпечення приймає типове обличчя хибної дихотомії, деякі проповідують використовувати його всюди, інші вважають, що це диявол. Кожен, хто говорить в абсолюті, помиляється :] +
    + +❌ **Інакше:** Ви пропустите деякі інструменти з неймовірною рентабельністю інвестицій, деякі, як-от Fuzz, lint і mutation, можуть надати цінність за 10 хвилин + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Cindy Sridharan пропонує багате портфоліо тестування у своїй дивовижній публікації «Тестування мікросервісів — таким же чином» + +![alt text](assets/bp-12-rich-testing.jpeg "Cindy Sridharan пропонує багате портфоліо тестування у своїй дивовижній публікації «Тестування мікросервісів — таким же чином»") + +☺️Приклад: [YouTube: “За межами модульних тестів: 5 блискучих типів тестів Node.JS (2018)” (Yoni Goldberg)](https://www.youtube.com/watch?v=-2zP494wdUY&feature=youtu.be) + +
    + +![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "Назва тесту, яка складається з 3 частин") + +
    + +

    + +## ⚪ ️2.2 Тестування компонентів може бути вашим найкращим заходом + +:white_check_mark: **Роби:** Кожен модульний тест охоплює невелику частину програми, і це дорого, щоб охопити цілу, тоді як наскрізне(end-to-end) тестування легко охоплює велику частину, але є нестабільним і повільним, чому б не застосувати збалансований підхід і написати тести, більші за модульні тести, але менші, ніж наскрізне тестування? Тестування компонентів – це неоспівана пісня світу тестування — вони забезпечують найкраще з обох світів: прийнятну продуктивність і можливість застосовувати шаблони TDD + реалістичне та велике покриття. + +Тести компонентів зосереджуються на «блоку» мікросервісу, вони працюють проти API, не мокають(mock) нічого, що належить самому мікросервісу (наприклад, справжню БД або принаймні версію цієї БД у пам’яті), але заглушують усе, що є зовнішнім як виклики до інших мікросервісів. Роблячи це, ми перевіряємо те, що розгортаємо, підходимо до програми ззовні всередину та отримуємо велику впевненість за прийнятний проміжок часу. + +[У нас є повний посібник, присвячений виключно правильному написанню компонентних тестів](https://github.com/testjavascript/nodejs-integration-tests-best-practices) + +
    + +❌ **Інакше:** Ви можете витратити довгі дні на написання модульних тестів, щоб дізнатися, що ви отримали лише 20% покриття системи + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Supertest дозволяє підходити до Express API в процесі (швидко та охоплює багато рівнів) + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Приклади з Mocha") + +![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) дозволяє підходити до Express API в процесі (швидко та охоплює багато рівнів)") + +
    + +

    + +## ⚪ ️2.3 Переконайтеся, що нові релізи не порушують API, використовуючи тести контракту + +:white_check_mark: **Роби:** Отже, ваш мікросервіс має кілька клієнтів, і ви запускаєте кілька версій сервісу з міркувань сумісності (щоб усі були задоволені). Потім ви змінюєте якесь поле і «бум!», якийсь важливий клієнт, який покладається на це поле, сердиться. Це 22-й підступ світу інтеграції: серверній стороні дуже складно врахувати всі численні очікування клієнтів — З іншого боку, клієнти не можуть проводити жодного тестування, оскільки сервер контролює дати випуску. Існує цілий спектр методів, які можуть помʼякшити проблему контракту, деякі з них прості, інші є багатшими на функції та вимагають крутішої кривої навчання. У простому та рекомендованому підході постачальник API публікує пакет npm із типом API (наприклад, JSDoc, TypeScript). Тоді споживачі зможуть отримати цю бібліотеку та отримати вигоду від аналізу часу кодування та перевірки. Вигадливіший підхід — використання [PACT](https://docs.pact.io/), створеного для формалізації цього процесу за допомогою дуже руйнівного підходу —«не сервер сам визначає план тестування, а клієнт визначає тести. PACT може записувати очікування клієнта та розміщувати в спільному місці, «посереднику», щоб сервер міг отримати очікування та запускати кожну збірку за допомогою бібліотеки PACT для виявлення порушених контрактів — очікування клієнта, яке не відповідають. Завдяки цьому всі невідповідності API сервера та клієнта виявляються на ранній стадії збирання/CI, що може позбавити вас від розчарування +
    + +❌ **Інакше:** Альтернативами є виснажливе ручне тестування або страх розгортання + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: + +![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Приклад з PACT") + +![alt text](assets/bp-14-testing-best-practices-contract-flow.png) + +
    + +

    + +## ⚪ ️ 2.4 Тестуйте мідлвари(middlewares) окремо + +:white_check_mark: **Роби:** Багато хто уникає мідлвари, оскільки воно представляє невелику частину системи та потребує активного сервера Express. Обидві причини неправильні — Проміжні програми невеликі, але впливають на всі або більшість запитів і можуть бути легко перевірені як чисті функції, які отримують {req,res} обʼєкти JS. Щоб перевірити функцію мідлвари, потрібно просто викликати її та перевірити ([використовуючи, наприклад, Sinon](https://www.npmjs.com/package/sinon)) взаємодію з об’єктами {req,res}, щоб переконатися, що функція виконала правильну дію. Бібліотека [node-mock-http](https://www.npmjs.com/package/node-mocks-http) йде ще далі й враховує об’єкти {req,res} разом зі спостереженням за їхньою поведінкою. Наприклад, він може стверджувати, чи статус http, встановлений для об’єкта res, відповідає очікуванням (див. приклад нижче) +
    + +❌ **Інакше:** Помилка в мідлварі Express === помилка в усіх або більшості запитів + +
    + +
    Приклади коду + +
    + +### :clap:Приклад правильного виконання: Тестування мідлвари в ізоляції без здійснення мережевих викликів і пробудження всієї машини Express + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") + +```javascript +//the middleware we want to test +const unitUnderTest = require("./middleware"); +const httpMocks = require("node-mocks-http"); +// Синтаксис Jest, еквівалентний describe() і it() у Mocha +test("A request without authentication header, should return http status 403", () => { + const request = httpMocks.createRequest({ + method: "GET", + url: "/user/42", + headers: { + authentication: "" + } + }); + const response = httpMocks.createResponse(); + unitUnderTest(request, response); + expect(response.statusCode).toBe(403); +}); +``` + +
    + +

    + +## ⚪ ️2.5 Вимірювання та ре факторинг за допомогою інструментів статичного аналізу + +:white_check_mark: **Роби:** Використання інструментів статичного аналізу допомагає, надаючи об’єктивні способи покращити якість коду та зберегти ваш код придатним для обслуговування. Ви можете додати інструменти статичного аналізу до збірки CI, щоб перервати її, коли вона виявить запах коду. Його головні переваги над звичайним лінтуванням — це можливість перевіряти якість у контексті кількох файлів (наприклад, виявляти дублікати), виконувати розширений аналіз (наприклад, складності коду) і стежити за історією та прогресом проблем із кодом. Два приклади інструментів, які ви можете використовувати, це [SonarQube](https://www.sonarqube.org/) (4900+ [зірочок](https://github.com/SonarSource/sonarqube)) і [Code Climate](https ://codeclimate.com/) (2000+ [зірочок](https://github.com/codeclimate/codeclimate)) + +Credit: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + +❌ **Інакше:** З низькою якістю коду помилки та продуктивність завжди будуть проблемою, яку не зможе вирішити жодна блискуча нова бібліотека чи сучасні функції + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: CodeClimate, комерційний інструмент, який може ідентифікувати складні методи: + +![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg "Приклад з CodeClimate") + +![alt text](assets/bp-16-yoni-goldberg-quality.png "CodeClimate, комерційний інструмент, який може ідентифікувати складні методи:") + +
    + +

    + +## ⚪ ️ 2.6 Перевірте свою готовність до хаосу, пов’язаного з Node + +:white_check_mark: **Роби:** Дивно, але більшість тестувань програмного забезпечення стосуються лише логіки та даних, але деякі з найгірших речей, які трапляються (і їх справді важко мігрувати), — це проблеми з інфраструктурою. Наприклад, ви коли-небудь перевіряли, що відбувається, коли пам’ять вашого процесу перевантажується, або коли сервер/процес вмирає, чи ваша система моніторингу розуміє, коли API стає на 50% повільнішим?. Щоб перевірити та пофіксити такі погані речі — [Інженерія хаосу](https://principlesofchaos.org/ua/) створена Netflix. Він має на меті забезпечити обізнаність, структуру та інструменти для перевірки стійкості нашої програми до хаотичних проблем. Наприклад, один із його відомих інструментів, [мавпа хаосу](https://github.com/Netflix/chaosmonkey), випадково вимикає сервери, щоб гарантувати, що наш сервіс усе ще може обслуговувати користувачів і не покладатися на один сервер (є також версія Kubernetes, [kube-monkey](https://github.com/asobti/kube-monkey), яка вбиває модулі). Усі ці інструменти працюють на рівні хостингу/платформи, але що, якщо ви хочете перевірити та створити чистий хаос Node, наприклад перевірити, як ваш процес Node справляється з неперехопленими помилками, необробленим відхиленням обіцянок, перевантаженням пам’яті v8 із максимально допустимим 1,7 ГБ чи чи ваш UX залишається задовільним, коли цикл подій часто блокується? щоб вирішити цю проблему, я написав [node-chaos](https://github.com/i0natan/node-chaos-monkey) (альфа), який надає всілякі хаотичні дії, пов’язані з Node +
    + +❌ **Інакше:** Тут немає виходу, закон Мерфі вдарить по вашому виробництву без жалю + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: : Node-chaos може генерувати різноманітні розіграші Node.js, щоб ви могли перевірити, наскільки ваша програма стійка до хаосу + +![alt text](assets/bp-17-yoni-goldberg-chaos-monkey-nodejs.png "Node-chaos може генерувати різноманітні розіграші Node.js, щоб ви могли перевірити, наскільки ваша програма стійка до хаосу") + +
    + +
    + +## ⚪ ️2.7 Уникайте глобальних фікстур сідів, додавайте данні для кожного тесту + +:white_check_mark: **Роби:** Дотримуючись золотого правила (Розділ 0), кожен тест повинен додавати власний набір рядків БД і діяти на них, щоб запобігти зчепленню та легко обґрунтувати потік тесту. Насправді це часто порушується тестувальниками, які заповнюють БД даними перед запуском тестів (також відомих як «test fixture») заради підвищення продуктивності. Хоча продуктивність справді є обґрунтованою проблемою —«її можна пофіксити (див. пункт «Тестування компонентів»), однак складність тесту є дуже болісним сумом, який у більшості випадків повинен керувати іншими міркуваннями. На практиці зробіть так, щоб кожен тестовий приклад явно додавав необхідні записи БД і діяв лише з цими записами. Якщо продуктивність стає критичною проблемою — збалансований компроміс може прийти у формі заповнення єдиного набору тестів, які не змінюють дані (наприклад, запити) +
    + +❌ **Інакше:** Кілька тестів зазнають невдач, розгортання перервано, наша команда витрачатиме дорогоцінний час зараз, у нас є помилка? дослідім, о ні — схоже, що два тести мутували однакові вихідні дані + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: тести не є незалежними і покладаються на якийсь глобальний хук для передачі глобальних даних БД + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Приклади з Mocha") + +```javascript +before(async () => { + //додавання даних про сайти та адмінів до нашої БД. Де дані? назовні. У якомусь зовнішньому фреймворку json або міграції + await DB.AddSeedDataFromJson('seed.json'); +}); +it("When updating site name, get successful confirmation", async () => { + //Я знаю, що сайт з назвою "портал" існує - я бачив це в початкових файлах + const siteToUpdate = await SiteService.getSiteByName("Portal"); + const updateNameResult = await SiteService.changeName(siteToUpdate, "newName"); + expect(updateNameResult).to.be(true); +}); +it("When querying by site name, get the right site", async () => { + //Я знаю, що сайт з назвою "портал" існує - я бачив це в початкових файлах + const siteToCheck = await SiteService.getSiteByName("Portal"); + expect(siteToCheck.name).to.be.equal("Portal"); //Невдача! Попередній тест змінив назву :[ +}); + +``` + +
    + +### :clap: Приклад правильного виконання: Ми можемо залишатися в межах тесту, кожен тест діє на власний набір даних + +```javascript +it("When updating site name, get successful confirmation", async () => { + //тест додає свіжі нові записи та діє лише на основі записів + const siteUnderTest = await SiteService.addSite({ + name: "siteForUpdateTest" + }); + const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); + expect(updateNameResult).to.be(true); +}); +``` + +
    + +
    + +## ⚪ ️2.8 Виберіть чітку стратегію очищення даних: після всього (рекомендовано) або після кожного + +:white_check_mark: **Роби:** Час, коли тести очищають базу даних, визначає спосіб написання тестів. Два найбільш життєздатних варіанти: очищення після всіх тестів і очищення після кожного тесту. Вибираючи останній варіант, очищення після кожного окремого тесту гарантує чисті таблиці та створює зручні переваги тестування для розробника. На момент початку тесту не існує інших записів, можна бути впевненим, які дані запитуються, і навіть може виникнути спокуса підрахувати рядки під час тверджень. Це має серйозні недоліки: під час роботи в багатопроцесному режимі тести можуть заважати один одному. Поки процес-1 очищає таблиці, у той самий момент процес-2 запитує дані та зазнає помилки (оскільки БД раптово видалено процесом-1). Крім того, важче усунути невдалі тести – відвідування БД не покаже жодних записів. + +Другий варіант — очищення після завершення всіх тестових файлів (або навіть щодня!). Такий підхід означає, що та сама БД з наявними записами обслуговує всі тести та процеси. Щоб не наступати один одному на ноги, тести повинні додавати та діяти на основі конкретних записів, які вони додали. Потрібно перевірити, чи додано якийсь запис? Припустімо, що є інші тисячі записів і запит на записи, які були додані явно. Потрібно перевірити, чи видалено запис? Неможливо припустити, що таблиця порожня, перевірте, чи немає цього конкретного запису. Ця техніка дає кілька значних переваг: вона працює нативно в багатопроцесному режимі, коли розробник хоче зрозуміти, що сталося – дані є, а не видаляються. Це також збільшує ймовірність виявлення помилок, оскільки БД заповнена записами, а не штучно порожня. [Перегляньте повну порівняльну таблицю тут](https://github.com/testjavascript/nodejs-integration-tests-best-practices/blob/master/graphics/db-clean-options.png). +
    + +❌ **Інакше:** Без стратегії розділення записів або очищення - Тести будуть наступати один на одного; Використання транзакцій працюватиме лише для реляційної БД і, ймовірно, ускладниться, коли з’являться внутрішні транзакції + +
    + +
    Приклади коду + +
    + +### :clap: Прибирання після ВСІХ перевірок. Не обов'язково після кожного запуску. Чим більше даних ми маємо під час виконання тестів, тим більше це нагадує переваги виробництва + +```javascript + // Після всього прибрати (рекомендовано) + // global-teardown.js +module.exports = async () => { + // ... + if (Math.ceil(Math.random() * 10) === 10) { + await new OrderRepository().cleanup(); + } +}; +``` + +
    + +
    + +## ⚪ ️2.9 Ізолюйте компонент від світу за допомогою перехоплювача HTTP + +:white_check_mark: **Роби:** Ізолюйте тестований компонент, перехоплюючи будь-який вихідний HTTP-запит і надаючи потрібну відповідь, щоб HTTP API співавтора не постраждав. Nock є чудовим інструментом для цієї місії, оскільки він забезпечує зручний синтаксис для визначення поведінки зовнішніх служб. Ізоляція є обов’язковою для запобігання шуму та повільної продуктивності, але здебільшого для імітації різних сценаріїв і реакцій. Хороший симулятор польоту — це не малювання чистого блакитного неба, а створення безпечних штормів і хаосу. Це посилюється в архітектурі мікросервісу, де фокус завжди має бути зосереджений на одному компоненті без залучення решти світу. Хоча можна імітувати поведінку зовнішнього сервісу за допомогою повторюваних тестів (mocking), бажано не торкатися розгорнутого коду та діяти на рівні мережі, щоб тести залишалися чисто чорною скринькою. Негативною стороною ізоляції є відсутність виявлення змін компонента співавтора та непорозуміння між двома службами. Обов’язково компенсуйте це за допомогою кількох контрактів або тестів E2E +
    + +❌ **Інакше:** Деякі служби надають фальшиву версію, яку абонент може розгорнути локально, зазвичай за допомогою Docker. Це полегшить налаштування та підвищить продуктивність, але не допоможе з симуляцією різних відповідей; Деякі служби надають середовище «пісочниці», тому справжня служба зазнає удару, але не спричиняє жодних витрат або побічних ефектів. Це зменшить шум під час налаштування сторонньої служби, але також не дозволить імітувати сценарії + +
    + +
    Приклади коду + +
    + +### :clap: Запобігання мережевим викликам зовнішніх компонентів дозволяє імітувати сценарії та мінімізувати шум + +```javascript +// Перехоплювати запити для сторонніх API і повертати попередньо визначену відповідь +beforeEach(() => { + nock('http://localhost/user/').get(`/1`).reply(200, { + id: 1, + name: 'John', + }); +}); +``` + +
    + +## ⚪ ️2.10 Перевірте схему відповіді, коли є автоматично згенеровані поля + +:white_check_mark: **Роби:** Якщо неможливо підтвердити певні дані, перевірте наявність і типи обов’язкових полів. Іноді відповідь містить важливі поля з динамічними даними, які неможливо передбачити під час написання тесту, як-от дати та числа, що збільшуються. Якщо договір API обіцяє, що ці поля не будуть нульовими і містять правильні типи, обов’язково перевірте це. Більшість бібліотек тверджень підтримують типи перевірки. Якщо відповідь невелика, перевірте дані, що повертаються, і введіть разом в одному твердженні (див. приклад коду). Ще один варіант — перевірити всю відповідь за документом OpenAPI (Swagger). Більшість тестувальників мають розширення спільноти, які перевіряють відповіді API на їхню документацію. + + +
    + +❌ **Інакше:** Хоча програма виклику коду/API покладається на деякі поля з динамічними даними (наприклад, ідентифікатор, дата), вона не прийде у відповідь і не порушить контракт + +
    + +
    Приклади коду + +
    + +### :clap: Стверджуючи, що поля з динамічним значенням існують і мають правильний тип + +```javascript + test('When adding a new valid order, Then should get back approval with 200 response', async () => { + // ... + // Стверджування + expect(receivedAPIResponse).toMatchObject({ + status: 200, + data: { + id: expect.any(Number), // Будь-яке число задовольняє цей тест + mode: 'approved', + }, + }); +}); +``` + +
    + +
    + +## ⚪ ️2.12 Перевірте крайні випадки інтеграції та хаос + +:white_check_mark: **Роби:** Перевіряючи інтеграції, виходьте за рамки щасливих і сумних шляхів. Перевіряйте не лише відповіді з помилками (наприклад, помилка HTTP 500), а й аномалії на рівні мережі, як-от повільні відповіді та відповіді, що закінчилися. Це доведе, що код стійкий і може обробляти різні мережеві сценарії, як-от вибір правильного шляху після тайм-ауту, відсутність крихких умов змагання та містить автоматичний вимикач для повторних спроб. Інструменти перехоплення з авторитетним досвідом можуть легко імітувати різні поведінки мережі, як-от неспокійне обслуговування, яке час від часу дає збій. Він навіть може зрозуміти, коли значення тайм-ауту HTTP-клієнта за замовчуванням перевищує змодельований час відповіді, і відразу, не чекаючи, викликати виняток часу очікування + + +
    + +❌ **Інакше:** Усі ваші тести проходять успішно, тільки виробництво буде аварійно або неправильно повідомляти про помилки, коли треті сторони надсилатимуть виняткові відповіді + +
    + +
    Приклади коду + +
    + +### :clap: Забезпечення того, що в разі збоїв у мережі автоматичний вимикач може врятувати ситуацію + +```javascript + test('When users service replies with 503 once and retry mechanism is applied, then an order is added successfully', async () => { + // Упорядкування + nock.removeInterceptor(userServiceNock.interceptors[0]) + nock('http://localhost/user/') + .get('/1') + .reply(503, undefined, { 'Retry-After': 100 }); + nock('http://localhost/user/') + .get('/1') + .reply(200); + const orderToAdd = { + userId: 1, + productId: 2, + mode: 'approved', + }; + + // Дія + const response = await axiosAPIClient.post('/order', orderToAdd); + + // Стверджування + expect(response.status).toBe(200); +}); +``` + +
    + +
    + + +## ⚪ ️2.13 Перевірте п’ять потенційних результатів + +:white_check_mark: **Роби:** Плануючи свої тести, подумайте про охоплення п’яти типових виходів потоку. Коли ваш тест запускає певну дію (наприклад, виклик API), відбувається реакція, відбувається щось значуще та вимагає тестування. Зауважте, що нам байдуже, як все працює. Ми зосереджені на результатах, речах, які помітні ззовні та можуть вплинути на користувача. Ці результати/реакції можна розділити на 5 категорій: + +• Відповідь - Тест викликає дію (наприклад, через API) і отримує відповідь. Тепер він займається перевіркою правильності даних відповіді, схеми та статусу HTTP + +• Новий стан - Після виклику дії деякі **загальнодоступні** дані, ймовірно, змінюються + +• Зовнішні виклики - Після виклику дії програма може викликати зовнішній компонент через HTTP або будь-який інший транспорт. Наприклад, дзвінок для відправки SMS, електронної пошти або стягнення коштів з кредитної картки + +• Message queues - Результатом потоку може бути повідомлення в черзі + +• Спостережливість - Деякі речі необхідно відстежувати, як-от помилки чи значні ділові події. Коли транзакція зазнає невдачі, ми очікуємо не лише правильної відповіді, але й правильної обробки помилок і належного журналювання/метрик. Ця інформація надходить безпосередньо до дуже важливого користувача – користувача ops (SRE/адміністратора) + + + +

    + +# Section 3️⃣: Frontend Тестування + +## ⚪ ️ 3.1 Відокремте інтерфейс від функціональності + +:white_check_mark: **Роби:** Коли ви зосереджуєтеся на тестуванні логіки компонентів, деталі інтерфейсу користувача стають шумом, який слід виділити, щоб ваші тести могли зосередитися на чистих даних. На практиці витягуйте потрібні дані з розмітки абстрактним способом, який не надто пов’язаний із графічною реалізацією, затверджуйте лише чисті дані (на відміну від графічних деталей HTML/CSS) і вимикайте анімацію, яка сповільнюється. У вас може виникнути спокуса уникнути візуалізації та протестувати лише задню частину інтерфейсу користувача (наприклад, служби, дії, магазин), але це призведе до вигаданих тестів, які не схожі на реальність, і не виявлять випадків, коли правильні дані навіть не потрапляє в інтерфейс користувача + +
    + +❌ **Інакше:** Чисті обчислені дані вашого тесту можуть бути готові через 10 мс, але тоді весь тест триватиме 500 мс (100 тестів = 1 хв) через деяку фантастичну та нерелевантну анімацію + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Відокремлення деталей інтерфейсу + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Приклад з React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Приклад з react-testing-library") + +```javascript +test("When users-list is flagged to show only VIP, should display only VIP members", () => { + // Arrange + const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; + + // Act + const { getAllByTestId } = render(); + + // Assert - Спочатку витягніть дані з інтерфейсу користувача + const allRenderedUsers = getAllByTestId("user").map(uiElement => uiElement.textContent); + const allRealVIPUsers = allUsers.filter(user => user.vip).map(user => user.name); + expect(allRenderedUsers).toEqual(allRealVIPUsers); //compare data with data, no UI here +}); +``` + +
    + +### :thumbsdown: Приклад антишаблону: Деталі та дані інтерфейсу користувача змішані + +```javascript +test("When flagging to show only VIP, should display only VIP members", () => { + // Arrange + const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; + + // Act + const { getAllByTestId } = render(); + + // Assert - Змішуйте інтерфейс і дані в твердженні + expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); +}); +``` + +
    + +

    + +## ⚪ ️ 3.2 Запит елементів HTML на основі атрибутів, які навряд чи зміняться + +:white_check_mark: **Роби:** Виконуйте запит до HTML-елементів на основі атрибутів, які, ймовірно, переживуть графічні зміни, на відміну від селекторів CSS і міток форм. Якщо призначений елемент не має таких атрибутів, створіть спеціальний тестовий атрибут, наприклад «test-id-submit-button». Виконання цього шляху не тільки гарантує, що ваші функціональні/логічні тести ніколи не зламаються через зміни зовнішнього вигляду, але також стає зрозумілим для всієї команди, що цей елемент і атрибут використовуються тестами, і їх не слід видаляти + +
    + +❌ **Інакше:** Ви хочете перевірити функціональність входу, яка охоплює багато компонентів, логіку та служби, все налаштовано ідеально – заглушки, шпигуни, виклики Ajax ізольовані. Все здається ідеальним. Тоді тест не вдається, тому що дизайнер змінив клас CSS div з 'thick-border' на 'thin-border' + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Запит елемента за допомогою спеціального атрибута для тестування + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Приклад з React") + +```html +// Код розмітки (React component) +

    + + {value} + + +

    +``` + +```javascript +// у цьому прикладі використовується бібліотека react-testing-library +test("Whenever no data is passed to metric, show 0 as default", () => { + // Arrange + const metricValue = undefined; + + // Act + const { getByTestId } = render(); + + expect(getByTestId("errorsLabel").text()).toBe("0"); +}); +``` + +
    + +### :thumbsdown: Приклад антишаблону: Покладаючись на атрибути + +```html + +{value} + +``` + +```javascript +// Приклад з Еnzyme +test("Whenever no data is passed, error metric shows zero", () => { + // ... + + expect(wrapper.find("[className='d-flex-column']").text()).toBe("0"); +}); +``` + +
    + +
    + +## ⚪ ️ 3.3 За можливості тестуйте з реалістичним і повністю відтвореним компонентом + +:white_check_mark: **Роби:** Щоразу, коли ваш компонент має прийнятний розмір, тестуйте свій компонент ззовні, як це роблять ваші користувачі, повністю візуалізуйте користувальницький інтерфейс, реагуйте на нього та стверджуйте, що оброблений користувальницький інтерфейс поводиться належним чином. Уникайте будь-якого знущального, часткового та поверхневого рендерингу — такий підхід може призвести до невловлених помилок через брак деталей і посилити технічне обслуговування, оскільки тести зіпсуються з внутрішніми елементами (див. маркований пункт ['Favour blackbox testing'](https://github). .com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-14-stick-to-black-box-testing-test-only-public-methods)). Якщо один із дочірніх компонентів значно уповільнює (наприклад, анімація) або ускладнює налаштування, подумайте про явну заміну його на підробку + +З огляду на все сказане, варто зробити одне застереження: ця техніка працює для малих/середніх компонентів, які містять дочірні компоненти розумного розміру. Повне відтворення компонента із занадто великою кількістю дочірніх компонентів ускладнить обґрунтування помилок тестування (аналіз першопричини) і може працювати надто повільно. У таких випадках напишіть лише кілька тестів проти цього жирного батьківського компонента та більше тестів щодо його дочірніх компонентів + +
    + +❌ **Інакше:** Під час перегляду внутрішніх компонентів, викликаючи його приватні методи та перевіряючи внутрішній стан, вам доведеться виконати рефакторинг усіх тестів під час рефакторингу реалізації компонентів. Чи справді у вас є можливості для такого рівня обслуговування? + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Реалістична робота з повністю відрендереним компонентом + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Приклад з React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Enzyme-blue.svg "Приклад з Enzyme") + +```javascript +class Calendar extends React.Component { + static defaultProps = { showFilters: false }; + + render() { + return ( +
    + /* Панель фільтрів із кнопкою, щоб приховати/показати фільтри */ + +
    + ); + } +} + +// Приклад з React & Enzyme +test("Realistic approach: When clicked to show filters, filters are displayed", () => { + // Arrange + const wrapper = mount(); + + // Act + wrapper.find("button").simulate("click"); + + // Assert + expect(wrapper.text().includes("Choose Filter")); + // Ось як користувач підійде до цього елемента: за текстом +}); +``` + +### :thumbsdown: Приклад антишаблону: Мокінг за допомогою неглибокого(shallow) рендерінгу + +```javascript +test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { + // Arrange + const wrapper = shallow(); + + // Act + wrapper + .find("filtersPanel") + .instance() + .showFilters(); + // Доторкніться до внутрішніх елементів, обійдіть інтерфейс і викликайте метод. Підхід білого ящика + + // Assert + expect(wrapper.find("Filter").props()).toEqual({ title: "Choose Filter" }); + // що, якщо ми змінимо ім’я пропу або не передамо нічого відповідного? +}); +``` + +
    + +
    + +## ⚪ ️ 3.4 Не спіть, використовуйте вбудовану підтримку фреймворків для асинхронних подій. Також спробуйте прискорити процес + +:white_check_mark: **Роби:** У багатьох випадках час завершення блоку, що тестується, просто невідомий (наприклад, анімація призупиняє появу елемента) - у такому випадку уникайте сплячого режиму (наприклад, setTimeOut) і віддайте перевагу більш детермінованим методам, які пропонують більшість платформ. Деякі бібліотеки дозволяють очікувати виконання операцій (наприклад, [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), інші надають API для очікування, як [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Іноді більш елегантним способом є заглушка повільного ресурсу, наприклад API, а потім, коли момент відповіді стане детермінованим, компонент може бути явно повторно відтворений. Якщо залежно від зовнішнього компонента, який спить, може виявитися корисним [поспішити годинник](https://jestjs.io/docs/en/timer-mocks). Сон — це шаблон, якого слід уникати, оскільки він змушує ваш тест бути повільним або ризикованим (якщо ви чекаєте занадто короткий період). Щоразу, коли сплячий режим і опитування неминучі, а платформа тестування не підтримує, деякі бібліотеки npm, як-от [wait-for-expect](https://www.npmjs.com/package/wait-for-expect), можуть допомогти з напів - детерміноване рішення +
    + +❌ **Інакше:** При тривалому сні тести будуть на порядок повільніше. Під час спроби переходу в режим сну для невеликих чисел тест не вдасться, якщо тестований пристрій не відповів своєчасно. Отже, це зводиться до компромісу між нестабільністю та поганою продуктивністю + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: API E2E, який вирішується лише після виконання асинхронних операцій (Cypress) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Використання Cypress для ілюстрації ідеї") +![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Приклад з react-testing-library") + +```javascript +// Використання Cypress +cy.get("#show-products").click(); // Навігація +cy.wait("@products"); // зачекайте, поки з'явиться маршрут +// цей рядок буде виконано лише тоді, коли маршрут буде готовий +``` + +### :clap: Приклад правильного виконання: Тестування бібліотеки, яка очікує елементи DOM + +```javascript +// @testing-library/dom +test("movie title appears", async () => { + // елемент спочатку відсутній... + + // чекати появи + await wait(() => { + expect(getByText("the lion king")).toBeInTheDocument(); + }); + + // дочекатися появи і повернути елемент + const movie = await waitForElement(() => getByText("the lion king")); +}); +``` + +### :thumbsdown: Приклад антишаблону: спеціальний код сну + +```javascript +test("movie title appears", async () => { + // елемент спочатку відсутній... + + // спеціальна логіка очікування (застереження: спрощено, без часу очікування) + const interval = setInterval(() => { + const found = getByText("the lion king"); + if (found) { + clearInterval(interval); + expect(getByText("the lion king")).toBeInTheDocument(); + } + }, 100); + + // дочекатися появи і повернути елемент + const movie = await waitForElement(() => getByText("the lion king")); +}); +``` + +
    + +
    + +## ⚪ ️ 3.5 Подивіться, як вміст подається в мережі + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Приклад з Lighthouse") + +✅ **Роби:** Застосуйте деякий активний монітор, який гарантує оптимізацію завантаження сторінки в реальній мережі - це стосується будь-яких проблем з UX, як-от повільне завантаження сторінки або немініфікований пакет. Ринок інструментів перевірки не короткий: такі базові інструменти, як [pingdom](https://www.pingdom.com/), AWS CloudWatch, [gcp StackDriver](https://cloud.google.com/monitoring/uptime-checks) /) можна легко налаштувати, щоб стежити за тим, чи працює сервер і чи відповідає він відповідно до прийнятної угоди про рівень обслуговування. Це лише дряпає поверхню того, що може піти не так, тому краще вибрати інструменти, які спеціалізуються на інтерфейсі (наприклад, [lighthouse](https://developers.google.com/web/tools/lighthouse/), [pagespeed]( https://developers.google.com/speed/pagespeed/insights/)) і виконувати детальніший аналіз. Слід зосередитися на симптомах, показниках, які безпосередньо впливають на UX, як-от час завантаження сторінки, [важливий малюнок](https://scotch.io/courses/10-web-performance-audit-tips-for-your-next- billion-users-in-2018/fmp-first-meaningful-paint), [час, поки сторінка не стане інтерактивною (TTI)](https://calibreapp.com/blog/time-to-interactive/). Крім того, можна також спостерігати за технічними причинами, такими як забезпечення стиснення вмісту, час до першого байта, оптимізація зображень, забезпечення розумного розміру DOM, SSL та багато інших. Бажано мати ці багаті монітори як під час розробки, як частину CI, так і, що найголовніше, - 24x7 на робочих серверах/CDN + +
    + +❌ **Інакше:** Мабуть, прикро усвідомлювати, що після такої ретельної роботи над створенням інтерфейсу користувача, проходження 100% функціональних тестів і складного об’єднання – UX жахливий і повільний через неправильну конфігурацію CDN + +
    + +
    Приклади коду + +### :clap: Приклад правильного виконання: Звіт про перевірку завантаження сторінки Lighthouse + +![](/assets/lighthouse2.png "Lighthouse звіт завантаження сторінки") + +
    + +
    + +## ⚪ ️ 3.6 Нестабільні та повільні ресурси, як-от серверні API + +:white_check_mark: **Роби:** Під час кодування основних тестів (не тестів E2E) уникайте залучення будь-яких ресурсів, які знаходяться поза межами вашої відповідальності та контролю, як-от серверний API, і використовуйте натомість заглушки (тобто test double). На практиці замість реальних мережевих викликів API використовуйте якусь тестову подвійну бібліотеку (наприклад, [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble) тощо) для заглушки відповіді API. Основною перевагою є запобігання нестабільності — API тестування або проміжної обробки за визначенням не є дуже стабільними і час від часу не проходять ваші тести, хоча ВАШ компонент поводиться нормально (виробниче середовище не призначене для тестування, і зазвичай воно гальмує запити). Це дозволить симулювати різні дії API, які повинні керувати поведінкою вашого компонента, наприклад, коли дані не знайдено або випадок, коли API видає помилку. І останнє, але не менш важливе, мережеві виклики значно сповільнюють тести + +
    + +❌ **Інакше:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Stubbing or intercepting API calls + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Приклад з React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Приклад з react-testing-library") + +```javascript +// unit under test +export default function ProductsList() { + const [products, setProducts] = useState(false); + + const fetchProducts = async () => { + const products = await axios.get("api/products"); + setProducts(products); + }; + + useEffect(() => { + fetchProducts(); + }, []); + + return products ?
    {products}
    :
    No products
    ; +} + +// test +test("When no products exist, show the appropriate message", () => { + // Arrange + nock("api") + .get(`/products`) + .reply(404); + + // Act + const { getByTestId } = render(); + + // Assert + expect(getByTestId("no-products-message")).toBeTruthy(); +}); +``` + +
    + +
    + +## ⚪ ️ 3.7 Майте дуже мало наскрізних тестів(e2e), які охоплюють всю систему + +:white_check_mark: **Роби:** AХоча E2E (наскрізне) зазвичай означає тестування лише інтерфейсу користувача за допомогою справжнього браузера (див. [пункт 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8% 8F-36-stub-flaky-and-slow-resources-like-backend-apis)), для інших вони означають тести, які розтягують всю систему, включаючи справжній сервер. Останній тип тестів є дуже цінним, оскільки вони охоплюють помилки інтеграції між інтерфейсом і сервером, які можуть виникнути через неправильне розуміння схеми обміну. Вони також є ефективним методом для виявлення проблем міжсервісної інтеграції (наприклад, мікросервіс A надсилає неправильне повідомлення мікросервісу B) і навіть для виявлення збоїв розгортання – для тестування E2E не існує таких дружніх і зрілих інтерфейсів, як UI. такі фреймворки, як [Cypress](https://www.cypress.io/) і [Puppeteer](https://github.com/GoogleChrome/puppeteer). Недоліком таких тестів є висока вартість налаштування середовища з такою кількістю компонентів і, здебільшого, їхня крихкість — з огляду на 50 мікросервісів, навіть якщо один зазнає невдачі, тоді зазнає невдачі весь E2E. З цієї причини ми повинні використовувати цю техніку економно і, можливо, мати 1-10 таких і не більше. Тим не менш, навіть невелика кількість тестів E2E, швидше за все, виявить тип проблем, на які вони націлені – помилки розгортання та інтеграції. Бажано запускати їх у середовищі, схожому на виробництво + +
    + +❌ **Інакше:** Інтерфейс користувача може багато вкладати в тестування своєї функціональності, але дуже пізно зрозуміє, що корисне навантаження (схема даних, з якою має працювати інтерфейс користувача) сильно відрізняється від очікуваного. + +
    + +## ⚪ ️ 3.8 Прискоріть тестування E2E, повторно використовуючи облікові дані для входу + +:white_check_mark: **Роби:** У тестах E2E, які включають справжню серверну частину та покладаються на дійсний маркер користувача для викликів API, ізолювати тест до рівня, на якому користувач створюється та входить у систему під час кожного запиту, не виправдовується. Замість цього увійдіть лише один раз перед початком виконання тестів (тобто хук before-all), збережіть маркер у локальному сховищі та повторно використовуйте його для запитів. Схоже, це порушує один із основних принципів тестування — підтримувати тест автономним без зв’язку ресурсів. Хоча це обґрунтоване занепокоєння, у тестах E2E продуктивність є ключовою проблемою, і створення 1-3 запитів API перед початком кожного окремого тесту може призвести до жахливого часу виконання. Повторне використання облікових даних не означає, що тести повинні діяти з тими самими записами користувачів. Якщо ви покладаєтеся на записи користувачів (наприклад, історію платежів тестового користувача), переконайтеся, що створили ці записи як частину тесту та не повідомляли про їх існування іншим тестам. Також пам’ятайте, що бекенд може бути підробленим – якщо ваші тести зосереджені на інтерфейсі, можливо, краще ізолювати його та заглушити бекенд API (див. [пункт 3.6](https://github.com/goldbergyoni/javascript-testing- best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)). + +
    + +❌ **Інакше:** Надано 200 тестів і припущення, що login=100ms=20 секунд лише для входу знову і знову + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Вхід перед усіма, а не перед кожним + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Використання Cypress для ілюстраціі ідеї") + +```javascript +let authenticationToken; + +// відбувається перед виконанням УСІХ тестів +before(() => { + cy.request('POST', 'http://localhost:3000/login', { + username: Cypress.env('username'), + password: Cypress.env('password'), + }) + .its('body') + .then((responseFromLogin) => { + authenticationToken = responseFromLogin.token; + }) +}) + +// відбувається перед КОЖНИМ тестом +beforeEach(setUser => { + cy.visit('/home', { + onBeforeLoad (win) { + win.localStorage.setItem('token', JSON.stringify(authenticationToken)) + }, + }) +}) + +``` + +
    + +
    + +## ⚪ ️ 3.9 Проведіть один тест "smoke" E2E, який просто пройде по карті сайту + +:white_check_mark: **Роби:** Для моніторингу виробництва та перевірки працездатності під час розробки запустіть єдиний тест E2E, який відвідує всі/більшість сторінок сайту та гарантує, що ніхто не зламався. Цей тип тесту забезпечує високу окупність інвестицій, оскільки його дуже легко писати та підтримувати, але він може виявити будь-які збої, включаючи функціональні проблеми, проблеми з мережею та розгортання. Інші стилі перевірки диму та працездатності не такі надійні та вичерпні — деякі оперативні команди просто перевіряють домашню сторінку (виробництво) або розробники, які запускають багато інтеграційних тестів, які не виявляють проблем з пакуванням і браузером. Само собою зрозуміло, що димовий тест не замінює функціональні тести, а лише служить швидким детектором диму + +
    + +❌ **Інакше:** Все може здатися ідеальним, усі тести пройшли, перевірка робочого стану також позитивна, але платіжний компонент мав деякі проблеми з упаковкою, і лише маршрут /Payment не відображається + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: "Smoke" тест по всіх сторінках + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Використання Cypress") + +```javascript +it("When doing smoke testing over all page, should load them all successfully", () => { + // Smoke тест на всіх сторінках + // використовуючи будь-який пакет E2E + cy.visit("https://mysite.com/home"); + cy.contains("Home"); + cy.visit("https://mysite.com/Login"); + cy.contains("Login"); + cy.visit("https://mysite.com/About"); + cy.contains("About"); +}); +``` + +
    + +
    + +## ⚪ ️ 3.10 Виставте тести як живий спільний документ + +:white_check_mark: **Роби:** Окрім підвищення надійності додатка, тести надають ще одну привабливу можливість – слугувати живою документацією додатка. Оскільки тести за своєю суттю розмовляють менш технічною мовою та мовою продукту/UX, використання правильних інструментів може служити артефактом спілкування, який значною мірою зближує всіх колег – розробників та їхніх клієнтів. Наприклад, деякі фреймворки дозволяють виражати потік і очікування (тобто план тестування) за допомогою зрозумілої людині мови, щоб будь-яка зацікавлена ​​сторона, включаючи менеджерів із продуктів, могла читати, затверджувати та співпрацювати над тестами, які щойно стали актуальним документом вимог. Цю техніку також називають «приймальним тестом», оскільки вона дозволяє клієнту визначити свої критерії прийнятності простою мовою. Це [BDD (тестування, кероване поведінкою)](https://en.wikipedia.org/wiki/Behavior-driven_development) у чистому вигляді. Одним із популярних фреймворків, які це дозволяють, є [Cucumber, який має смак JavaScript](https://github.com/cucumber/cucumber-js), див. приклад нижче. Інша схожа, але інша можливість, [StoryBook](https://storybook.js.org/), дозволяє виставляти компоненти інтерфейсу користувача як графічний каталог, де можна переглядати різні стани кожного компонента (наприклад, візуалізувати сітку без фільтрів). , візуалізуйте цю сітку з кількома рядками або без жодного тощо), подивіться, як це виглядає та як активувати цей стан - це також може сподобатися спеціалістам із продуктів, але здебільшого служить живою документацією для розробників, які використовують ці компоненти. + +❌ **Інакше:** Після інвестування найкращих ресурсів у тестування просто шкода не використати ці інвестиції та отримати велику цінність + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Опис тестів людською мовою за допомогою cucumber-js + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Приклад з Cucumber") + +```javascript +// ось як можна описати тести за допомогою Cucumber: проста мова, яка дозволяє будь-кому розуміти та співпрацювати + +Feature: Twitter new tweet + + I want to tweet something in Twitter + + @focus + Scenario: Tweeting from the home page + Given I open Twitter home + Given I click on "New tweet" button + Given I type "Hello followers!" in the textbox + Given I click on "Submit" button + Then I see message "Tweet saved" + +``` + +### :clap: Приклад правильного виконання: Візуалізація наших компонентів, їх різних станів і вхідних даних за допомогою Storybook + +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 Виявляйте візуальні проблеми за допомогою автоматизованих інструментів + +:white_check_mark: **Роби:** Налаштуйте автоматичні інструменти для створення скріншотів інтерфейсу користувача, коли представлені зміни, і виявлення візуальних проблем, як-от накладення вмісту або порушення. Це гарантує, що не тільки правильні дані підготовлені, але й користувач може їх зручно переглядати. Ця техніка не є широко поширеною, наше мислення щодо тестування схиляється до функціональних тестів, але користувач відчуває візуальні ефекти, а з такою кількістю типів пристроїв дуже легко не помітити неприємну помилку інтерфейсу користувача. Деякі безкоштовні інструменти можуть надати основи — створювати та зберігати знімки екрана для огляду очей людини. Хоча цього підходу може бути достатньо для невеликих додатків, він має недоліки, як і будь-яке інше ручне тестування, яке потребує людської праці щоразу, коли щось змінюється. З іншого боку, досить складно автоматично виявляти проблеми з інтерфейсом користувача через відсутність чіткого визначення – тут спрацьовує поле «Візуальна регресія» та розв’язує цю загадку, порівнюючи старий інтерфейс користувача з останніми змінами та виявляючи відмінності. Деякі OSS/безкоштовні інструменти можуть надавати деякі з цих функцій (наприклад, [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS] (https://github.com/HuddleEng/PhantomCSS)>), але може стягувати значний час налаштування.Комерційна лінія інструментів (наприклад, [Applitools](https://applitools.com/), [Percy.io](https ://percy.io/)) робить крок далі, згладжуючи інсталяцію та розширюючи такі функції, як інтерфейс користувача користувача, сповіщення, інтелектуальне захоплення шляхом усунення «візуального шуму» (наприклад, реклами, анімації) і навіть аналіз першопричини DOM /CSS зміни, які призвели до проблеми + +
    + +❌ **Інакше:** Наскільки якісною є сторінка з вмістом, яка відображає чудовий вміст (пройдено 100% тестів), завантажується миттєво, але половина області вмісту прихована? + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: Типова візуальна регресія – правильний контент, який подається погано + +![alt text](assets/amazon-visual-regression.jpeg "Amazon - зламана сторніка") + +
    + +### :clap: Приклад правильного виконання: Налаштування wraith для захоплення та порівняння знімків інтерфейсу користувача + +![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") + +``` +​# Add as many domains as necessary. Key will act as a label​ + +domains: + english: "http://www.mysite.com"​ + +​# Type screen widths below, here are a couple of examples​ + +screen_widths: + + - 600​ + - 768​ + - 1024​ + - 1280​ + +​# Type page URL paths below, here are a couple of examples​ +paths: + about: + path: /about + selector: '.about'​ + subscribe: + selector: '.subscribe'​ + path: /subscribe +``` + +### :clap: Приклад правильного виконання: Використання Applitools для порівняння знімків та інших розширених функцій + +![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg "Використання Applitools") ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Використання Cypress") + +```javascript +import * as todoPage from "../page-objects/todo-page"; + +describe("visual validation", () => { + before(() => todoPage.navigate()); + beforeEach(() => cy.eyesOpen({ appName: "TAU TodoMVC" })); + afterEach(() => cy.eyesClose()); + + it("should look good", () => { + cy.eyesCheckWindow("empty todo list"); + todoPage.addTodo("Clean room"); + todoPage.addTodo("Learn javascript"); + cy.eyesCheckWindow("two todos"); + todoPage.toggleTodo(0); + cy.eyesCheckWindow("mark as completed"); + }); +}); +``` + +
    + +

    + +# Section 4️⃣: Вимірювання ефективності тесту + +

    + +## ⚪ ️ 4.1 Отримайте достатнє покриття, щоб бути впевненим, ~80%, здається, це щасливе число + +:white_check_mark: **Роби:** Мета тестування полягає в тому, щоб отримати достатню впевненість для швидкого просування; очевидно, чим більше коду тестується, тим впевненішою може бути команда. Покриття — це показник того, скільки рядків коду (і розгалужень, операторів тощо) охоплено тестами. То скільки вистачить? 10–30% — це, очевидно, занадто мало, щоб отримати будь-яке уявлення про правильність збірки, з іншого боку, 100% — це дуже дорого й може перемістити вашу увагу з критичних шляхів на екзотичні куточки коду. Довга відповідь полягає в тому, що це залежить від багатьох факторів, таких як тип застосування —«якщо ви створюєте наступне покоління Airbus A380, 100% є обов’язковим, для веб-сайту з мультфільмами 50% може бути забагато. Хоча більшість ентузіастів тестування стверджують, що правильний поріг охоплення є контекстним, більшість із них також згадує число 80% як правило ([Fowler: "in the upper 80s or 90s"](https://martinfowler. com/bliki/TestCoverage.html)), який, імовірно, повинен задовольнити більшість програм. + +Поради щодо впровадження: Можливо, ви захочете налаштувати безперервну інтеграцію (CI), щоб мати поріг покриття ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) і зупинити збірку що не відповідає цьому стандарту (також можна налаштувати порогове значення для кожного компонента, див. приклад коду нижче). На додаток до цього, подумайте про виявлення зменшення покриття збірки (коли нещодавно зафіксований код має менше покриття) — це підштовхне розробників збільшити або принаймні зберегти кількість перевіреного коду. Загалом, охоплення — це лише один показник, кількісний, якого недостатньо, щоб визначити надійність вашого тестування. І його також можна обдурити, як показано в наступних пунктах + +
    + +❌ **Інакше:** Впевненість і цифри йдуть пліч-о-пліч, навіть не знаючи, що ви перевірили більшу частину системи -також буде певний страх, і страх сповільнить вас + +
    + +
    Приклади коду + +
    + +### :clap: Приклад: Типовий звіт про покриття + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "Типовий звіт про покриття") + +
    + +### :clap: Приклад правильного виконання: Налаштування покриття для кожного компонента (за допомогою Jest) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Jest") + +![alt text](assets/bp-18-code-coverage2.jpeg "Налаштування покриття для кожного компонента (за допомогою Jest)") + +
    + +

    + +## ⚪ ️ 4.2 Перевірте звіти про покриття, щоб виявити неперевірені області та інші дивацтва + +:white_check_mark: **Роби:** Деякі проблеми непомічені, і їх дуже важко знайти за допомогою традиційних інструментів. Насправді це не помилки, а скоріше дивовижна поведінка програми, яка може мати серйозні наслідки. Наприклад, часто деякі області коду ніколи або рідко викликаються — ви думали, що клас «PricingCalculator» завжди встановлює ціну продукту, але виявилося, що він насправді ніколи не викликається, хоча ми маємо 10000 продуктів у БД і багато продажів… Покриття коду звіти допомагають зрозуміти, чи працює програма так, як ви думаєте. Окрім цього, він також може підкреслити, які типи коду не перевірено —«інформація про те, що 80% коду перевірено, не говорить про те, чи охоплено критичні частини. Створювати звіти легко — просто запустіть свою програму в робочому стані або під час тестування з відстеженням покриття, а потім перегляньте кольорові звіти, у яких показано, як часто викликається кожна область коду. Якщо ви не поспішаєте зазирнути в ці дані — ви можете знайти деякі проблеми +
    + +❌ **Інакше:** Якщо ви не знаєте, які частини вашого коду залишилися необробленими, ви не знаєте, звідки можуть виникнути проблеми + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: Що не так із цим звітом про покриття? + +На основі реального сценарію, коли ми відстежували використання нашої програми в QA та знаходили цікаві шаблони входу (Підказка: кількість помилок входу непропорційна, щось явно не так. Нарешті виявилося, що якась помилка інтерфейсу постійно вражає серверний API входу) + +![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "Що не так із цим звітом про покриття?") + +
    + +

    + +## ⚪ ️ 4.3 Виміряйте логічне покриття за допомогою тестування на мутації + +:white_check_mark: **Роби:** Традиційний показник покриття часто брехня: він може показати вам 100% покриття коду, але жодна з ваших функцій, навіть жодна, не повертає правильну відповідь. Як так? він просто вимірює, які рядки коду відвідав тест, але не перевіряє, чи тести справді щось перевіряли — заявлено для правильної відповіді. Як хтось, хто подорожує у справах і показує штампи в паспорті —«це не доводить жодної роботи, лише те, що він відвідав кілька аеропортів і готелів. + +Тестування на основі мутацій тут, щоб допомогти, вимірюючи кількість коду, який був фактично ПРОТЕСТОВАНИЙ, а не просто ВІДВІДАНИЙ. [Stryker](https://stryker-mutator.io/) — це бібліотека JavaScript для тестування на мутації, реалізація якої дуже гарна: + +(1) він навмисно змінює код і «підсаджує помилки». Наприклад, код newOrder.price===0 стає newOrder.price!=0. Ці «помилки» називаються мутаціями + +(2) він запускає тести, якщо всі вдаються, тоді у нас є проблема —«тести не виконали своєї мети виявлення помилок, мутації так звані вижили. Якщо тести провалилися, то чудово, мутації були вбиті. + +Знання того, що всі або більшість мутацій були знищені, дає набагато більшу впевненість, ніж традиційне покриття, а час налаштування подібний +
    + +❌ **Інакше:** Ви будете обдурені, якщо повірите, що 85% покриття означає, що ваш тест виявить помилки в 85% вашого коду + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: 100% покриття, 0% тестування + +![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Використання Stryker") + +```javascript +function addNewOrder(newOrder) { + logger.log(`Adding new order ${newOrder}`); + DB.save(newOrder); + Mailer.sendMail(newOrder.assignee, `A new order was places ${newOrder}`); + + return { approved: true }; +} + +it("Test addNewOrder, don't use such test names", () => { + addNewOrder({ assignee: "John@mailer.com", price: 120 }); +}); // Запускає 100% покриття коду, але нічого не перевіряє +``` + +
    + +### :clap: Приклад правильного виконання: Звіти Stryker, інструмент для перевірки мутацій, виявляють і підраховують кількість коду, який не перевіряється (Мутації) + +![alt text](assets/bp-20-yoni-goldberg-mutation-testing.jpeg "Звіти Stryker, інструмент для перевірки мутацій, виявляють і підраховують кількість коду, який не перевіряється (Мутації)") + +
    + +

    + +## ⚪ ️4.4 Запобігання проблемам тестового коду за допомогою тестових лінтерів + +:white_check_mark: **Роби:** Набір плагінів ESLint створено спеціально для перевірки шаблонів коду тестів і виявлення проблем. Наприклад, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) попереджатиме, коли тест написаний на глобальному рівні (а не син оператора describe()) або коли тести [skipped](https://mochajs.org/#inclusive-tests), що може призвести до хибного переконання, що всі тести успішно пройдені. Подібним чином [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) може, наприклад, попередити, коли тест взагалі не має тверджень (нічого не перевіряє) + +
    + +❌ **Інакше:** Побачивши 90% охоплення коду та 100% зелених тестів, ваше обличчя буде широко посміхатися, доки ви не зрозумієте, що багато тестів нічого не стверджують, а багато наборів тестів просто пропущено. Сподіваюся, ви нічого не розгорнули на основі цього помилкового спостереження + +
    +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: Тестовий приклад, повний помилок, на щастя, усі вони виявлені за допомогою лінтерів (Linters) + +```javascript +describe("Too short description", () => { + const userToken = userService.getDefaultToken() // *error:no-setup-in-describe, use hooks (sparingly) instead + it("Some description", () => {});//* error: valid-test-description. Must include the word "Should" + at least 5 words +}); + +it.skip("Test name", () => {// *error:no-skipped-tests, error:error:no-global-tests. Put tests only under describe or suite + expect("somevalue"); // error:no-assert +}); + +it("Test name", () => {*//error:no-identical-title. Assign unique titles to tests +}); +``` + +
    + +

    + +# Section 5️⃣: CI та інші показники якості + +

    + +## ⚪ ️ 5.1 Збагачуйте ваші лінтери та припиняйте збірки, які мають проблеми з лінінгами + +:white_check_mark: **Роби:** Linters — це безкоштовний обід, після 5-хвилинного налаштування ви безкоштовно отримуєте автопілот, який охороняє ваш код і виявляє значні проблеми під час введення. Пройшли ті часи, коли розмови стосувалися косметики (без крапок з комою!). Сьогодні Linters може виявляти серйозні проблеми, як-от помилки, які неправильно видаються та втрачають інформацію. На додаток до вашого базового набору правил (наприклад, [стандарт ESLint](https://www.npmjs.com/package/eslint-plugin-standard) або [стиль Airbnb](https://www.npmjs.com/package /eslint-config-airbnb)), подумайте про включення деяких спеціалізованих лінтерів, таких як [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect), які можуть виявляти тести без твердження, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) може виявляти обіцянки без вирішення (ваш код ніколи не продовжиться), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme), який може виявити активні регулярні вирази, які можуть бути використані для атак DOS, і [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) здатний викликати тривогу, коли код використовує методи службової бібліотеки, які є частиною V8 основні методи, такі як Lodash.\_map(…) +
    + +❌ **Інакше:** Уявіть собі дощовий день, коли ваша продуктивність продовжує давати збої, але журнали не відображають трасування стека помилок. Що трапилось? Ваш код помилково викинув об’єкт без помилки, і трасування стека було втрачено, що є вагомою причиною для того, щоб битися головою об цегляну стіну. 5-хвилинне налаштування Linter може виявити цю TYPO і врятувати ваш день + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Приклад антишаблону: Помилково створено неправильний об’єкт Error, трасування стека для цієї помилки не з’явиться. На щастя, ESLint виявляє наступну виробничу помилку + +![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "Помилково створено неправильний об’єкт Error, трасування стека для цієї помилки не з’явиться. На щастя, ESLint виявляє наступну виробничу помилку") + +
    + +

    + +## ⚪ ️ 5.2 Скоротіть цикл зворотного зв’язку за допомогою локального Developer-CI + +:white_check_mark: **Роби:** Використовуєте CI з блискучою перевіркою якості, як-от тестування, лінтування, перевірка вразливостей тощо? Допоможіть розробникам запустити цей конвеєр також локально, щоб отримати миттєвий відгук і скоротити [цикл зворотного зв’язку](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2 -петлі зворотного зв'язку/). чому ефективний процес тестування складається з багатьох ітераційних циклів: (1) тестування -> (2) зворотній зв'язок -> (3) рефакторинг. Чим швидший зворотній зв’язок, тим більше ітерацій покращення може виконати розробник для кожного модуля та покращити результати. З іншого боку, коли зворотній зв’язок надходить із запізненням, менше ітерацій покращення може бути упаковано в один день, команда може вже перейти до іншої теми/завдання/модуля та не готова вдосконалювати цей модуль. + +На практиці деякі постачальники CI (Приклад: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) дозволяють запускати конвеєр локально. Деякі комерційні інструменти, як-от [wallaby, надають дуже цінну інформацію для тестування](https://wallabyjs.com/) як прототип розробника (без зв’язку). Крім того, ви можете просто додати сценарій npm до package.json, який запускає всі команди якості (наприклад, test, lint, vulnerabilities) — використовуйте такі інструменти, як [concurrently](https://www.npmjs.com/package/concurrently) для розпаралелювання і ненульовий код виходу, якщо один із інструментів вийшов з ладу. Тепер розробник має просто викликати одну команду — наприклад. «Якість виконання npm» — щоб отримати миттєвий відгук. Також подумайте про переривання коміту, якщо перевірка якості не вдалася за допомогою githook ([husky може допомогти](https://github.com/typicode/husky)) +
    + +❌ **Інакше:** Коли якісні результати надходять наступного дня після коду, тестування не стає плавною частиною розробки, а формальним артефактом. + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Сценарії npm, які виконують перевірку якості коду, усі виконуються паралельно на вимогу або коли розробник намагається надіслати новий код + +```javascript +"scripts": { + "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", + "inspect:lint": "eslint .", + "inspect:vulnerabilities": "npm audit", + "inspect:license": "license-checker --failOn GPLv2", + "inspect:complexity": "plato .", + + "inspect:all": "concurrently -c \"bgBlue.bold,bgMagenta.bold,yellow\" \"npm:inspect:quick-testing\" \"npm:inspect:lint\" \"npm:inspect:vulnerabilities\" \"npm:inspect:license\"" + }, + "husky": { + "hooks": { + "precommit": "npm run inspect:all", + "prepush": "npm run inspect:all" + } +} + +``` + +
    + +

    + +## ⚪ ️5.3 Виконайте тестування e2e на справжньому робочому дзеркалі + +:white_check_mark: **Роби:** Наскрізне тестування (e2e) є головною проблемою кожного конвеєра CI —«створення ідентичного ефемерного робочого дзеркала на льоту з усіма пов’язаними хмарними службами може бути виснажливим і дорогим. Пошук найкращого компромісу — ваша гра: [Docker-compose](https://serverless.com/) дозволяє створювати ізольоване докеризоване середовище з ідентичними контейнерами за допомогою одного простого текстового файлу, але технологія підтримки (наприклад, мережа, модель розгортання) відрізняється з реальних виробництв. Ви можете поєднати його з [‘AWS Local’](https://github.com/localstack/localstack), щоб працювати із заглушкою справжніх служб AWS. Якщо ви використовуєте [безсерверні](https://serverless.com/) кілька фреймворків, як-от безсерверні, і [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) дозволяє локальний виклик коду FaaS. + +Величезна екосистема Kubernetes ще має формалізувати стандартний зручний інструмент для локального та CI-дзеркалювання, хоча часто запускається багато нових інструментів. Одним із підходів є запуск «мінімізованого Kubernetes» за допомогою таких інструментів, як [Minikube](https://kubernetes.io/docs/setup/minikube/) і [MicroK8s](https://microk8s.io/), які нагадують справжній річ лише з меншими накладними витратами. Інший підхід полягає в тестуванні на віддаленому «реальному Kubernetes». Деякі постачальники CI (наприклад, [Codefresh](https://codefresh.io/)) мають власну інтеграцію з середовищем Kubernetes і дозволяють легко запускати конвеєр CI через реальний інші дозволяють створювати спеціальні сценарії для віддаленого Kubernetes. +
    + +❌ **Інакше:** Використання різних технологій для виробництва та тестування вимагає підтримки двох моделей розгортання та роз’єднує розробників і команду операцій + +
    + +
    Приклади коду + +
    + +### :clap: Приклад: конвеєр CI, який генерує кластер Kubernetes на льоту ([Авторство: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) + +
    deploy:
    stage: deploy
    image: registry.gitlab.com/gitlab-examples/kubernetes-deploy
    script:
    - ./configureCluster.sh $KUBE_CA_PEM_FILE $KUBE_URL $KUBE_TOKEN
    - kubectl create ns $NAMESPACE
    - kubectl create secret -n $NAMESPACE docker-registry gitlab-registry --docker-server="$CI_REGISTRY" --docker-username="$CI_REGISTRY_USER" --docker-password="$CI_REGISTRY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL"
    - mkdir .generated
    - echo "$CI_BUILD_REF_NAME-$CI_BUILD_REF"
    - sed -e "s/TAG/$CI_BUILD_REF_NAME-$CI_BUILD_REF/g" templates/deals.yaml | tee ".generated/deals.yaml"
    - kubectl apply --namespace $NAMESPACE -f .generated/deals.yaml
    - kubectl apply --namespace $NAMESPACE -f templates/my-sock-shop.yaml
    environment:
    name: test-for-ci
    + +
    + +

    + +## ⚪ ️5.4 Паралелізуйте виконання тесту + +:white_check_mark: **Роби:** Якщо все зроблено правильно, тестування стане вашим другом цілодобово та без вихідних, що надає майже миттєвий відгук. На практиці виконання 500 обмежених ЦП модульних тестів в одному потоці може тривати занадто довго. На щастя, сучасні тестувальники та платформи CI (як-от [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) і [розширення Mocha](https ://github.com/yandex/mocha-parallel-tests)) можна розпаралелити тест на кілька процесів і значно покращити час зворотного зв’язку. Деякі постачальники CI також розпаралелюють тести між контейнерами (!), що ще більше скорочує цикл зворотного зв’язку. Незалежно від того, локально над кількома процесами чи над деяким хмарним CLI з використанням кількох машин — розпаралелювання попиту, зберігаючи автономність тестів, оскільки кожен може виконуватися на різних процесах + +❌ **Інакше:** Отримання результатів тестування через 1 годину після введення нового коду, оскільки ви вже кодуєте наступні функції, є чудовим рецептом для того, щоб зробити тестування менш актуальним + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: Mocha parallel & Jest легко випереджають традиційну Mocha завдяки розпаралелюванню тестування ([Авторство: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) + +![alt text](assets/bp-24-yonigoldberg-jest-parallel.png "Mocha parallel & Jest легко випереджають традиційну Mocha завдяки розпаралелюванню тестування") + +
    + +

    + +## ⚪ ️5.5 Тримайтеся подалі від юридичних проблем, використовуючи ліцензію та перевірку на плагіат + +:white_check_mark: **Роби:** Проблеми ліцензування та плагіату, ймовірно, не є вашою головною проблемою зараз, але чому б також не поставити галочку в цьому полі через 10 хвилин? Купа пакетів npm, таких як [перевірка ліцензії](https://www.npmjs.com/package/license-checker) і [перевірка на плагіат](https://www.npmjs.com/package/plagiarism-checker) ( рекламний ролик із безкоштовним планом) можна легко вставити у ваш конвеєр CI та перевірити на наявність таких проблем, як залежності з обмежувальними ліцензіями або код, який було скопійовано з Stack Overflow і, очевидно, порушує деякі авторські права + +❌ **Інакше:** Ненавмисно розробники можуть використати пакети з невідповідними ліцензіями або скопіювати комерційний код і зіткнутися з юридичними проблемами + +
    + +
    Приклади коду + +
    + +### :clap: Приклад правильного виконання: + +```javascript +//install license-checker in your CI environment or also locally +npm install -g license-checker + +//ask it to scan all licenses and fail with exit code other than 0 if it found unauthorized license. The CI system should catch this failure and stop the build +license-checker --summary --failOn BSD + +``` + +
    + +![alt text](assets/bp-25-nodejs-licsense.png) + +
    + +

    + +## ⚪ ️5.6 Постійно перевіряйте наявність вразливих залежностей + +:white_check_mark: **Роби:** Навіть найавторитетніші залежності, такі як Express, мають відомі вразливості. Це можна легко приборкати за допомогою інструментів спільноти, таких як [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), або комерційних інструментів, таких як [snyk](https:// snyk.io/) (пропонуємо також безкоштовну версію спільноти). Обидва можна викликати з вашого КІ під час кожної збірки + +❌ **Інакше:** Щоб захистити ваш код від уразливостей без спеціальних інструментів, потрібно постійно стежити за публікаціями в Інтернеті про нові загрози. Досить нудно + +
    + +
    Приклади коду + +
    + +### :clap: Приклад: Результат аудиту NPM + +![alt text](assets/bp-26-npm-audit-snyk.png "Результат аудиту NPM") + +
    + +

    + +## ⚪ ️5.7 Автоматизуйте оновлення залежностей + +:white_check_mark: **Роби:** Yarn і npm останнє представлення package-lock.json поставило серйозну проблему (дорога в пекло вимощена благими намірами) — тепер за замовчуванням пакунки більше не отримують оновлення. Навіть команда, яка виконує багато свіжих розгортань за допомогою «встановлення npm» і «оновлення npm», не отримає нових оновлень. У кращому випадку це призводить до неповноцінних версій пакунків або до вразливого коду в гіршому. Команди тепер покладаються на добру волю та пам’ять розробників, щоб вручну оновити package.json або використовувати інструменти [наприклад, ncu](https://www.npmjs.com/package/npm-check-updates) вручну. Надійнішим способом може бути автоматизація процесу отримання найнадійніших версій залежностей, хоча немає універсальних рішень, але є два можливі шляхи автоматизації: + +(1) CI може давати збій збіркам із застарілими залежностями — використанням таких інструментів, як [‘npm outdated’](https://docs.npmjs.com/cli/outdated) або ‘npm-check-updates (ncu)’ . Це змусить розробників оновити залежності. + +(2) Використовуйте комерційні інструменти, які сканують код і автоматично надсилають запити на отримання з оновленими залежностями. Залишається одне цікаве питання: якою має бути політика оновлення залежностей —«оновлення кожного патча створює надто багато накладних витрат, оновлення безпосередньо під час випуску основного може вказувати на нестабільну версію (багато пакетів виявляються вразливими в перші дні після випуску, [перегляньте](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) інцидент eslint-scope). + +Ефективна політика оновлення може дозволити певний «період набуття прав» — нехай код відстає від @latest на деякий час і версії, перш ніж вважати локальну копію застарілою (наприклад, локальна версія — 1.3.1, а версія сховища — 1.3.8)
    + +❌ **Інакше:** У вашому виробництві працюватимуть пакунки, які були явно позначені їх автором як ризиковані + +
    + +
    Приклади коду + +
    + +### :clap: Приклад: [ncu](https://www.npmjs.com/package/npm-check-updates) можна використовувати вручну або в конвеєрі CI, щоб визначити, наскільки код відстає від останніх версій + +![alt text](assets/bp-27-yoni-goldberg-npm.png "можна використовувати вручну або в конвеєрі CI, щоб визначити, наскільки код відстає від останніх версій") + +
    + +

    + +## ⚪ ️ 5.8 Інші поради CI, не пов’язані з Node + +:white_check_mark: **Роби:** Ця публікація зосереджена на порадах щодо тестування, які пов’язані з Node JS або, принаймні, можуть бути представлені на прикладі Node JS. Однак у цьому розділі згруповано кілька добре відомих порад, не пов’язаних з Node + +
    1. Use a declarative syntax. This is the only option for most vendors but older versions of Jenkins allows using code or UI
    2. Opt for a vendor that has native Docker support
    3. Fail early, run your fastest tests first. Create a ‘Smoke testing’ step/milestone that groups multiple fast inspections (e.g. linting, unit tests) and provide snappy feedback to the code committer
    4. Make it easy to skim-through all build artifacts including test reports, coverage reports, mutation reports, logs, etc
    5. Create multiple pipelines/jobs for each event, reuse steps between them. For example, configure a job for feature branch commits and a different one for master PR. Let each reuse logic using shared steps (most vendors provide some mechanism for code reuse)
    6. Never embed secrets in a job declaration, grab them from a secret store or from the job’s configuration
    7. Explicitly bump version in a release build or at least ensure the developer did so
    8. Build only once and perform all the inspections over the single build artifact (e.g. Docker image)
    9. Test in an ephemeral environment that doesn’t drift state between builds. Caching node_modules might be the only exception
    +
    + +❌ **Інакше:** Ви пропустите роки мудрості + +

    + +## ⚪ ️ 5.9 Матриця побудови: виконуйте ті самі кроки CI, використовуючи кілька версій Node + +:white_check_mark: **Роби:** Перевірка якості пов’язана з інтуїцією, чим більше ви охопите, тим більше вам пощастить у виявленні проблем на ранній стадії. Під час розробки пакетів для багаторазового використання або запуску виробництва для кількох клієнтів із різними конфігураціями та версіями Node, CI має запустити конвеєр тестів для всіх перестановок конфігурацій. Наприклад, якщо припустити, що ми використовуємо MySQL для одних клієнтів, а Postgres для інших —«деякі постачальники CI підтримують функцію під назвою «Матриця», яка дозволяє виконувати тестування всіх перестановок MySQL, Postgres і кількох версій Node, таких як 8, 9 і 10. Це робиться лише за допомогою конфігурації без будь-яких додаткових зусиль (за умови, що у вас є тестування чи будь-які інші перевірки якості). Інші КІ, які не підтримують Matrix, можуть мати розширення або налаштування, щоб дозволити це +
    + +❌ **Інакше:** Отже, після всієї цієї важкої роботи з написання тестування, ми дозволимо помилкам прокрадатися лише через проблеми з конфігурацією? + +
    + +
    Приклади коду + +
    + +### :clap: Приклад: Використання визначення збірки Travis (постачальник CI) для запуску одного тесту на кількох версіях Node + +
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    +
    + +

    + +# Team + +## Yoni Goldberg + +
    + +
    + +**Роль:** Письменник + +**Опис:** Я незалежний консультант, який працює з компаніями зі списку Fortune 500 і гаражними стартапами над вдосконаленням їхніх додатків JS і Node.js. Більше ніж будь-яка інша тема мене захоплює, і я прагну оволодіти мистецтвом тестування. Я також автор [найкращих практик Node.js](https://github.com/goldbergyoni/nodebestpractices) + +**📗 Онлайн-курс:** Сподобався цей посібник і ви бажаєте вдосконалити свої навички тестування? Відвідайте мій комплексний курс [Тестування Node.js і JavaScript від А до Я](https://www.testjavascript.com) + +
    + +**Follow:** + +- [🐦 Twitter](https://twitter.com/goldbergyoni/) +- [📞 Contact](https://testjavascript.com/contact-2/) +- [✉️ Newsletter](https://testjavascript.com/newsletter//) + +
    +
    +
    + +## [Bruno Scheufler](https://github.com/BrunoScheufler) + +**Роль:** Технічний оглядач і радник + +Подбав про те, щоб переглянути, вдосконалити, відшліфувати та відшліфувати всі тексти + +**Опис:** full-stack веб-інженер, ентузіаст Node.js та GraphQL +
    +
    + +## [Ido Richter](https://github.com/idori) + +**Роль:** Concept, design and great advice + +**Опис:** Кмітливий розробник інтерфейсу, експерт із CSS і фанат емодзі + +## [Kyle Martin](https://github.com/js-kyle) + +**Роль:** Допомагає підтримувати роботу цього проекту та переглядає методи безпеки + +**Опис:** Любить працювати над проектами Node.js і безпекою веб-додатків. + +## Автори ✨ + +Дякуємо цим чудовим людям, які зробили внесок у це сховище! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Scott Davis

    🖋

    Adrien REDON

    🖋

    Stefano Magni

    🖋

    Yeoh Joer

    🖋

    Jhonny Moreira

    🖋

    Ian Germann

    🖋

    Hafez

    🖋

    Ruxandra Fediuc

    🖋

    Jack

    🖋

    Peter Carrero

    🖋

    Huhgawz

    🖋

    Haakon Borch

    🖋

    Jaime Mendoza

    🖋

    Cameron Dunford

    🖋

    John Gee

    🖋

    Aurelijus Rožėnas

    🖋

    Aaron

    🖋

    Tom Nagle

    🖋

    Yves yao

    🖋

    Userbit

    🖋

    Glaucia Lemos

    🚧

    koooge

    🖋

    Michal

    🖋

    roywalker

    🖋

    dangen

    🖋

    biesiadamich

    🖋

    Yanlin Jiang

    🖋

    sanguino

    🖋

    Morgan

    🖋

    Lukas Bischof

    ⚠️ 🖋

    JuanMa Ruiz

    🖋

    Luís Ângelo Rodrigues Jr.

    🖋

    José Fernández

    🖋

    Alejandro Gutierrez Barcenilla

    🖋

    Jason

    🖋

    Otavio Araujo

    ⚠️ 🖋

    Alex Ivanov

    🖋

    Yiqiao Xu

    🖋

    YuBin, Hsu

    🌍 💻
    + + + + + diff --git a/readme-zh-CN.md b/readme-zh-CN.md index 0b0e1ac4..18b3ee5f 100644 --- a/readme-zh-CN.md +++ b/readme-zh-CN.md @@ -68,11 +68,11 @@ JS 领域的 CI 指南(9 条) 我们的思维空间被主体生产代码充满,因此无法腾出额外的“大脑空间”存放复杂的东西。如果向可怜的大脑中塞进其他复杂代码,将会使得整个部分变慢,而这个部分正是用来解决我们需要测试的问题的。这也是大部分团队放弃测试的原因。 -另一方面,测试是一个友好的助手,一个你乐于与之合作、投资小汇报大的助手。科学证明我们有两套大脑系统:系统 1 用于无需努力的活动如在一个空旷的路上开车;系统 2 用于复杂和繁琐的工作如算一道数学表达式。将你的测试为系统 1 设计,当你看一段测试代码时,需要像改 HTML 文档一样简单而不是像计算 2 × (17 × 24)。 +另一方面,测试是一个友好的助手,一个你乐于与之合作、投资小回汇报大的助手。科学证明我们有两套大脑系统:系统 1 用于无需努力的活动如在一个空旷的路上开车;系统 2 用于复杂和繁琐的工作如算一道数学表达式。将你的测试为系统 1 设计,当你看一段测试代码时,需要像改 HTML 文档一样简单而不是像计算 2 × (17 × 24)。 为了达到这个目的,我们可以通过选择性价比高、投入产出比(ROI)高的技术、工具以及测试对象。仅测试需要的内容,努力保持其灵活性,某些时候甚至值得去舍弃一些测试来换取灵活性和简洁性。 -![alt text](/assets/headspace.png "We have no head room for additional complexity") +![alt text](/assets/zh-CN/headspace.png "We have no head room for additional complexity") 下面的大部分建议衍生自这一原则。 @@ -129,7 +129,7 @@ describe('Products Service', function() {
    ### :clap: 正例: 一个包含三部分的用例名 -![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") +![alt text](/assets/zh-CN/bp-1-3-parts.jpg "A test name that constitutes 3 parts") @@ -732,7 +732,7 @@ describe('Order service', function() {
    ### :clap: 正例: Cindy Sridharan 在她的文章“测试微服务——理智的方式”中提出了一个丰富的测试组合 -![alt text](assets/bp-12-rich-testing.jpeg "Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the sane way’") +![alt text](assets/zh-CN/bp-12-rich-testing.jpg "Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the sane way’") ☺️Example: [YouTube: “Beyond Unit Tests: 5 Shiny Node.JS Test Types (2018)” (Yoni Goldberg)](https://www.youtube.com/watch?v=-2zP494wdUY&feature=youtu.be) @@ -770,7 +770,7 @@ describe('Order service', function() { ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") -![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) allows approaching Express API in-process (fast and cover many layers)") +![alt text](assets/zh-CN/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) allows approaching Express API in-process (fast and cover many layers)") @@ -797,7 +797,7 @@ describe('Order service', function() { ![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") -![alt text](assets/bp-14-testing-best-practices-contract-flow.png ) +![alt text](assets/zh-CN/bp-14-testing-best-practices-contract-flow.png ) @@ -1026,7 +1026,7 @@ test('When flagging to show only VIP, should display only VIP members', () => { const { getAllByTestId } = render(); // Assert - Mix UI & data in assertion - expect(getAllByTestId('user')).toEqual('[
  • John Doe
  • ]'); + expect(getAllByTestId('user')).toEqual('[
  • John Doe
  • ]'); }); ``` @@ -1062,7 +1062,7 @@ test('When flagging to show only VIP, should display only VIP members', () => { // the markup code (part of React component)

    - {value} + {value}

    ``` @@ -1315,7 +1315,7 @@ export default function ProductsList() { fetchProducts(); }, []); - return products ?
    {products}
    :
    No products
    + return products ?
    {products}
    :
    No products
    } // test @@ -1625,7 +1625,7 @@ describe('visual validation', () => { ### :thumbsdown: 反例: 这份覆盖率报告有什么问题?基于一个真实的场景,我们跟踪了 QA 中的应用程序使用情况,并发现了一些有趣的登录模式(提示:登录失败的数量是不成比例的,有些地方显然有问题。最终表现为一些前端的 bug 不断触发后端登录API) -![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report? based on a real-world scenario where we tracked our application usage in QA and find out interesting login patterns (Hint: the amount of login failures is non-proportional, something is clearly wrong. Finally it turned out that some frontend bug keeps hitting the backend login API) +![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report? based on a real-world scenario where we tracked our application usage in QA and find out interesting login patterns (Hint: the amount of login failures is non-proportional, something is clearly wrong. Finally it turned out that some frontend bug keeps hitting the backend login API") @@ -1674,7 +1674,7 @@ it("Test addNewOrder, don't use such test names", () => { ### :clap: 正例: Stryker 报告,一个编译测试工具,发现并统计没有被测试到的代码(变异) -![alt text](assets/bp-20-yoni-goldberg-mutation-testing.jpeg "Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations)") +![alt text](assets/zh-CN/bp-20-yoni-goldberg-mutation-testing.jpg "Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations)") @@ -2052,3 +2052,6 @@ script: **Role:** 帮助保持本项目的运行,并审查与安全性有关的实践 **About:** 喜欢从事 Node.js 项目和 Web 应用安全性的工作。 + \ No newline at end of file diff --git a/readme-zh-TW.md b/readme-zh-TW.md new file mode 100644 index 00000000..d45796a7 --- /dev/null +++ b/readme-zh-TW.md @@ -0,0 +1,1905 @@ + + +
    + +# 👇 為什麼本指南可以幫助你將測試能力提升到下一個等級 + +
    + +## 📗 46+ 個最佳實踐:非常全面且徹底 + +這是從 A 到 Z 的 JavaScript 及 Node.js 可靠的指南。它為你總結及規劃了市場上大量的部落格文章、書籍及工具。 + +## 🚢 進階:從基礎向前邁進 10,000 英里 + +從基礎往前邁進的旅程,包括:在生產(production)環境中測試、變異測試(mutation testing)、以屬性為基礎(property-based)的測試以及許多策略和專業工具。如果你認真閱讀本指南書,你的測試技能可能會高於平均水準。 + +## 🌐 全端:前端、後端、CI、所有部分 + +首先了解任何應用程式都通用的測試實踐。然後再深入研究你選擇的領域:前端/UI、後端、CI 或者全部。 + + +
    + +### 作者 Yoni Goldberg + +- A JavaScript & Node.js consultant +- 📗 [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) - My comprehensive online course with more than [10 hours of video](https://www.testjavascript.com), 14 test types and more than 40 best practices +- [Follow me on Twitter](https://twitter.com/goldbergyoni/) + +
    + +### 翻譯 - 以你的語言來閱讀本文 + +- 🇹🇼[Traditional Chinese](readme-zh-TW.md) - Courtesy of [Yubin Hsu](https://github.com/yubinTW) +- 🇨🇳[Chinese](readme-zh-CN.md) - Courtesy of [Yves yao](https://github.com/yvesyao) +- 🇰🇷[Korean](readme.kr.md) - Courtesy of [Rain Byun](https://github.com/ragubyun) +- 🇵🇱[Polish](readme-pl.md) - Courtesy of [Michal Biesiada](https://github.com/mbiesiad) +- 🇪🇸[Spanish](readme-es.md) - Courtesy of [Miguel G. Sanguino](https://github.com/sanguino) +- 🇧🇷[Portuguese-BR](readme-pt-br.md) - Courtesy of [Iago Angelim Costa Cavalcante](https://github.com/iagocavalcante) , [Douglas Mariano Valero](https://github.com/DouglasMV) and [koooge](https://github.com/koooge) +- 🇫🇷[French](readme-fr.md) - Courtesy of [Mathilde El Mouktafi](https://github.com/mel-mouk) +- 🇺🇦[Ukrainian](readme-ua.md) - Courtesy of [Serhii Shramko](https://github.com/Shramkoweb) +- Want to translate to your own language? please open an issue 💜 + +

    + +## `目錄` + +#### [`第 0 章:黃金原則`](#第-0-章黃金原則-1) + +一個激發所有人的建議 (1個特殊項目) + +#### [`第 1 章:測試剖析`](#第-1-章測試剖析-1) + +基礎 - 建立乾淨的測試 (12項) + +#### [`第 2 章:後端`](#第-2-章後端測試) + +有效率地撰寫後端及微服務的測試 (8項) + +#### [`第 3 章:前端`](#第-3-章前端測試) + +為網頁 UI (包括組件及E2E) 撰寫測試 (11項) + +#### [`第 4 章:測量測試的有效程度`](#第-4-章測量測試效果) + +測量測試的品質 (4項) + +#### [`第 5 章:持續整合 (Continuous Integration)`](#第-5-章持續整合-ci-或其他提高品質的手段) + +JavaScript 世界的 CI 指南 (9項) + +

    + +# 第 0 章:黃金原則 + +
    + +## ⚪️ 0 黃金原則:Design for lean testing + +:white_check_mark: **建議:** +測試程式與主要生產環境的程式不同,要把他設計的極其簡單、簡短、具體、扁平、使人愉悅的去使用及學習。一段測試程式應該要可以讓人一眼就看懂其目的。 + +我們的思考空間被主要的程式邏輯所占滿,並沒有額外的腦容量去處理複雜的東西。如果把其他複雜的程式塞進我們可憐的大腦,將會使得整個團隊的運作變慢,而這些複雜的程式正是用來解決我們需要測試的問題。這也是許多團隊放棄測試的原因。 + +另一方面,測試是一個友好的助手,一個讓你樂意與他合作、投資小且回報大的助手。科學證明我們有兩套大腦系統:系統 1 用於無須努力的活動,如在空曠的路上開車;系統 2 用於複雜和繁瑣的工作,如計算一道數學式。把你的測試程式設計成如系統 1 一般,當你看著你的測試,要像修改 HTML 文件一樣的簡單,而不是像計算 2 x (17 x 24)。 + +為了達到這個目標,我們可以選擇具有成本效益和高投資報酬率的的技術、工具和測試目標。只測試需要的內容,努力保持他的靈活性,某些時候甚至得捨棄一些測試來換取靈活性和簡潔性。 + +![alt text](/assets/headspace.png "We have no head room for additional complexity") + +以下大部分的建議衍生自這一原則。 + +### 準備好了嗎? + +

    + +# 第 1 章:測試剖析 + +
    + +## ⚪ ️ 1.1 每個測試的名稱要包含的三個部分 + +:white_check_mark: **建議:** 一份測試報告應該告訴那些不一定熟悉程式的人,目前應用程式的修訂版本是否符合他們的要求,包括:測試人員、DevOps 工程師和兩年後的你。如果測試能包含這三個需求面的描述,就能很好的實現這一點: + +(1) 測試的對象是什麼? 例如,ProductsService.addNewProduct 這個方法。 + +(2) 在什麼情況或場景下? 例如,價格沒有傳給該方法。 + +(3) 預期的結果是什麼? 例如,新的產品沒有被批准。 + +
    + +❌ **否則:** 一個名叫"新增產品"的測試失敗了。這有確切地告訴你到底是什麼地方出問題嗎? + +
    + +**👇 Note:** 每個項目都會有一個程式範例,有時候還會搭配圖片。 +
    + +
    程式範例 + +
    + +### :clap: 正例:一個包含這三部分的測試名稱 + +![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Using Mocha to illustrate the idea") + +```javascript +// 1. unit under test +describe('Products Service', function() { + describe('Add new product', function() { + // 2. scenario and 3. expectation + it('When no price is specified, then the product status is pending approval', ()=> { + const newProduct = new ProductService().add(...); + expect(newProduct.status).to.equal('pendingApproval'); + }); + }); +}); +``` + +
    + +### :clap: 正例:一個包含這三部分的測試名稱 + +![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") + +
    + +
    +
    © Credits & read-more + 1. Roy Osherove - Naming standards for unit tests +
    + +

    + +## ⚪ ️ 1.2 以 AAA 模式來建構測試 + +:white_check_mark: **建議:** 用三個部分來組織你的測試:Arrange 安排、Act 執行、Assert 斷言 (AAA)。依照這個結構,可以確保讀者不用花費腦力去理解你的測試。 + +第一個 A - Arrange 安排:所有使系統達到測試所要模擬的情境的程式。這可能包含實體化某個待測單元的建構子、新增 DB 的資料、mocking/stubbing 物件和其他準備程式。 + +第二個 A - Act 執行:執行測試單元。通常為一行程式。 + +第三個 A - Assert 斷言:確保得到的值符合期待。通常為一行程式。 + +
    + +❌ **否則:** 你不僅需要花很多時間去理解主要程式,而且本應是最簡單的部分 - 測試,也會讓你腦力耗盡。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:以 AAA 模式來建構測試 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +describe("Customer classifier", () => { + test("When customer spent more than 500$, should be classified as premium", () => { + // Arrange + const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; + const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); + + // Act + const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); + + // Assert + expect(receivedClassification).toMatch("premium"); + }); +}); +``` + +
    + +### :thumbsdown: 反例:沒有分隔、一大坨、難以理解 + +```javascript +test("Should be classified as premium", () => { + const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; + const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); + const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); + expect(receivedClassification).toMatch("premium"); +}); +``` + +
    + +

    + +## ⚪ ️1.3 用產品語言來描述預期:使用 BDD 風格的斷言 + +:white_check_mark: **建議:** 使用聲明的方式撰寫測試,可以使讀者無腦的 get 到重點。如果你的程式使用各種條件邏輯包起來,會增加讀者的理解難度。因此,我們應該盡量使用類似人類語言的描述與言如 ```expect``` 或 ```should``` 而不是自己寫程式。如果 Chai 或 Jest 沒有你想要用的斷言,且這個斷言可以被頻繁的重複利用的話,可以考慮 [擴充 Jest 的匹配器 (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) 或是寫一個 [客製化的 Chai 插件](https://www.chaijs.com/guide/plugins/)。 + +
    + +❌ **否則:** 團隊的測試會越寫越少,且會用 .skip() 把討厭的測試略過。 + +
    + +
    程式範例
    + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +### :thumbsdown: 反例:讀者必須快速的看完冗長且複雜的程式碼,才能理解該測試的目的 + +```javascript +test("When asking for an admin, ensure only ordered admins in results", () => { + // assuming we've added here two admins "admin1", "admin2" and "user1" + const allAdmins = getUsers({ adminOnly: true }); + + let admin1Found, + adming2Found = false; + + allAdmins.forEach(aSingleUser => { + if (aSingleUser === "user1") { + assert.notEqual(aSingleUser, "user1", "A user was found and not admin"); + } + if (aSingleUser === "admin1") { + admin1Found = true; + } + if (aSingleUser === "admin2") { + admin2Found = true; + } + }); + + if (!admin1Found || !admin2Found) { + throw new Error("Not all admins were returned"); + } +}); +``` + +
    + +### :clap: 正例:快速瀏覽以下的聲明式測試非常輕鬆 + +```javascript +it("When asking for an admin, ensure only ordered admins in results", () => { + // assuming we've added here two admins + const allAdmins = getUsers({ adminOnly: true }); + + expect(allAdmins) + .to.include.ordered.members(["admin1", "admin2"]) + .but.not.include.ordered.members(["user1"]); +}); +``` + +
    + +

    + +## ⚪ ️ 1.4 堅持黑箱測試:只測試公開方法 + +:white_check_mark: **建議:** 測試內部邏輯是無意義且浪費時間的。如果你的程式/API 回傳了正確的結果,你真的需要花三個小時的時間去測試它內部究竟如何實現的,並且在之後維護這一堆脆弱的測試嗎?每當測試一個公開方法時,其私有方法的實作也會被隱性地測試,只有當存在某個問題(例如錯誤的輸出)時測試才會中斷。這種方法也稱為 ```行為測試```。另一方面,如果你測試內部方法 (白箱方法) — 你的關注點將從組件的輸出結果轉移到具體的實作細節上,如果某天內部邏輯改變了,即使結果依然正確,你也要花精力去維護之前的測試邏輯,這無形中增加了維護成本。 +
    + +❌ **否則:** 你的測試會像[狼來了](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf)一樣,總是叫喚著出問題了 (例如一個因為內部變數名稱改變而導致的測試失敗)。不出所料,人們很快就會開始忽視 CI 的通知,直到某天,一個真正的 bug 被忽視... + +
    +
    程式範例 + +
    + +### :thumbsdown: 反例:一個無腦測試內部方法的測試 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") + +```javascript +class ProductService { + // this method is only used internally + // Change this name will make the tests fail + calculateVATAdd(priceWithoutVAT) { + return { finalPrice: priceWithoutVAT * 1.2 }; + // Change the result format or key name above will make the tests fail + } + // public method + getPrice(productId) { + const desiredProduct = DB.getProduct(productId); + finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice; + return finalPrice; + } +} + +it("White-box test: When the internal methods get 0 vat, it return 0 response", async () => { + // There's no requirement to allow users to calculate the VAT, only show the final price. Nevertheless we falsely insist here to test the class internals + expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0); +}); +``` + +
    + +

    + +## ⚪ ️ ️1.5 使用正確的測試替身 (Test Double):避免總是使用 stub 和 spy + +:white_check_mark: **建議:** 測試替身是把雙刃劍,他們在提供巨大價值的同時,耦合了應用的內部邏輯 ([一篇關於測試替身的文章: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)) 在使用測試替身前,問自己一個很簡單的問題:我是用它來測試需求文件中定義的可見的功能或者可能可見的功能嗎?如果不是,那就可能是白盒測試了。 + +舉例來說,如果你想測試你的應用程式在支付服務當機時的預期行為,你可以 stub 支付服務並觸發一些"沒有回應"的回傳行為,以確保被測試的單元回傳正確的值。這可以測試特定場景下應用程式的行為、回應及輸出結果。你也可以使用一個 spy 來斷言當服務當機時是否有發送電子郵件 - 這又是一個針對可能出現在需求文件中行為的檢查 ("如果無法儲存付款資訊,發送電子郵件")。反過來說,如果你 mock 的支付服務,能確保它被正確呼叫並傳入正確的 JavaScript 型別,那麼你的測試重點是內部的邏輯,它與應用程式的功能關係不大,而且可能會經常變化。 + +
    + +❌ **否則:** 任何程式的重構都會需要程式中所有的 mock 進行相對應的更新。測試變成了一種負擔,而不是一個助力。 + +
    + +
    程式範例 + +
    + +### :thumbsdown: 反例:關注內部實作的 mock + +![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg "Examples with Sinon") + +```javascript +it("When a valid product is about to be deleted, ensure data access DAL was called once, with the right product and right config", async () => { + // Assume we already added a product + const dataAccessMock = sinon.mock(DAL); + // hmmm BAD: testing the internals is actually our main goal here, not just a side-effect + dataAccessMock + .expects("deleteProduct") + .once() + .withArgs(DBConfig, theProductWeJustAdded, true, false); + new ProductService().deletePrice(theProductWeJustAdded); + dataAccessMock.verify(); +}); +``` + +
    + +### :clap: 正例:Spy 專注於測試需求,但身為一個 side effect,無可避免地會接觸到內部程式結構 + +```javascript +it("When a valid product is about to be deleted, ensure an email is sent", async () => { + // Assume we already added here a product + const spy = sinon.spy(Emailer.prototype, "sendEmail"); + new ProductService().deletePrice(theProductWeJustAdded); + // hmmm OK: we deal with internals? Yes, but as a side effect of testing the requirements (sending an email) + expect(spy.calledOnce).to.be.true; +}); +``` + +
    + +

    + +## 📗 想要透過影片來學習這些做法嗎? + +### 歡迎來我的線上課程網站 [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +

    + +## ⚪ ️1.6 不要 "foo", 使用真實的資料 + +:white_check_mark: **建議:** 生產環境中的 bug 通常是在一些特殊或者意外的輸入下出現的 — 所以測試的輸入資料越真實,越容易在早期抓住問題。使用現有的一些函式庫(比如 [Faker](https://www.npmjs.com/package/faker))去造"假"真實數據來模擬生產環境數據的多樣性和形式。比如,這些函示庫可以產生真實的電話號碼、用戶名稱、信用卡、公司名稱等等。你還可以創建一些測試(在單元測試之上,而不是替代)生產隨機 fakers 數據來擴充你的測試單元,甚至從生產環境中導入真實的資料。如果想要更進階的話,請看下一個項目:基於屬性的測試 (property-based testing)。 +
    + +❌ **否則:** 你要部屬的程式都在 "foo" 之類的輸入值中正確的通過測試,結果上線之後收到像是 ```@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA``` 之類的輸入值後掛掉了。 + +
    + +
    程式範例 + +
    + +### :thumbsdown: 反例: 一個測試案例使用非真實資料去通過測試 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +const addProduct = (name, price) => { + const productNameRegexNoSpace = /^\S*$/; // no white-space allowed + + if (!productNameRegexNoSpace.test(name)) return false; // this path never reached due to dull input + + // some logic here + return true; +}; + +test("Wrong: When adding new product with valid properties, get successful confirmation", async () => { + // The string "Foo" which is used in all tests never triggers a false result + const addProductResult = addProduct("Foo", 5); + expect(addProductResult).toBe(true); + // Positive-false: the operation succeeded because we never tried with long + // product name including spaces +}); +``` + +
    + +### :clap:正例:使用隨機產生的真實資料來輸入 + +```javascript +it("Better: When adding new valid product, get successful confirmation", async () => { + const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); + // Generated random input: {'Sleek Cotton Computer', 85481} + expect(addProductResult).to.be.true; + // Test failed, the random input triggered some path we never planned for. + // We discovered a bug early! +}); +``` + +
    + +

    + +## ⚪ ️ 1.7 Property-based testing 基於屬性的測試:測試輸入的多種組合 + +:white_check_mark: **建議:** 通常我們只會選擇少部分的輸入樣本去做測試。 即使是使用了上一項提到的工具去模擬真實數據,我們也只覆蓋到了一部分輸入的組合 (```method('', true, 1)```, ```method('string', false , 0)```)。然而在生產環境中,一個擁有 5 個參數的 API,可能會遇到上千種排列組合的輸入,而其中的某一種可能會把你的程式搞掛(可參考 [Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing))。 + +如何撰寫一個測試,可以自動發送 1000 種不同輸入的排列組合,並捕捉到使我們的程式不能正確回傳的輸入?基於屬性的測試 (Property-based testing) 就是這樣一種技術:透過發送所有可能的輸入組合到你的測試單元中,它增加了發現 bug 的可能性。 + +例如,給定一個方法 — ```addNewProduct(id, name, isDiscount)``` — 函示庫將使用許多 ```(number, string, boolean)``` 的組合來呼叫這個方法,比如 ```(1, 'iPhone', false)```,```(2, 'Galaxy', true)```。您可以使用您喜歡的測試運行器(Mocha、Jest等),使用 [js-verify](https://github.com/jsverify/jsverify) 或者 [testcheck](https://github.com/leebyron/testcheck-js) (文件寫得比較好) 來執行基於屬性的測試。 + +更新:Nicolas Dubien 在下面的回復中建議使用 [fast-check](https://github.com/dubzzz/fast-check#readme),它似乎提供了更多的功能,且有被積極維護。 + +
    + +❌ **否則:** 你無意中選擇的測試輸入只涵蓋到運作正常的程式片段。不幸的是,他沒有發現真正的錯誤,這也降低了把測試當作發現錯誤的工具的成效。 + +
    + +
    程式範例 + +
    + +### :clap: 正例: 使用 fast-check 來測試許多的輸入組合 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +import fc from "fast-check"; + +describe("Product service", () => { + describe("Adding new", () => { + // this will run 100 times with different random properties + it("Add new product with random yet valid properties, always successful", () => + fc.assert( + fc.property(fc.integer(), fc.string(), (id, name) => { + expect(addNewProduct(id, name).status).toEqual("approved"); + }) + )); + }); +}); +``` + +
    + +

    + +## ⚪ ️ 1.8 如果需要,只使用簡短的行內快照 (inline snapshots) + +:white_check_mark: **建議:** 如果你需要進行 快照測試 ([snapshot testing](https://jestjs.io/docs/en/snapshot-testing)),只使用短而集中的快照 (如3~7行),該快照是測試程式的一部份,而不是在外部文件中。保持好這一原則,將會確保你的測試的自我解釋性且不會那麼脆弱。 + +另一方面,"classic snapshots"的教學和工具鼓勵將大文件 (如組件的渲染結果、API 的 JSON 結果) 存儲在一些外部媒介上,並確保每次測試運行時,將收到的結果與保存的版本進行比較。舉個例子,這將會隱性地將我們的測試與包含3000個數值的1000行內容耦合在一起,而測試者從未閱讀和推理過這些數據。為什麼這樣是不對的? 這樣做,將會有1000個原因讓你的測試失敗 - 只要有一行改變,快照比對就會 fail,而這可能會經常發生。多頻繁?當有每一個空格、註解或一點 CSS/HTML 的變化。不僅如此,測試名稱也不會提供關於失敗的線索,因為它只是檢查這1000行是否有變化,而且它還鼓勵測試者去接受一個他無法檢查和驗證的大文件作為期望的結果。所有這些都是測試目標不明確、測試目標過多的症狀。 + +值得注意的是,在少數情況下,大型的外部快照是可以接受的 - 當斷言的對象是 schema 而不是所有內容時 (提取出要的值並專注在某個欄位上),或者當收到的文件內容幾乎不會改變時。 + +
    + +❌ **否則:** 一個 UI 的測試失敗了。程式看起來是對的,畫面上也完美渲染了每個像素,但怎麼了? 你的測試程式發現收到的內容與期望的不同,或許只是多了一個空格... + +
    + +
    程式範例 + +
    + +### :thumbsdown: 反例: 將看不到的 2000 行程式耦合進我們的測試案例中 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +it("TestJavaScript.com is renderd correctly", () => { + // Arrange + + // Act + const receivedPage = renderer + .create( Test JavaScript ) + .toJSON(); + + // Assert + expect(receivedPage).toMatchSnapshot(); + // We now implicitly maintain a 2000 lines long document + // every additional line break or comment - will break this test +}); +``` + +
    + +### :clap: 正例:期望是可見且集中的 + +```javascript +it("When visiting TestJavaScript.com home page, a menu is displayed", () => { + // Arrange + + // Act + const receivedPage = renderer + .create( Test JavaScript ) + .toJSON(); + + // Assert + + const menu = receivedPage.content.menu; + expect(menu).toMatchInlineSnapshot(` +
      +
    • Home
    • +
    • About
    • +
    • Contact
    • +
    +`); +}); +``` + +
    + +

    + +## ⚪ ️1.9 避免使用全域的 test fixtures 或 seeds,而是放進每個測試中 + +:white_check_mark: **建議:** 參照黃金原則,每個測試需要在它自己的 DB 中進行操作避免互相污染。但現實中,這條規則經常被打破:為了性能的提升而在執行測試前初始化全域資料庫 (也被稱為"[test fixture](https://en.wikipedia.org/wiki/Test_fixture)")。儘管性能很重要,但是它可以通過後面講的「組件測試」來做取捨。為了減輕複雜度,我們可以在每個測試中只初始化自己需要的數據。除非性能問題真的非常嚴重,那還是可以做一定程度的妥協 - 僅在全域放不會改變的數據 (比如 query)。 + +
    + +❌ **否則:** 有一些測試 fail 了,團隊花了許多時間後發現,只是因為兩個測試同時改變了同一個 seed。 + +
    + +
    程式範例 + +
    + +### :thumbsdown: 反例:測試案例之間不是獨立的。而是相依於全域的 DB 資料 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +before(async () => { + // adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + await DB.AddSeedDataFromJson('seed.json'); +}); +it("When updating site name, get successful confirmation", async () => { + // I know that site name "portal" exists - I saw it in the seed files + const siteToUpdate = await SiteService.getSiteByName("Portal"); + const updateNameResult = await SiteService.changeName(siteToUpdate, "newName"); + expect(updateNameResult).to.be(true); +}); +it("When querying by site name, get the right site", async () => { + // I know that site name "portal" exists - I saw it in the seed files + const siteToCheck = await SiteService.getSiteByName("Portal"); + expect(siteToCheck.name).to.be.equal("Portal"); // Failure! The previous test change the name :[ +}); + +``` + +
    + +### :clap: 正例:每個測試案例只操作他自己的資料 + +```javascript +it("When updating site name, get successful confirmation", async () => { + // test is adding a fresh new records and acting on the records only + const siteUnderTest = await SiteService.addSite({ + name: "siteForUpdateTest" + }); + + const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); + + expect(updateNameResult).to.be(true); +}); +``` + +
    + +
    + +## ⚪ ️ 1.10 不要 catch 錯誤,expect 他們 + +:white_check_mark: **建議:** 當你要測試一些輸入是否有觸發錯誤時,使用 ```try-catch-finally``` 來檢查他是否會進入到 catch 區塊,看起來沒什麼問題。但會變成一個笨拙且冗長的測試案例 (如下面程式範例),他會隱藏簡單的測試意圖和預期的結果。 + +一個更為優雅的作法是使用專用的單行斷言:如 Chai 中的 ```expect(method).to.throw``` 或是 Jest 中的 ```expect(method).toThrow()```。必須要確保這個 expection 包含某個預期的 error type,如果只得到一個通用的錯誤型態,那應用程式將無法表明更多訊息給使用者。 + +
    + +❌ **否則:** 從測試報告 (如 CI 報告) 中要看出哪裡有錯會非常困難。 + +
    + +
    程式範例 + +
    + +### :thumbsdown: 反例:一個很長的測試案例,嘗試使用 ```try-catch``` 來斷言錯誤 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +```javascript +it("When no product name, it throws error 400", async () => { + let errorWeExceptFor = null; + try { + const result = await addNewProduct({}); + } catch (error) { + expect(error.code).to.equal("InvalidInput"); + errorWeExceptFor = error; + } + expect(errorWeExceptFor).not.to.be.null; + // if this assertion fails, the tests results/reports will only show + // that some value is null, there won't be a word about a missing Exception +}); +``` + +
    + +### :clap: 正例:一個容易閱讀及被了解的 expection,甚至能被 QA 或 PM 理解 + +```javascript +it("When no product name, it throws error 400", async () => { + await expect(addNewProduct({})) + .to.eventually.throw(AppError) + .with.property("code", "InvalidInput"); +}); +``` + +
    + +

    + +## ⚪ ️ 1.11 為測試案例打上標籤 + +:white_check_mark: **建議:** 不同的測試需要在不同的情境下執行:快速冒煙測試、無 IO 的測試、開發者儲存或提交檔案的測試、送出一個 PR 後的 end-to-end 測試等等。 可以用一些 ```#cold``` ```#api``` ```#sanity``` 之類的標籤來標註這些測試,這樣就可以在測試時只執行特定的子集合。例如在 Mocha 中可以這樣來執行:```mocha -- grep 'sanity'```。 +
    + +❌ **否則:** 執行所有測試案例,包括執行大量查詢 DB 的測試,開發者做的任何微小的變更都需要花很長的時間去跑完所有的測試,將會導致開發者不想再執行測試。 + +
    + +
    程式範例: + +
    + +### :clap: 正例:將測試案例標記為 '#cold-test' 讓執行測試的人可以只執行速度快的測試案例 (cold 指的是沒有 IO 的快速測試,甚至可以在開發人員打字時頻繁地執行) + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +// this test is fast (no DB) and we're tagging it correspondigly +// now the user/CI can run it frequently +describe("Order service", function() { + describe("Add new order #cold-test #sanity", function() { + test("Scenario - no currency was supplied. Expectation - Use the default currency #sanity", function() { + // code logic here + }); + }); +}); +``` + +
    + +

    + +## ⚪ ️ 1.12 把測試案例進行至少兩個層次的分類 + +:white_check_mark: **建議:** 對測試案例套用一些結構,讓每個看到這個測試案例的人都可以很容易得理解需求 (測試是最好的文件) 和正在測試的各種情境。一個常見的方法是在測試上方寫至少兩個用來"描述"的區塊:第一個是測試單元的名稱,第二個是額外的分類名稱,如情境或自定義的類別 (參考下面的程式範例和畫面輸出)。這樣的做法也會大幅的改善測試報告的呈現。讀者將會很容易的推斷出測試的類別,讀懂該測試的內容並與失敗的測試關聯起來。此外,對開發者來說,瀏覽這一連串的測試也變得更加容易。有許多額外的結構也是可以考慮使用的,像是 [given-when-then](https://github.com/searls/jasmine-given) 或 [RITE](https://github.com/ericelliott/riteway)。 + +
    + +❌ **否則:** 當看到一份毫無結構且數量眾多的測試報告時,讀者只能透過粗略地閱讀整份報告來總結,並將失敗的錯誤案例關聯起來。思考一個情況,當100個測試案例中有7個失敗時,看一個分層結構良好的測試報告與看一個扁平的測試結果清單相比,那些錯誤的測試案例很有可能都在同一個流程或分類底下,讀者將可以很快的推斷出錯誤的地方或看出哪部分是他們失敗的原因。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:利用測試案例的名稱和情境來組織,可以產生良好的測試報告,如下所示 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +// Unit under test +describe("Transfer service", () => { + // Scenario + describe("When no credit", () => { + // Expectation + test("Then the response status should decline", () => {}); + + // Expectation + test("Then it should send email to admin", () => {}); + }); +}); +``` + +![alt text](assets/hierarchical-report.png) + +
    + +### :thumbsdown: 反例:扁平的測試列表會使讀者很難去看懂 user story 和失敗的測試之間的關係 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Mocha") + +```javascript +test("Then the response status should decline", () => {}); + +test("Then it should send email", () => {}); + +test("Then there should not be a new transfer record", () => {}); +``` + +![alt text](assets/flat-report.png) + +
    + +
    + +

    + +## ⚪ ️1.13 其他通用且良好的測試習慣 + +:white_check_mark: **建議:** 本篇文章的重點是與 NodeJS 相關的測試建議或至少可以用 NodeJS 來舉例說明的內容。然而,這裡有幾個與 NodeJS 無關的建議,且是眾所皆知的。 + +學習並實現 [TDD原則](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) - 他對許多人來說非常有價值,但如果他不適合你的風格,不要被嚇到,不是只有你這樣。試著在寫程式之前使用 [red-green-refactor](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html) 的風格來撰寫測試,並確保每個測試案例只檢查一個測試目標。當你發現一個 bug 時,在修復它之前先新增一個可以檢測到它的測試案例,讓每個測試案例在變綠之前至少失敗一次,接著快速撰寫簡單的程式讓這個測試通過 - 然後逐步重構這些程式到可以上 production 的水準,避免對環境 (如路徑或作業系統等) 有任何相依性。 + +
    + +❌ **否則:** 你會錯過這數十年來的智慧結晶 + +

    + +# 第 2 章:後端測試 + +## ⚪ ️2.1 豐富您的測試組合:不局限於單元測試和測試金字塔 + +:white_check_mark: **建議:** 雖然 [測試金字塔](https://martinfowler.com/bliki/TestPyramid.html) 已經有超過十年的歷史了,但他仍然是個很好的模型,他提出了三種測試類型,並影響了大多數開發者的測試策略。與此同時,大量閃亮的新測試技術出現了,並隱藏在測試金字塔的陰影下。考慮到近十年來我們所看到的所有巨變 (Microservices, cloud, serverless),這個非常老的模型是否仍能適用於所有類型的應用?測試界不應該考慮新的測試技術嗎? + +不要誤會,在 2019 年,測試金字塔、TDD、單元測試仍然是強大的技術,且對於大多數應用仍是最佳選擇。但是像其他模型一樣,儘管它有用,但是一定[會在某些時候出問題](https://en.wikipedia.org/wiki/All_models_are_wrong)。例如,我們有一個 IoT 的應用程式,將許多事件傳入一個 Kafka/RabbitMQ 這樣的 message-bus 中,然後這些事件流入資料庫並被經由 UI 來做查詢。我們真的需要花費 50% 的測試預算去為這個幾乎沒有邏輯的中心化的整合應用程式寫單元測試嗎?隨著應用類型 (bots, crypto, Alexa-skills) 的多樣增長,測試金字塔可能將不再是某些場景的最佳選擇了。 + +是時候豐富你的測試組合並了解更多的測試類型了(下一節會給你一些小建議),這些類似於測試金字塔的思維模型與你所面臨的現實問題會更加匹配("嘿,我們的 API 掛了,我們來寫 consumer-driven contract testing 吧!")。讓您的測試多樣化,比如建立基於風險分析的檢查模型 — 評估可能出現問題的地方,並提供一些預防措施以減輕這些潛在風險。 + +需要注意的是:軟體世界中的 TDD 模型面臨兩個極端的態度,一些人鼓吹到處使用它,另一些人則認為它是魔鬼。每個說絕對的人都是錯的 :] + +
    + +❌ **否則:** 你將錯過一些超高 CP 值的工具,比如 Fuzz、lint、mutation,這些工具只需 10 分鐘設定就能為你提供許多好處。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:Cindy Sridharan 在她的文章 "Testing Microservices — the sane way" 中提出了一個豐富的測試組合 + +![alt text](assets/bp-12-rich-testing.jpeg "Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the sane way’") + +☺️Example: [YouTube: “Beyond Unit Tests: 5 Shiny Node.JS Test Types (2018)” (Yoni Goldberg)](https://www.youtube.com/watch?v=-2zP494wdUY&feature=youtu.be) + +
    + +![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "A test name that constitutes 3 parts") + +
    + +

    + +## ⚪ ️2.2 組件化測試可能是最有效的利器 + +:white_check_mark: **建議:** 應用程式中的每個單元測試僅能覆蓋整個程式的一小部分,要覆蓋全部會非常麻煩,而端到端測試可以很輕鬆地覆蓋大量區域,但是比較脆弱而且很慢。何不找一個平衡點:寫一些比單元測試大,但是比端到端測試小的測試。組件測試是測試世界的一顆遺珠 — 它找到了兩個模式的最佳平衡點:不錯的性能和使用 TDD 模式的可能性與真實且強大的覆蓋率。 + +組件測試關注於微服務"單元",他們針對 API 來做事,不 mock 任何屬於微服務本身的東西(像是真實的 DB,甚至是該 DB 的 in-memory 版本)但是 stub 所有外部的東西,像是呼叫其他的微服務。藉由這種方式,我們可以測試我們部署的部分,由外而內地覆蓋應用程式,可以節省大量時間並獲得信心。 + +
    + +❌ **否則:** 你可能花了好幾天來寫單元測試,卻發現只得到了 20% 的覆蓋率。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:使用 Supertest 來測試 Express API (快速且覆蓋多個層次) + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + +![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) allows approaching Express API in-process (fast and cover many layers)") + +
    + +

    + +## ⚪ ️2.3 利用 contract tests 來確保新的 release 不會破壞 API 的使用 + +:white_check_mark: **建議:** 你的微服務有許多客戶,而你為了兼容性而運行著很多種版本 (keeping everyone happy)。當你改了某些程式後 "砰!",某些使用該服務的重要客戶生氣了。伺服端要滿足所有客戶的期望是非常困難的 - 另一方面,客戶端無法執行任何測試,因為 release 的日期是伺服端決定的。 + +[Consumer-driven contracts and the framework PACT](https://docs.pact.io/) 誕生了,它以一種破壞性的方式規範了這一流程 — 不再由伺服端定義測試計劃,而是客戶端決定伺服端的測試! PACT 可以記錄客戶端的期望並存放在一個共享的位置 — 中間人(Broker),伺服端可以 pull 下這些期望並利用 PACT 的函示庫在所有版本中檢測是否有被破壞的契約,也就是客戶端的期望沒有被滿足。通過這種方式,所有 伺服端-用戶端 沒對好的 API 將會在 build/CI 階段被發現,從而減少你的煩惱。 +
    + +❌ **否則:** 所有的變更都將會造成繁瑣的人工測試,導致開發者害怕部屬 + +
    + +
    程式範例 + +
    + +### :clap: 正例: + +![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") + +![alt text](assets/bp-14-testing-best-practices-contract-flow.png) + +
    + +

    + +## ⚪ ️2.4 單獨測試你的 middlewares + +:white_check_mark: **建議:** 許多人拒絕測試 middleware,因為它們只佔系統的一小部分而且相依於真實的 Express server。這兩個原因都不正確 — middleware 雖然小,但是影響著所有或至少大部分請求,而且可以被簡單地作為純函數測試 (參數為 ```{req,res}``` 的 JavaScript 物件)。要測試 middleware 函數,只需要呼叫它,並且監看 ([如使用 Sinon](https://www.npmjs.com/package/sinon)) 與 ```{req,res}``` 的互動來確保函數有執行正確的行為。 [node-mock-http](https://www.npmjs.com/package/node-mocks-http) 函示庫則更進一步:它還監聽了 ```{req,res}``` 物件的行為。例如,它可以斷言 res 物件上的 http 狀態是否符合預期。(看下面的程式範例) +
    + +❌ **否則:** Express middlewares 的 bug === 所有或大部分 request 的 bug + +
    + +
    程式範例 + +
    + +### :clap: 正例:單獨測試 middleware,不發出網路請求或啟動整個 Express 伺服器 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + +```javascript +// the middleware we want to test +const unitUnderTest = require("./middleware"); +const httpMocks = require("node-mocks-http"); +// Jest syntax, equivelant to describe() & it() in Mocha +test("A request without authentication header, should return http status 403", () => { + const request = httpMocks.createRequest({ + method: "GET", + url: "/user/42", + headers: { + authentication: "" + } + }); + const response = httpMocks.createResponse(); + unitUnderTest(request, response); + expect(response.statusCode).toBe(403); +}); +``` + +
    + +

    + +## ⚪ ️2.5 使用靜態分析工具來測量與重構 + +:white_check_mark: **建議:** 使用靜態分析工具可以幫助你客觀地提升程式品質並保持可維護性。你可以將靜態分析工具放在你的 CI 中。除了普通的 linting 外,它的主要賣點是查看多個檔案的上下文來檢查程式碼品質 (例如:發現程式有沒有重複定義的地方)、執行進階的分析 (例如:程式複雜度) 以及追蹤 code issue 的歷史和進度。有兩個工具供你使用:[SonarQube](https://www.sonarqube.org/) (6,300+ [stars](https://github.com/SonarSource/sonarqube)) 和 [Code Climate](https://codeclimate.com/) (2,300+ [stars](https://github.com/codeclimate/codeclimate))。 + +Credit: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + +❌ **否則;** 程式碼的品質過差,再新的函式庫或功能都無法拯救你的 bug 和性能 + +
    + +
    程式範例 + +
    + +### :clap: 正例:CodeClimate,一個可以發現複雜方法的商業工具 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg "Examples with CodeClimate") + +![alt text](assets/bp-16-yoni-goldberg-quality.png "CodeClimate, a commercial tool that can identify complex methods:") + +
    + +

    + +## ⚪ ️ 2.6 檢查你對 Node 相關渾沌的準備工作 + +:white_check_mark: **建議:** 奇怪的是,大部分的軟體測試都只專注在邏輯和資料層面,但最重要且很難被緩解的,是那些基礎設施的問題。例如,你有測試過當你的程序記憶體過載、伺服器或程序死掉時的表現嗎?或者你的監控系統可以檢測到 API 的回應時間慢了 50% 嗎?為了測試與減輕類似的問題,Netflix 設立了混沌工程 [Chaos engineering](https://principlesofchaos.org/)。它的目的是提供意識、框架及工具來測試我們的應用程式對於混沌問題的彈性。比如,最著名的工具之一,渾沌猴子 [the chaos monkey](https://github.com/Netflix/chaosmonkey),他會隨機地殺掉服務以確保我們的服務仍然可以提供服務給客戶,而不是僅依賴一個單獨的伺服器 (Kubernetes 也有一個 [kube-monkey](https://github.com/asobti/kube-monkey) 用來殺掉 pods)。這些工具都是作用在伺服器/平台面,但如果你想測試及產生單純的 Node 渾沌,比如檢查你的 Node 程序如何處理未知錯誤、未知的 promise rejection、v8 使用的記憶體超過 1.7GB 的限制以及當 event loop 卡住後你的 UX 是否仍然可以正常運行?為了解決上面提到的問題, [node-chaos](https://github.com/i0natan/node-chaos-monkey) 提供了各種 Node 相關的渾沌。 + +
    + +❌ **否則:** 莫非定律一定會擊中你的產品,無可避免的 + +
    + +
    程式範例 + +
    + +### :clap: 正例:Node-chaos 可以產生所有類型的 Node.js 問題,因此您可以測試您的應用程序對於渾沌的適應能力 + +![alt text](assets/bp-17-yoni-goldberg-chaos-monkey-nodejs.png "Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to chaos") + +
    + +

    + +# 第 3 章:前端測試 + +## ⚪ ️ 3.1 將 UI 與功能分離 + +:white_check_mark: **建議:** 當專注於測試組件邏輯時,UI 的細節就變成了應該被剔除的雜音,這樣您的測試目標就可以集中在資料面上。實際上,提取出程式中所需的資料,將降低與畫面的耦合,僅對單純的資料 (與 HTML/CSS 等圖形細節相比) 進行斷言,並停用會拖慢速度的動畫。您應該要試著避免畫面的渲染,僅測試 UI 後面的部分 (例如,服務、動作、存儲),但這將導致測試與實際情況不相符,「正確的資料根本無法呈現在 UI 上」這種問題就無法發現。 + +
    + +❌ **否則:** 你的測試可能花了 10ms 就準備好資料,但因為一些無關緊要的花俏動畫,讓整個測試案例持續了 500ms。(100個測試 = 1分鐘) + +
    + +
    程式範例: + +
    + +### :clap: 正例;分離 UI 的細節 + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +test("When users-list is flagged to show only VIP, should display only VIP members", () => { + // Arrange + const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; + + // Act + const { getAllByTestId } = render(); + + // Assert - Extract the data from the UI first + const allRenderedUsers = getAllByTestId("user").map(uiElement => uiElement.textContent); + const allRealVIPUsers = allUsers.filter(user => user.vip).map(user => user.name); + expect(allRenderedUsers).toEqual(allRealVIPUsers); // compare data with data, no UI here +}); +``` + +
    + +### :thumbsdown: 反例:混雜了 UI 與資料的斷言 + +```javascript +test("When flagging to show only VIP, should display only VIP members", () => { + // Arrange + const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; + + // Act + const { getAllByTestId } = render(); + + // Assert - Mix UI & data in assertion + expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); +}); +``` + +
    + +

    + +## ⚪ ️ 3.2 使用不易改變的屬性來查询 HTML 元素 + +:white_check_mark: **建議:** 使用不太容易受畫面變更而影響的屬性來查詢 HTML 元素 (例如 form label,而不是 CSS selector)。如果指定的元素沒有這樣的屬性,則創建一個專用的測試屬性,如 `test-id-submit-button`。這樣做不僅可以確保您的功能/邏輯測試不會因為外觀變化而中斷,而且整個團隊可以清楚地看到,測試案例使用了這個元素和屬性,不應該刪除它。 + +
    + +❌ **否則:** 假設你想要測試一個跨越許多組件、邏輯和服務的登入功能,一切都設置得很完美 - stub、spy、Ajax 的呼叫都是隔離的。看似一切都很完美,但卻發現測試失敗了,因為開發者將 div 的 class 從 `thick-border` 改為 `thin-border`。 + +
    + +
    程式範例 + +
    + +### :clap: 正例: 使用專用的 attribute 來查詢元素來進行測試 + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") + +```html +// the markup code (part of React component) +

    + + {value} + + +

    +``` + +```javascript +// this example is using react-testing-library +test("Whenever no data is passed to metric, show 0 as default", () => { + // Arrange + const metricValue = undefined; + + // Act + const { getByTestId } = render(); + + expect(getByTestId("errorsLabel").text()).toBe("0"); +}); +``` + +
    + +### :thumbsdown: 反例: 依靠於 CSS attributes + +```html + +{value} + +``` + +```javascript +// this exammple is using enzyme +test("Whenever no data is passed, error metric shows zero", () => { + // ... + + expect(wrapper.find("[className='d-flex-column']").text()).toBe("0"); +}); +``` + +
    + +
    + +## ⚪ ️ 3.3 如果可以,使用真實且完全渲染的組件來進行測試 + +:white_check_mark: **建議:** 只要尺寸合適,像使用者那樣從外部測試你的組件,完全渲染 UI,對其進行操作,並斷言對那些 UI 的行為是否符合預期。避免各種 mock、partial 和 shallow rendering - 這樣做可能會因為缺乏細節而導致有未捕捉到的 bug,而且由於測試會擾亂內部的結構而使得維護變得更加困難 (參考 堅持黑箱測試)。如果其中一個子組件明顯拖慢速度 (如 動畫) 或很難去設定,可以考慮使用假的組件去替換它。 + +綜上所說,需要注意的是:這種技術適用於包含合理大小子組件的中小型組件。完全渲染一個有太多子組件的組件會讓他很難被看出失敗的原因 (root cause analysis),而且可能會非常慢。在這種情況下,可以對那些很肥的父組件撰寫少量的測試,並對其子組件多寫幾個測試。 + +
    + +❌ **否則:** 呼叫組件的私有方法來測試組件的內部狀態。後續重構組件時你必須重構所有測試。你真的有能力進行這種程度的維護嗎? + +
    + +
    程式範例 + +
    + +### :clap: 正例: 操作一個充分渲染的真實組件 + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Enzyme-blue.svg "Examples with Enzyme") + +```javascript +class Calendar extends React.Component { + static defaultProps = { showFilters: false }; + + render() { + return ( +
    + A filters panel with a button to hide/show filters + +
    + ); + } +} + +// Examples use React & Enzyme +test("Realistic approach: When clicked to show filters, filters are displayed", () => { + // Arrange + const wrapper = mount(); + + // Act + wrapper.find("button").simulate("click"); + + // Assert + expect(wrapper.text().includes("Choose Filter")); + // This is how the user will approach this element: by text +}); +``` + +### :thumbsdown: 反例: 使用 shallow rendering 來測試 + +```javascript +test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { + // Arrange + const wrapper = shallow(); + + // Act + wrapper + .find("filtersPanel") + .instance() + .showFilters(); + // Tap into the internals, bypass the UI and invoke a method. White-box approach + + // Assert + expect(wrapper.find("Filter").props()).toEqual({ title: "Choose Filter" }); + // what if we change the prop name or don't pass anything relevant? +}); +``` + +
    + +
    + +## ⚪ ️ 3.4 不要 sleep,善用框架內建對非同步事件的支援,並試著加速他 + +:white_check_mark: **建議:** 在許多情況下,被測試單元的完成時間是未知的 (例如,因為動畫而延遲了元件的出現) — 在這種情況下,不要 sleep (例如使用 setTimeout),而是使用大多數框架提供的更靠譜的方法。一些函示庫允許等待操作 (例如 [Cypress .request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)),另一些函示庫提供用於等待的 API,如 [@testing-library/dom 的方法 wait(expect(element))](https://testing-library.com/docs/guide-disappearance)。有時後,更優雅的方法是 stub 那些比較慢的資源,像是 API,然後一旦響應時間變得確定,組件就可以顯式地重新渲染。當依賴一些 sleep 的外部組件時,[加快時鐘的速度](https://jestjs.io/docs/en/timer-mocks)或許能提供幫助。 sleep 是一種需要避免的模式,因為它會導致你的測試變得緩慢或有風險(如果等待的時間太短)。當 sleep 和輪詢不可避免且測試框架原生不支持時,一些 npm 的函示庫 (如 [wait-for-expect](https://www.npmjs.com/package/wait-for-expect)) 可以幫助解決半確定性問題。 + +
    + +❌ **否則:** 當 sleep 的時間太長時,測試速度會慢上一個數量級。當嘗試縮短 sleep 時間時,如果被測試的單元沒有及時響應,測試將會失敗。這時你不得不在脆弱的測試和糟糕的性能之間進行權衡。 + +
    + +
    程式範例 + +
    + +### :clap: 正例: E2E API 在非同步的處理完後 resolves (Cypress) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +// using Cypress +cy.get("#show-products").click(); // navigate +cy.wait("@products"); // wait for route to appear +// this line will get executed only when the route is ready +``` + +### :clap: 正例:測試的函示庫等待 DOM 元素 + +```javascript +// @testing-library/dom +test("movie title appears", async () => { + // element is initially not present... + + // wait for appearance + await wait(() => { + expect(getByText("the lion king")).toBeInTheDocument(); + }); + + // wait for appearance and return the element + const movie = await waitForElement(() => getByText("the lion king")); +}); +``` + +### :thumbsdown: 反例: 自製的 sleep 程式 + +```javascript +test("movie title appears", async () => { + // element is initially not present... + + // custom wait logic (caution: simplistic, no timeout) + const interval = setInterval(() => { + const found = getByText("the lion king"); + if (found) { + clearInterval(interval); + expect(getByText("the lion king")).toBeInTheDocument(); + } + }, 100); + + // wait for appearance and return the element + const movie = await waitForElement(() => getByText("the lion king")); +}); +``` + +
    + +
    + +## ⚪ ️ 3.5 觀察資源經由網路被提供的情況 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") + +✅ **建議:** 使用一些活動監視器,以確保在真實網路下的頁面載入情況是最佳的 — 這包含一些使用者體驗的問題:像是緩慢的頁面載入時間或未經壓縮的資源。市面上有很豐富的檢查工具:像 [pingdom](https://www.pingdom.com/)、AWS CloudWatch、[GCP StackDriver](https://cloud.google.com/monitoring/uptime-checks/) 這些工具可以很容易地監視伺服器是否正常運作著,是否有在合理的 SLA 下回應。不過這只解決了表面上的問題,最好選擇前端專用的工具 (如 [lighthouse](https://developers.google.com/web/tools/lighthouse/)、[pagespeed](https://developers.google.com/speed/pagespeed/insights/)) 來進行更全面的分析。並聚焦在那些直接影響使用者體驗的指標上,像是頁面載入時間、[有意義的繪製](https://scotch.io/courses/10-web-performance-audit-tips-for-your-next-billion-users-in-2018/fmp-first-meaningful-paint)、[頁面可互動時間(TTI)](https://calibreapp.com/blog/time-to-interactive/)。更重要的是,還可以關注其他原因,像是確保內容有被壓縮、[第一個 byte 的時間](https://developer.mozilla.org/en-US/docs/Glossary/time_to_first_byte)、圖片的最佳化、並確保合理的 DOM 尺寸、SSL 或其他。建議在開發期間將這些監視器納入 CI 的一部分,以及更重要的,在 24x7 的 production 伺服器/ CDN 上使用它們。 + +
    + +❌ **否則:** 設計了一個精美的 UI、且通過了 100% 的功能測試與精心的包裝,使用者體驗卻因為 CDN 的錯誤設定而變得糟糕及緩慢。 + +
    + +
    程式範例 + +### :clap: 正例:Lighthouse 的頁面載入檢測報告 + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + +
    + +
    + +## ⚪ ️ 3.6 stub 那些不穩定或緩慢的資源如後端 API + +:white_check_mark: **建議:** 當撰寫你主要的測試 (不是 E2E 測試) 時,避免接觸任何超出你職責和控制範圍的資源,如後端 API,而是使用 stub (測試替身)。使用一些測試替身的函式庫 (如 [Sinon](https://sinonjs.org/)、[Test doubles](https://www.npmjs.com/package/testdouble) 等) 來 stub API 的回應,而不是真正的對 API 進行呼叫。最大的好處是防止出現故障 — 測試或 API 的定義常常在變動的時候,儘管組件的表現正確 (生產環境不適合進行測試,它通常對 API 的呼叫進行限制),但有時會呼叫失敗。通過 stub 來模擬各種 API 行為,比如當沒有找到資料或 API 拋出錯誤時測試組件行為。最後但並非最不重要的原因是,經過網絡的呼叫將會大大降低執行測試的速度。 + +
    + +❌ **否則:** 平均執行測試的時間不再只是幾毫秒而已,一個普通的 API 呼叫至少需要 100 毫秒,這會讓你的測試慢 20 倍以上。 + +
    + +
    程式範例 + +
    + +### :clap: 正例: Stub 或攔截 API 的呼叫 + +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") + +```javascript +// unit under test +export default function ProductsList() { + const [products, setProducts] = useState(false); + + const fetchProducts = async () => { + const products = await axios.get("api/products"); + setProducts(products); + }; + + useEffect(() => { + fetchProducts(); + }, []); + + return products ?
    {products}
    :
    No products
    ; +} + +// test +test("When no products exist, show the appropriate message", () => { + // Arrange + nock("api") + .get(`/products`) + .reply(404); + + // Act + const { getByTestId } = render(); + + // Assert + expect(getByTestId("no-products-message")).toBeTruthy(); +}); +``` + +
    + +
    + +## ⚪ ️ 3.7 寫幾個跨越整個系統的 E2E 測試 + +:white_check_mark: **建議:** 雖然 E2E (end to end,端到端) 通常代表在真實瀏覽器中進行 UI 測試 (參考 3.6 節),某些情況下,它們表示覆蓋整個系統的測試,包括連接真正的後端。後者的測試非常有價值,因為它們涵蓋那些前端和後端之間整合的問題,這些問題可能是由於溝通上,schema 產生誤會所導致。它們也是一種有效的方法來發現 backend-to-backend 的整合問題 (例如微服務 A 將錯誤的訊息發送給微服務 B) 甚至可以檢測出部署上的錯誤,目前後端沒有像前端 UI 測試工具如 [Cypress](https://www.cypress.io/) 或 [Puppeteer](https://github.com/GoogleChrome/puppeteer) 一樣友善且成熟的 E2E 框架。這種測試的缺點是,設定涵蓋這麼多組件的環境的成本很高,而且大多數組件都很脆弱 — 假設有 50 個微服務,只要其中一個死掉,整個 E2E 就會失敗。基於這個原因,我們應該少用這種技術,大概 1-10 個就夠了。也就是說,即使是少量的 E2E 測試也有機會捕獲它們 — 部署或整合的問題。建議在與生產環境相似的 stage 運行它們。 + +
    + +❌ **否則:** UI 可能在功能測試上花費了大量的精力,但最後才發現後端回傳的內容 (UI 要使用的資料格式) 與預期中的不一樣。 + +
    + +## ⚪ ️ 3.8 藉由重複使用登入憑證來加速 E2E 測試 + +white_check_mark: **建議:** 在涉及真實的後端並必須使用有效的使用者 token 進行 API 呼叫的 E2E 測試中,我們沒有必要將每個測試都從「新增使用者並登錄」開始。相反的,在測試執行開始之前只登錄一次 (使用 before-all hook),將 token 儲存在本地端中,並在每個 request 之間重複使用它。雖然這似乎違反了測試的核心原則之一 — 保持測試的獨立性,不要耦合資源。這是一個合理的擔憂,但在 E2E 測試中,執行測試的性能是一個關鍵問題,在執行每個測試案例之前呼叫 1-3 個 API 可能會大大增加執行時間。重複使用憑證並不意味著測試必須基於相同的使用者資料 — 如果相依於使用者資料 (例如測試使用者付款的歷史記錄),那麼要確保產生這些資料來作為測試的一部分,並避免與其他測試共享它們。還要記住,後端是可以 fake 的 — 如果你的重點是測試前端,那麼最好隔離它,然後 stub 後端 API (參考 3.6 節)。 + +
    + +❌ **否則:** 給定 200 個測試案例,假設登錄需要花費的時間為 100ms,則至少需要花費 20s,在這一遍遍的登錄上。 + +
    + +
    程式範例 + +
    + +### :clap: 正例: 在 before-all 中登錄,而不是 before-each + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +let authenticationToken; + +// happens before ALL tests run +before(() => { + cy.request('POST', 'http://localhost:3000/login', { + username: Cypress.env('username'), + password: Cypress.env('password'), + }) + .its('body') + .then((responseFromLogin) => { + authenticationToken = responseFromLogin.token; + }) +}) + +// happens before EACH test +beforeEach(setUser => () { + cy.visit('/home', { + onBeforeLoad (win) { + win.localStorage.setItem('token', JSON.stringify(authenticationToken)) + }, + }) +}) + +``` + +
    + +
    + +## ⚪ ️ 3.9 寫一個走過整個網站的 E2E 冒煙測試 + +:white_check_mark: **建議:** 為了 production 環境的監控及開發時期的完整性檢查,執行一個 E2E 測試,讓這個測試走訪過所有或大多數的網站頁面,並確保那些頁面沒有損毀。這類型的測試投資報酬率很高,因為他很容易去撰寫及維護,卻可以檢測出各種類型的故障,包括功能性、網路或佈屬的問題。其他類型的冒煙測試或完整性檢查並沒有那麼可靠及詳盡 - 有些 ops 團隊只是 ping 網站首頁 (在production環境),或開發人員執行了一些整合測試,卻沒發現到打包或瀏覽器的問題。毫無疑問的,冒煙測試並不會取代功能測試,而只是作為一個快速的煙霧偵測器。 + +
    + +❌ **否則:** 一切看似很完美,所有的測試都通過了,在 production 環境的健康狀態檢查也是 ok 的,但 Payment 這個組件有一些打包的問題,導致 /Paymout 這個路徑沒有被渲染。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:一個跑過所有頁面的冒煙測試 + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +it("When doing smoke testing over all page, should load them all successfully", () => { + // exemplified using Cypress but can be implemented easily + // using any E2E suite + cy.visit("https://mysite.com/home"); + cy.contains("Home"); + cy.visit("https://mysite.com/Login"); + cy.contains("Login"); + cy.visit("https://mysite.com/About"); + cy.contains("About"); +}); +``` + +
    + +
    + +## ⚪ ️ 3.10 將測試作為一個活的協作文件來看待 + +:white_check_mark: **建議:** 除了提升應用程式的可靠性,測試還有一個非常誘人的應用 - 作為活的程式文件。由於測試程式本質上使用的是一種技術含量較低的產品/ UX 語言,因此使用正確的工具可以將他們轉換成一種易於溝通的媒介,方便開發人員與他們的使用者進行協調。舉例來說,有一些框架可以使用人類可閱讀的語言來表達流程與期望 (如,測試計畫),這樣一來,所有相關人員包括產品經理,都可以對測試進行閱讀、批准以及協作,如此一來.這個測試就成了活的需求文件。這樣的技術也被稱作 "驗收測試",因為它可以讓使用者用簡單的語言定義驗收標準。這是最純粹的 [BDD (行為驅動測試)](https://en.wikipedia.org/wiki/Behavior-driven_development),其中一個支援這個功能的框架是 [Cucumber](https://github.com/cucumber/cucumber-js),可以參考下面的程式範例。另一個相似但不同應用情境的是 [StoryBook](https://storybook.js.org/),它可以把 UI 的組件弄成圖形化的目錄,讓使用者可以瀏覽每個組件的各種狀態 (例如一個 grid w/o filter,讓他畫出多個 row 或沒有 row 等。),看他長得怎樣以及如何去觸發他的不同狀態 - 這也可以提供給產品相關人員,但主要是作為活的文件給使用這些組建的開發者們。 + +❌ **否則:** 在測試上已經耗費了大量的資源,如果不好好利用這項投資來獲取更大的價值,非常可惜。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:利用 cucumber-js 以人類可閱讀的語言來描述測試 + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") + +```javascript +// this is how one can describe tests using cucumber: plain language that allows anyone to understand and collaborate + +Feature: Twitter new tweet + + I want to tweet something in Twitter + + @focus + Scenario: Tweeting from the home page + Given I open Twitter home + Given I click on "New tweet" button + Given I type "Hello followers!" in the textbox + Given I click on "Submit" button + Then I see message "Tweet saved" + +``` + +### :clap: 正例:利用 Storybook 來展示組件的的不同狀態及輸入 + +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 使用自動化工作來偵測視覺問題 + +:white_check_mark: **建議:** 設定自動化工具,在出現變化的時候擷取 UI 畫面,並檢測是否有內容重疊或破圖等問題。這樣做不僅可以確保資料的正確性,使用者也可以很方便的看到他們。這樣的技術沒有被廣泛的使用,我們的測試思維比較傾向於功能測試,但這代表了真實的使用者體驗,而且可以輕易地發現像是會在多個設備上展示的 UI 問題。有些免費的工具可以提供一些基本的功能 - 產生或儲存螢幕截圖,讓肉眼可以檢查。雖然這種方法對於規模較小的應用程式已經足夠,但他的缺點就跟任何手動測試一樣 - 在任何變更後都需要人力來處理。另一方面,由於缺乏清楚的定義,自動檢測 UI 問題非常有挑戰性,視覺回歸 (Visual Regression) 解決這難題的方法是,比較舊的 UI 與最新版的的差異,並顯示檢測結果。一些開源/免費的工具可以提供這樣的能力 (例如,[wraith](https://github.com/BBC-News/wraith), [PhantomCSS](https://github.com/HuddleEng/PhantomCSS)),但他們的安裝比較耗時。一些商業工具 (例如,[Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) 則更進一步,他們簡化了安裝流程,並封裝了許多進階的功能,像是管理 UI、警告、藉由過濾 "視覺噪音"(如,廣告、動畫)來進行智慧抓取,甚至可以分析出造成 DOM/CSS 發生問題的根本原因。 + +
    + +❌ **否則:** 一個顯示內容且通過100%的功能測試的頁面,載入速度非常快,但有一半的內容都被隱藏了,這樣的頁面是好的嗎? + +
    + +
    程式範例 + +
    + +### :thumbsdown: 反例:一個典型的 visual regression,右側內容顯示異常 + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + +### :clap: 正例:設定 wraith 來抓取並比對 UI 截圖 + +![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") + +``` +​# Add as many domains as necessary. Key will act as a label​ + +domains: + english: "http://www.mysite.com"​ + +​# Type screen widths below, here are a couple of examples​ + +screen_widths: + + - 600​ + - 768​ + - 1024​ + - 1280​ + +​# Type page URL paths below, here are a couple of examples​ +paths: + about: + path: /about + selector: '.about'​ + subscribe: + selector: '.subscribe'​ + path: /subscribe +``` + +### :clap: 正例:使用 Applitools 來獲得截圖的比對結果以及其他進階功能 + +![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg "Using Applitools") ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") + +```javascript +import * as todoPage from "../page-objects/todo-page"; + +describe("visual validation", () => { + before(() => todoPage.navigate()); + beforeEach(() => cy.eyesOpen({ appName: "TAU TodoMVC" })); + afterEach(() => cy.eyesClose()); + + it("should look good", () => { + cy.eyesCheckWindow("empty todo list"); + todoPage.addTodo("Clean room"); + todoPage.addTodo("Learn javascript"); + cy.eyesCheckWindow("two todos"); + todoPage.toggleTodo(0); + cy.eyesCheckWindow("mark as completed"); + }); +}); +``` + +
    + +

    + +# 第 4 章:測量測試效果 + +

    + +## ⚪ ️ 4.1 藉由足夠的覆蓋率來獲得信心,~80% 看起來是個幸運數 + +:white_check_mark: **建議:** 測試的目的是為了得到足夠的信心去進行更快速的迭代,很顯然地,越多的程式被測試到,團隊會更為自信。測試覆蓋率是用來測量測試程式走過多少行 (或 branch, statement, ...)。那要多少才夠?10% ~ 30% 明顯無法證明專案的正確性,但 100% 則可能會過於浪費時間,而且可能會迫使你關注太多枝微末節的程式。答案是,需要參考很多因素並取決於應用程式的類型,如果你正在建立次世代的空中巴士 A380,那 100% 的覆蓋率是必須的;然而對於一個卡通圖片的網站來說,50% 的覆蓋率可能太高。雖然大部分的測試愛好者都說覆蓋率的最低門檻要依客觀因素來決定,但他們都提到,根據經驗 80% 是個不錯的數字。([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)),足夠滿足大多數的應用程式。 + +實作建議:你可能會想在 CI 工具中設定測試覆蓋率的門檻 ([Jest link](https://jestjs.io/docs/configuration#collectcoverage-boolean)),並中斷那些未達覆蓋率門檻的建置 (也可以為每個組件設定覆蓋率門檻,參考下面的程式範例)。另外,也可以監測建置的覆蓋率是否下降 (當有一個新的且覆蓋率較低的程式被 commit) — 這將促使開發者去提升或至少維持一定的測試數量。說了很多,但測試覆蓋率只是一個量化出來的數值,它並不能證明你的測試是強壯的。或許你也會被他騙到 (參考下一小節的內容)。 + +
    + +❌ **否則:** 自信與數字是相輔相成的,如果無法確保測試已經覆蓋了大部分的系統,你將感到害怕,且恐懼會使你變慢。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:一個典型的覆蓋率報告 + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: 正例:為每個組件設定覆蓋率 (使用 Jest) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") + +![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest)") + +
    + +

    + +## ⚪ ️ 4.2 檢查測試覆蓋率的報告來發現沒有被測試的區域或奇怪的地方 + +:white_check_mark: **建議:** 有些問題隱藏在雷達之下,使用傳統的工具很難發現到它們。它們通常不是真正的 bug,大多數情況下是應用程式的奇怪行為,而這些行為可能會造成嚴重影響。例如,一些程式區域幾乎不會或很少被呼叫 — 你以為 "PricingCalculator" 這個 class 只會設定產品價格,結果他幾乎不會被呼叫,即使我們的資料庫中有 10000 件商品以及很多筆交易…… 測試覆蓋率報告可以幫助你發現應用程式是否按照你的期望在執行。除此之外,它會 highlight 出哪些類型的程式沒有被測試到,80% 的程式被測試並不能代表程式中關鍵的部分有被覆蓋到。產生報告很簡單,只需要在執行測試的時候開啟覆蓋率追蹤的功能,然後讓那些花花綠綠的報告來告訴你每個程式區塊被呼叫的頻率。如果你花時間去看這些數據,你可能會發現一些問題。 + +
    + +❌ **否則:** 如果你不知道你的程式裡面有哪些地方沒有被測試到,你將無法知道問題的來源。 + +
    + +
    程式範例 + +
    + +### :thumbsdown: 反例:這個測試覆蓋率的報告出了什麼問題? + +基於一個真實世界的情境,我們在 QA 中追蹤了我們應用程式的使用情況,並發現了這個有趣的登錄模式 (提示:登入失敗的數量不成正比的,顯然是有問題的。最後發現,有一些前端的 bug 一直在打後端的登入 API) + +![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report?") + +
    + +

    + +## ⚪ ️ 4.3 使用「變異測試」測量邏輯覆蓋率 + +:white_check_mark: **建議:** 傳統的測試覆蓋率通常是騙人的,他可能會告訴你有 100% 的測試覆蓋率,但可能你的 function 都沒有回傳正確的值。為什麼會這樣?因為他只是很單純的測量你的測試程式走過哪幾行,而不會檢查測試案例到底測試了什麼,他到底有沒有確實去斷言正確的回應。就像有個人因公出差,他出示了他的護照,他無法證明他做了什麼工作,只能證明有去過哪幾個機場。 + +基於變異的測試,是透過測量"實際測試"的程式數量而不僅僅是"訪問"過的數量來提供協助。[Stryker](https://stryker-mutator.io/) 是一個用於進行變異測試的 JavaScript 函示庫,他的實作非常巧妙: + +(1) 他會刻意在你的程式中「植入 bug」。例如程式 `newOrder.price === 0` 會被改成 `newOrder.price != 0`,這個 "bug" 就稱為變異。 + +(2) 他會跑過一次測試,如果測試通過了代表有些問題,這些測試案例沒有達到發現 bug 的目的,導致這些變異活了下來。如果測試失敗了,非常好,那些變異就會被殺掉。 + +相較於傳統的測試覆蓋率,如果知道所有的變異都被殺死,會讓你更有自信,而且這兩者花費的時間差不多。 + +
    + +❌ **否則:** 你可能會誤以為 85% 的測試覆蓋率能發現程式中 85% 的 bug。 + +
    + +
    程式範例 + +
    + +### :thumbsdown: 反例: 100% coverage, 0% testing + +![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") + +```javascript +function addNewOrder(newOrder) { + logger.log(`Adding new order ${newOrder}`); + DB.save(newOrder); + Mailer.sendMail(newOrder.assignee, `A new order was places ${newOrder}`); + + return { approved: true }; +} + +it("Test addNewOrder, don't use such test names", () => { + addNewOrder({ assignee: "John@mailer.com", price: 120 }); +}); // Triggers 100% code coverage, but it doesn't check anything +``` + +
    + +### :clap: 正例:Stryker 的報告,一個變異測試的工具,偵測並統計沒有被測試到的程式 (變異) + +![alt text](assets/bp-20-yoni-goldberg-mutation-testing.jpeg "Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations)") + +
    + +

    + +## ⚪ ️4.4 使用 Test linter 來避免測試程式的問題 + +:white_check_mark: **建議:** 有一系列的 ESLint 外掛可以檢查測試程式的風格並發現問題。比如 [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) 會警告一個寫在 global 層的測試案例 (不是寫在 describe() 底下),或者當測試案例被 [skip](https://mochajs.org/#inclusive-tests) 時會發出警告,因為這可能會導致你誤會所有測試都通過了。類似的像,[eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) 可以在一個測試案例沒有任何斷言 (沒有檢查任何內容) 時給出警告。 + +
    + +❌ **否則:** 當你滿足於 90% 的測試覆蓋率或 100% 的綠色報告時,卻發現很多測試都沒什麼斷言,或是很多測試直接被 skip 掉了。但願你沒有把這份程式佈署出去。 + +
    +
    程式範例 + +
    + +### :thumbsdown: 反例:一個充滿錯誤的測試案例,還好都被 Linter 抓到了 + +```javascript +describe("Too short description", () => { + const userToken = userService.getDefaultToken() // *error:no-setup-in-describe, use hooks (sparingly) instead + it("Some description", () => {}); // *error: valid-test-description. Must include the word "Should" + at least 5 words +}); + +it.skip("Test name", () => { // *error:no-skipped-tests, error:error:no-global-tests. Put tests only under describe or suite + expect("somevalue"); // *error:no-assert +}); + +it("Test name", () => { // *error:no-identical-title. Assign unique titles to tests +}); +``` + +
    + +

    + +# 第 5 章:持續整合 (CI) 或其他提高品質的手段 + +

    + +## ⚪ ️ 5.1 豐富你的 linter 並捨棄有 linting 問題的建置 + +:white_check_mark: **建議:** 只需要 5 分鐘的設定,就可以免費得到自動保護程式碼的工具來偵測出程式中的問題。Linter 不再只是樣式工具,現在的 linter 可以抓到許多嚴重的問題,像是 error 沒有被正確的拋出或訊息的遺失。在基本的規則 (如 [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) 或 [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)) 之上,可以考慮加上一些特殊的 linter,像是 [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) 可以用來偵測測試案例有沒有寫斷言,[eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) 可以發現 Promise 有沒有 resolve (否則會導致你的程式不能繼續),[eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) 可以發現可能會導致 DOS 攻擊的正規表示式,還有 [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) 會在程式碼使用到 V8 的核心方法的時候給予警告,例如 Lodash.\_map(…)。 +
    + +❌ **否則:** 想像在某個雨天中,你的程式一直 crash,而且 log 沒有顯示 stack trace 的訊息。到底發生什麼事了?你的程式錯誤地拋出了一個非 error 的物件,而且 stack trace 都不見了,這會讓你想去撞牆。只要用 5 分鐘來設定 linter 就可以幫你偵測出這種 typo 錯誤,並拯救你一整天。 + +
    + +
    程式範例 + +
    + +### :thumbsdown: 反例:Error 物件被拋出,這樣的錯誤不會出現 stack trace。幸運的是,ESLint 抓到了這個 bug。 + +![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug") + +
    + +

    + +## ⚪ ️ 5.2 透過本地端的 CI 來縮短回饋循環 + +:white_check_mark: **建議:** 在本地端使用一個包含測試、Lint、穩定性檢查等功能的 CI,可以幫助開發者快速得到回饋並縮短 [回饋循環](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/)。因為一個有效的測試流程包含很多迭代循環 (1) 嘗試 -> (2) 回饋 -> (3) 重構。所以回饋越快,開發者可以在每個流程中可以執行的迭代就越多,且可以得到更好的結果。反過來,如果回饋來得很慢,一天只能執行很少個迭代,那團隊可能會因為急需執行下一個主題/任務/循環,而不再優化當前的循環。 + +目前有些 CI 的服務供應商 (如:[CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) 允許在本地端執行 CI pipeline。有些商業工具像是 [wallaby](https://wallabyjs.com/) 為開發提供了非常有用的測試功能。或者你可以在 package.json 中增加 npm script 來跑一些提升程式品質的指令 — 使用工具如 [concurrently](https://www.npmjs.com/package/concurrently) 來並行執行,並在任何工具執行失敗後拋出非 0 的結束碼。開發者只需執行一個指令(如 `npm run quality` )來快速獲取回饋。也可以用 githook 來取消沒有通過程式品質檢查的提交( [husky](https://github.com/typicode/husky) 可以幫助你)。 +
    + +❌ **否則** 如果品質檢查的結果在程式提交後第二天才收到,那測試就不算開發的一部分了。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:用來執行程式品質檢查的 npm script,在開發者主動觸發或嘗試提交新程式時執行。 + +```javascript +"scripts": { + "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", + "inspect:lint": "eslint .", + "inspect:vulnerabilities": "npm audit", + "inspect:license": "license-checker --failOn GPLv2", + "inspect:complexity": "plato .", + + "inspect:all": "concurrently -c \"bgBlue.bold,bgMagenta.bold,yellow\" \"npm:inspect:quick-testing\" \"npm:inspect:lint\" \"npm:inspect:vulnerabilities\" \"npm:inspect:license\"" + }, + "husky": { + "hooks": { + "precommit": "npm run inspect:all", + "prepush": "npm run inspect:all" + } +} + +``` + +
    + +

    + +## ⚪ ️5.3 在真正 production 的鏡像環境中執行 e2e 測試 + +:white_check_mark: **建議:** End to end (e2e) 測試是每個 CI pipeline 會面臨的大挑戰 - 即時創建一個與真正 production 環境相同的鏡像環境並擁有所有相關的服務,是很費時費力的。你需要找到適合的折衷點:[Docker-compose](https://serverless.com/) 藉由一個純文字檔將 docker 化的環境放在獨立的 container 中,但他背後使用的技術 (例如網路與佈署模型) 仍然與真實世界有所差異。可以將其與 [AWS Local](https://github.com/localstack/localstack) 整合,在真正的 AWS服務中做使用。如果你使用 [serverless](https://serverless.com/) 框架,[AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) 可以讓你在本地端調用 FaaS 程式碼。 + +龐大的 Kubernetes 生態系還沒有一個標準、方便的本地端、CI鏡像的工具,儘管現在已有許多工具的出現。有一種方法是使用像是 [Minikube](https://kubernetes.io/docs/setup/minikube/) 和 [MicroK8s](https://microk8s.io/) 這樣的工具來運行一個 "最小化的 Kubernetes",這些工具更貼近現實,且成本花費很小。另一種方法是在遠端的 "真實 Kubernetes" 環境上運行測試,一些 CI 的服務供應商 (如 [Codefresh](https://codefresh.io/)) 與 Kubernetes 環境擁有原生的整合,讓在 CI pipeline 上執行真實的環境變得更為容易。有的供應商則可以讓你針對遠端的 Kubernetes 自訂腳本。 +
    + +❌ **否則:** 在生產環境和測試環境中使用不同的技術,就會需要維護兩種佈署模型,會使開發人員與維運人員分開。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:動態產生 Kubernetes cluster 的 CI pipeline ([Credit: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) + +```yaml +deploy: +stage: deploy +image: registry.gitlab.com/gitlab-examples/kubernetes-deploy +script: +- ./configureCluster.sh $KUBE_CA_PEM_FILE $KUBE_URL $KUBE_TOKEN +- kubectl create ns $NAMESPACE +- kubectl create secret -n $NAMESPACE docker-registry gitlab-registry --docker-server="$CI_REGISTRY" --docker-username="$CI_REGISTRY_USER" --docker-password="$CI_REGISTRY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL" +- mkdir .generated +- echo "$CI_BUILD_REF_NAME-$CI_BUILD_REF" +- sed -e "s/TAG/$CI_BUILD_REF_NAME-$CI_BUILD_REF/g" templates/deals.yaml | tee ".generated/deals.yaml" +- kubectl apply --namespace $NAMESPACE -f .generated/deals.yaml +- kubectl apply --namespace $NAMESPACE -f templates/my-sock-shop.yaml +environment: +name: test-for-ci +``` + +
    + +

    + +## ⚪ ️5.4 並行測試工作 + +:white_check_mark: **建議:** 在合理的情況下,測試是你 24/7 的好朋友,他為你帶來即時的回饋。實際上,在單線程的 CPU 上執行 500 個單元測試可能會非常耗時。幸運的是,近代的測試執行器或 CI 平台 (如 [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) 或 [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) 可以將測試並行為多個程序來執行,藉此來大幅縮短回饋的時間。一些 CI 的廠商也支援跨容器並行測試,更進一步地縮短回饋的時間。無論是在本地端使用多個程序,或是在一些 cloud CLI 上使用多台機器來執行測試,並行化的重點是要保持測試的自主性,因為每個測試都可能在不同的程序上做執行。 + +❌ **否則:** 如果送出程式碼一個小時後才收到測試結果,但你已經在開發下一個功能了,這會導致測試對你來說變的不是那麼重要。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:藉由測試並行化,Mocha parallel 與 Jest 可以輕易的超越傳統 Mocha ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) + +![alt text](assets/bp-24-yonigoldberg-jest-parallel.png "Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization (Credit: JavaScript Test-Runners Benchmark)") + +
    + +

    + +## ⚪ ️5.5 使用 License 和抄襲檢查來避免法務上的問題 + +:white_check_mark: **建議:** License 和抄襲的問題或許不是你現在關注的點,但為什麼不在 10 分鐘內把這件工作設定好呢?許多 npm 的套件,像是 [license check](https://www.npmjs.com/package/license-checker) 和 [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (商業軟體,但有免費使用版本) 可以很容易的整合進你的 CI pipeline 中,並檢查那些像是使用限制性 license 或從 Stack Overflow 複製貼上明顯侵犯版權的程式。 + +❌ **否則:** 在不經意的情況下,開發人員可能會使用具有不適當 License 的套件,或將商業程式複製貼上,從而遇到法律上的問題。 + +
    + +
    程式範例 + +
    + +### :clap: 正例: + +```javascript +//install license-checker in your CI environment or also locally +npm install -g license-checker + +//ask it to scan all licenses and fail with exit code other than 0 if it found unauthorized license. The CI system should catch this failure and stop the build +license-checker --summary --failOn BSD + +``` + +
    + +![alt text](assets/bp-25-nodejs-licsense.png) + +
    + +

    + +## ⚪ ️5.6 持續檢查有漏洞的相依套件 + +:white_check_mark: **建議:** 即使是最有信譽的相依套件,如 Express,也有已知的漏洞。可以藉由使用社群工具 (如 [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit)) 或商業工具 (如 [snyk](https://snyk.io/) (也有免費版本)) 來輕鬆解決問題。可以在每次的建置中,透過 CI pipeline 調用他們。 + +❌ **否則:** 在沒有專用工具的情況下,要保持你的程式沒有漏洞,就需要不斷追蹤網路上新發佈的漏洞威脅資訊,這會相當令人乏味。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:NPM Audit 的結果 + +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + +

    + +## ⚪ ️5.7 自動升級相依套件 + +:white_check_mark: **建議:** Yarn 和 npm 的 package-lock.json 間接導入了一個嚴重的問題(本意是好的,但卻通往地獄)- 預設情況下,套件將不再得到更新。即使團隊使用 `npm install` 和 `npm update` 也不會獲得任何更新。會導致專案相依於不好的套件版本,或者最壞的情況是使用到容易被攻擊的程式。現在,團隊依靠開發人員的善意和記憶來手動更新 package.json 或手動使用像 [ncu]((https://www.npmjs.com/package/npm-check-updates)) 這樣的工具。然而更靠譜的方式是自動獲取可靠的相依套件版本,雖然沒有最佳的解決方案,但目前有兩種可能的自動化方式: + +(1) 使用 [npm outdated](https://docs.npmjs.com/cli/outdated) 或 npm-check-updates (ncu),當有過時的相依套件時,讓 CI 的建置失敗。這樣可以強制開發人員來更新相依套件。 + +(2) 使用商業工具,他們可以掃描程式並自動發送更新相依套件的 PR。剩下的有趣問題是相依套件的更新策略 — 若每個補丁都更新會產生太多的開銷,而大版本發佈時更新可能會指向一個不穩定的版本(許多套件在發佈後的幾天內被爆出漏洞,請參閱 [eslint-scope]((https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/)) 事件)。 + +有效的更新策略可能是允許一些 "容忍期" — 讓程式可以延後 @latest 一段時間和版本,再將本地端的副本視為過時(例如本地版本為 1.3.1 ,存儲庫版本為1.3.8)。 + +
    + +❌ **否則:** 你在 production 環境所使用的相依套件,可能已經被該作者標示為是有風險的。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:[ncu](https://www.npmjs.com/package/npm-check-updates) 可以手動或在 CI pipeline 中使用,以檢測程式落後最新版本多少。 + +![alt text](assets/bp-27-yoni-goldberg-npm.png "ncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions") + +
    + +

    + +## ⚪ ️ 5.8 其他,與 node 無關的 CI 建議 + +:white_check_mark: **建議:** 本文的重點是與 Node 有點關係的測試建議。但本節整理了一些眾所周知的與 Node 無關的技巧: + +1. 使用聲明式語法。這是大多數工具的唯一選擇,雖然舊版本的 Jenkins 允許使用程式或 UI。 +1. 選擇具有本地端 Docker 支援的工具。 +1. 盡快失敗,先執行最快的測試。設立一個"冒煙測試"的 step/milestone,對多個快速檢查工具(如 linting,單元測試)進行分組,為程式提交者提供快速回饋。 +1. 設法方便地瀏覽建置的所有產出,包括測試報告,覆蓋率報告,變異報告,log 等。 +1. 為每個事件創建多個 pipelines/jobs。例如,為 feature branch 的提交設定一個 job,為 master PR 設定另一個。 (大多數工具提供了一些程式重用的機制) +1. 永遠不要在 job 定義中加入機密信息,從 secret store 或 job 的設定中獲取。 +1. 在 release 中明確定義版本號。 +1. 僅建置一次,並對整個 build artifact(例如 Docker image)執行所有的檢查。 +1. 在一個臨時的環境中執行測試,在不同建置之間不會改變狀態。快取 node_modules 可能是唯一的例外。 + +
    + +❌ **否則:** 你會錯過多年的智慧結晶 + +

    + +## ⚪ ️ 5.9 建置模型(Matrix):使用多個 Node 版本執行同一個 CI 流程 + +:white_check_mark: **建議:** 品質的檢查是用於發現意外,測試覆蓋的部分越多,你就越可能儘早地發現問題。在開發會重複使用的套件或執行具有各種設定和 Node 版本的多用戶生產環境時,CI pipeline 必須在所有設定的組合上執行測試。例如,假設我們的某些客戶使用 MySQL,另一批客戶使用 Postgres。一些 CI 工具支持一種稱為"Matrix"的功能,該功能可以針對 MySQL、Postgres 或多個 Node 版本(如8、9、10)的所有組合執行測試。只要設定即可完成而無需任何額外工作。其他不支援 Matrix 的 CI 可能可以通過安裝外掛或一定程度的調整來實現這個功能。 + +
    + +❌ **否則:** 在做了那麼多辛苦的測試工作之後,怎麼能讓錯誤僅僅因為設定的問題而出現。 + +
    + +
    程式範例 + +
    + +### :clap: 正例:使用 Travis (CI 供應商) 的建置定義,在多個 Node 版本上執行相同的測試 + +
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    +
    + +

    + +# Team + +## Yoni Goldberg + +
    + +
    + +**Role:** Writer + +**About:** I'm an independent consultant who works with Fortune 500 companies and garage startups on polishing their JS & Node.js applications. More than any other topic I'm fascinated by and aims to master the art of testing. I'm also the author of [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices) + +**📗 Online Course:** Liked this guide and wish to take your testing skills to the extreme? Consider visiting my comprehensive course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +
    + +**Follow:** + +- [🐦 Twitter](https://twitter.com/goldbergyoni/) +- [📞 Contact](https://testjavascript.com/contact-2/) +- [✉️ Newsletter](https://testjavascript.com/newsletter//) + +
    +
    +
    + +## [Bruno Scheufler](https://github.com/BrunoScheufler) + +**Role:** Tech reviewer and advisor + +Took care to revise, improve, lint and polish all the texts + +**About:** full-stack web engineer, Node.js & GraphQL enthusiast + +
    +
    + +## [Ido Richter](https://github.com/idori) + +**Role:** Concept, design and great advice + +**About:** A savvy frontend developer, CSS expert and emojis freak + +## [Kyle Martin](https://github.com/js-kyle) + +**Role:** Helps keep this project running, and reviews security related practices + +**About:** Loves working on Node.js projects and web application security. diff --git a/readme.kr.md b/readme.kr.md index fa1822a9..f2f90843 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -21,7 +21,7 @@ JavaScript 및 Node.js에 대한 A부터 Z까지의 믿음직한 가이드입니 ### Yoni Goldberg 작성 * JavaScript & Node.js 컨설턴트 -* 👨‍🏫 [나의 테스팅 워크샵](https://www.testjavascript.com) - 유럽과 미국에서의 [제 워크샵](https://www.testjavascript.com)에 대해서 알아보십시오. +* 👨‍🏫 [나의 테스팅 워크샵](https://www.testjavascript.com) - 유럽과 미국에서의 [제 워크샵](https://www.testjavascript.com/)에 대해서 알아보십시오. * [트위터 팔로우 하기](https://twitter.com/goldbergyoni/) * [LA](https://js.la/), [베로나](https://2019.nodejsday.it/), [하르키우](https://kharkivjs.org/), [무료 웨비나](https://zoom.us/webinar/register/1015657064375/WN_Lzvnuv4oQJOYey2jXNqX6A)를 들으러 오십시오. 향후 이벤트는 곧 결정될 것입니다. * [저의 JavaScript 뉴스 레터](https://testjavascript.com/newsletter/) - 인사이트와 오직 전략적인 문제에 대한 내용 @@ -321,7 +321,7 @@ it("화이트박스 테스트: 내부 method가 VAT 0을 받으면 0을 반환 ```javascript it("유효한 제품을 삭제하려고 할 때, 올바른 제품과 올바른 구성 정보로 데이터 액세스 DAL을 한 번 호출했는지 확인한다", async () => { // 이미 제품을 추가했다고 가정 - const dataAccessMock = sinon.mock(DAL); + const dataAccessMock = sinon.mock(DAL); // 좋지 않음: 내부 테스트는 side-effect를 위해서가 주요 목적을 위해서 입니다. dataAccessMock.expects("deleteProduct").once().withArgs(DBConfig, theProductWeJustAdded, true, false); new ProductService().deletePrice(theProductWeJustAdded); @@ -448,7 +448,7 @@ describe("Product service", () => { :white_check_mark: **이렇게 해라:** [스냅샷 테스트](https://jestjs.io/docs/en/snapshot-testing)가 필요한 경우 외부 파일이 아닌 테스트의 일부 ([인라인 스냅샷](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots))에 포함 된 짧고 집중된 스냅샷(3~7 라인)만 사용하십시오. 이 지침을 따르면 따로 설명이 필요없고 잘 깨지지 않는 테스트가 됩니다. -반면에, '고전적인 스냅샷' 튜토리얼 및 도구는 외부에 큰 파일(예: 구성 요소 랜더링 마크업, API JSON 결과)를 저장하고, 테스트를 실행할 때 마다 수신된 결과를 저장된 버전과 비교하기를 권장합니다. 예를 들어, 이것은 1,000 라인(우리가 절대 읽지 않고 추론하지 않을 3,000개의 데이터 값을 가진)의 코드를 우리 테스트에 암시적으로 연결할 수 있습니다. 왜 이것이 잘못 되었을까요? 이렇게하면 테스트에 실패할 1,000 가지 이유가 생깁니다. 한줄만 변경되어도 스냅샷이 유효하지 않게 되고, 이런일이 일어날 가능성이 높습니다. 얼마나 자주? 모든 공백, 주석에서 혹은 사소한 CSS/HTML 변경에 대해서. 뿐만 아니라 테스트 이름은 1,000 라인이 변경되지 않았는지를 나타내기 때분에, 실패에 대한 단서를 제공하지 않습니다. 또한 테스트 작성자가 긴 문서(검사하고 확인할 수 없는)를 받아들이게끔 합니다. 이 모든 것은 초점이 맞지않고 너무 많은 것을 달성하려는 모호하고 간절한 테스트 증상입니다. +반면에, '고전적인 스냅샷' 튜토리얼 및 도구는 외부에 큰 파일(예: 구성 요소 랜더링 마크업, API JSON 결과)를 저장하고, 테스트를 실행할 때 마다 수신된 결과를 저장된 버전과 비교하기를 권장합니다. 예를 들어, 이것은 1,000 라인(우리가 절대 읽지 않고 추론하지 않을 3,000개의 데이터 값을 가진)의 코드를 우리 테스트에 암시적으로 연결할 수 있습니다. 왜 이것이 잘못 되었을까요? 이렇게하면 테스트에 실패할 1,000 가지 이유가 생깁니다. 한줄만 변경되어도 스냅샷이 유효하지 않게 되고, 이런일이 일어날 가능성이 높습니다. 얼마나 자주? 모든 공백, 주석에서 혹은 사소한 CSS/HTML 변경에 대해서. 뿐만 아니라 테스트 이름은 1,000 라인이 변경되지 않았는지를 나타내기 때분에, 실패에 대한 단서를 제공하지 않습니다. 또한 테스트 작성자가 긴 문서(검사하고 확인할 수 없는)를 받아들이게끔 합니다. 이 모든 것은 초점이 맞지않고 너무 많은 것을 달성하려는 모호하고 간절한 테스트 증상입니다. 긴 외부 스냅샷이 허용되는 경우가 거의 없다는 점은 주목할 가치가 있습니다 - 데이터가 아닌 스키마를 assert 할 때(값 추출 및 필드에 집중) 또는 수신된 문서가 거의 변경되지 않는 경우 @@ -468,20 +468,18 @@ describe("Product service", () => { "Examples with Jest") ```javascript -it('TestJavaScript.com 이 올바르게 랜더링 된다.', () => { - -//Arrange - -//Act -const receivedPage = renderer -.create( Test JavaScript < /DisplayPage>) -.toJSON(); - -//Assert -expect(receivedPage).toMatchSnapshot(); -// 이제 2,000 라인의 문서를 암묵적으로 유지합니다. -// 모든 줄바꿈 또는 주석이 테스트를 망가뜨립니다. - +it("TestJavaScript.com 이 올바르게 랜더링 된다.", () => { + //Arrange + + //Act + const receivedPage = renderer + .create( Test JavaScript ) + .toJSON(); + + //Assert + expect(receivedPage).toMatchSnapshot(); + // 이제 2,000 라인의 문서를 암묵적으로 유지합니다. + // 모든 줄바꿈 또는 주석이 테스트를 망가뜨립니다. }); ``` @@ -490,18 +488,18 @@ expect(receivedPage).toMatchSnapshot(); ### :clap: 올바른 예: expectation이 잘 보이고 집중된다. ```javascript -it('TestJavaScript.com 홈페이지를 방문하면 메뉴가 보인다.', () => { -//Arrange +it("TestJavaScript.com 홈페이지를 방문하면, 메뉴가 보인다.", () => { + //Arrange -//Act -receivedPage tree = renderer -.create( Test JavaScript < /DisplayPage>) -.toJSON(); + //Act + const receivedPage = renderer + .create( Test JavaScript ) + .toJSON(); -//Assert + //Assert -const menu = receivedPage.content.menu; -expect(menu).toMatchInlineSnapshot(` + const menu = receivedPage.content.menu; + expect(menu).toMatchInlineSnapshot(`