From 0dfc2b173216552d25da7be90ccf6bd8767426e1 Mon Sep 17 00:00:00 2001 From: Stefano Magni Date: Mon, 12 Aug 2019 15:18:57 +0200 Subject: [PATCH 001/502] Fix 1.10 chapter "otherwise" bold --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 9ff0db8f..d56c6d79 100644 --- a/readme.md +++ b/readme.md @@ -590,7 +590,7 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e
-❌ **Otherwise:**It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong
@@ -1912,4 +1912,4 @@ A5NDY3LDE1OTcyNDA3NzUsMjEwMzQzMDE2NiwtMzc1NjYzODQs LTEyODY1MzE2MDAsLTI5NzUwMjYyMyw0MzUxOTU4ODAsMTc2NT k2NzEzMCw3OTQ4ODg1MTcsLTE4MDA1NTUwMDYsOTM1MTI0ODc5 LDc3NTU2MTAxOSwtMjEwMzIxODMzM119 ---> \ No newline at end of file +--> From efb2742de5d26db452e3ef502842ed463e680a74 Mon Sep 17 00:00:00 2001 From: Stefano Magni Date: Mon, 12 Aug 2019 15:36:54 +0200 Subject: [PATCH 002/502] Fix Sinon link --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 9ff0db8f..05aa957c 100644 --- a/readme.md +++ b/readme.md @@ -1220,7 +1220,7 @@ test('movie title appears', async () => { ## ⚪ ️ 3.6 Stub flakky and slow resources like backend APIs -:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon]https://sinonjs.org/, [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests
@@ -1912,4 +1912,4 @@ A5NDY3LDE1OTcyNDA3NzUsMjEwMzQzMDE2NiwtMzc1NjYzODQs LTEyODY1MzE2MDAsLTI5NzUwMjYyMyw0MzUxOTU4ODAsMTc2NT k2NzEzMCw3OTQ4ODg1MTcsLTE4MDA1NTUwMDYsOTM1MTI0ODc5 LDc3NTU2MTAxOSwtMjEwMzIxODMzM119 ---> \ No newline at end of file +--> From 6d4852d8aea2ff895cbf3923f97f8a4a1cea5368 Mon Sep 17 00:00:00 2001 From: Stefano Magni Date: Mon, 12 Aug 2019 15:43:21 +0200 Subject: [PATCH 003/502] Fix the Percy name --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 9ff0db8f..b43b626e 100644 --- a/readme.md +++ b/readme.md @@ -1414,7 +1414,7 @@ Feature: Twitter new tweet ## ⚪ ️ 3.11 Detect visual issues with automated tools -:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS]([https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Perci.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue +:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS]([https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue
@@ -1912,4 +1912,4 @@ A5NDY3LDE1OTcyNDA3NzUsMjEwMzQzMDE2NiwtMzc1NjYzODQs LTEyODY1MzE2MDAsLTI5NzUwMjYyMyw0MzUxOTU4ODAsMTc2NT k2NzEzMCw3OTQ4ODg1MTcsLTE4MDA1NTUwMDYsOTM1MTI0ODc5 LDc3NTU2MTAxOSwtMjEwMzIxODMzM119 ---> \ No newline at end of file +--> From 6f4316cc766e57b60dd20b4ab69aaa165a786ec1 Mon Sep 17 00:00:00 2001 From: Stefano Magni Date: Mon, 12 Aug 2019 15:54:01 +0200 Subject: [PATCH 004/502] Align the "dependency updates" list to the "mutation testing" one --- readme.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 9ff0db8f..620892a0 100644 --- a/readme.md +++ b/readme.md @@ -1802,7 +1802,13 @@ license-checker --summary --failOn BSD

## ⚪ ️5.7 Automate dependency updates -:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: (1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. (2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: + +(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. + +(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). + +An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8)
@@ -1912,4 +1918,4 @@ A5NDY3LDE1OTcyNDA3NzUsMjEwMzQzMDE2NiwtMzc1NjYzODQs LTEyODY1MzE2MDAsLTI5NzUwMjYyMyw0MzUxOTU4ODAsMTc2NT k2NzEzMCw3OTQ4ODg1MTcsLTE4MDA1NTUwMDYsOTM1MTI0ODc5 LDc3NTU2MTAxOSwtMjEwMzIxODMzM119 ---> \ No newline at end of file +--> From ddc3970384fe146ac0c19518c3d2ab73f696aac6 Mon Sep 17 00:00:00 2001 From: Stefano Magni Date: Mon, 12 Aug 2019 15:56:41 +0200 Subject: [PATCH 005/502] Fix minor typos --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 9ff0db8f..deaaba67 100644 --- a/readme.md +++ b/readme.md @@ -1867,11 +1867,11 @@ license-checker --summary --failOn BSD **Role:** Writer -**About:** I'm an independent consultant who works with 500 fortune corporates 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) +**About:** I'm an independent consultant who works with 500 fortune corporates 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)
-**Workshop:** 👨‍🏫 Want to learn all these practices and techniques at your offices (Europe & USA)? [register here for my testing workshop](https://testjavascript.com/) +**Workshop:** 👨‍🏫 Want to learn all these practices and techniques at your offices (Europe & USA)? [Register here for my testing workshop](https://testjavascript.com/)
**Follow:** @@ -1912,4 +1912,4 @@ A5NDY3LDE1OTcyNDA3NzUsMjEwMzQzMDE2NiwtMzc1NjYzODQs LTEyODY1MzE2MDAsLTI5NzUwMjYyMyw0MzUxOTU4ODAsMTc2NT k2NzEzMCw3OTQ4ODg1MTcsLTE4MDA1NTUwMDYsOTM1MTI0ODc5 LDc3NTU2MTAxOSwtMjEwMzIxODMzM119 ---> \ No newline at end of file +--> From 346f5f4a306734a35ee6a676b5e765077b88e42a Mon Sep 17 00:00:00 2001 From: Yeoh Joer Date: Tue, 13 Aug 2019 00:11:10 +0800 Subject: [PATCH 006/502] Fix typo `strucuturing` --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 9ff0db8f..02d8000e 100644 --- a/readme.md +++ b/readme.md @@ -34,7 +34,7 @@ A single advice that inspires all the others (1 special bullet) * ### `Section 1: The Test Anatomy` -The foundation - strucuturing clean tests (12 bullets) +The foundation - structuring clean tests (12 bullets) * ### `Section 2: Backend` From 5928be78669b49c8935760797ea81d1cbaa6b6f6 Mon Sep 17 00:00:00 2001 From: Jhonny Moreira Date: Mon, 12 Aug 2019 13:26:29 -0300 Subject: [PATCH 007/502] Fix typos in section 1.5 anti pattern example --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 9ff0db8f..2559e60a 100644 --- a/readme.md +++ b/readme.md @@ -334,10 +334,10 @@ For example, if you want to test what your app behaves reasonably when the payme 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-effecr + //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); - mock.verify(); + dataAccessMock.verify(); }); ```
@@ -1912,4 +1912,4 @@ A5NDY3LDE1OTcyNDA3NzUsMjEwMzQzMDE2NiwtMzc1NjYzODQs LTEyODY1MzE2MDAsLTI5NzUwMjYyMyw0MzUxOTU4ODAsMTc2NT k2NzEzMCw3OTQ4ODg1MTcsLTE4MDA1NTUwMDYsOTM1MTI0ODc5 LDc3NTU2MTAxOSwtMjEwMzIxODMzM119 ---> \ No newline at end of file +--> From f1b0d1b93503a1fab709cdc8d5007fdb346348b6 Mon Sep 17 00:00:00 2001 From: Yeoh Joer Date: Tue, 13 Aug 2019 00:31:06 +0800 Subject: [PATCH 008/502] Remove extra hash signs in Heading 3 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 02d8000e..c8d0dd29 100644 --- a/readme.md +++ b/readme.md @@ -342,7 +342,7 @@ it("When a valid product is about to be deleted, ensure data access DAL was call ```
-### ### :clap:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals +### :clap:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals ```javascript it("When a valid product is about to be deleted, ensure an email is sent", async () => { From 22d2a076ecab103e160314a4c1cedebad7c0b0e0 Mon Sep 17 00:00:00 2001 From: Yeoh Joer Date: Tue, 13 Aug 2019 00:40:56 +0800 Subject: [PATCH 009/502] Fix typo `travells` --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index c8d0dd29..72ca76b6 100644 --- a/readme.md +++ b/readme.md @@ -11,7 +11,7 @@ This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and ## 🚢 Advanced: Goes 10,000 miles beyond the basics -Hop into a journey that travells way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average +Hop into a journey that travels way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average ## 🌐 Full-stack: front, backend, CI, anything @@ -1335,7 +1335,7 @@ beforeEach(setUser => () {
-## ⚪ ️ 3.9 Have one E2E smoke test that just travells across the site map +## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map :white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector From d90ac666d3d85770dfd1fc8dba5e32671a0ac007 Mon Sep 17 00:00:00 2001 From: Yeoh Joer Date: Tue, 13 Aug 2019 01:04:47 +0800 Subject: [PATCH 010/502] The character 'M' of MySQL is an upper case 'M' --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 9ff0db8f..6de4c870 100644 --- a/readme.md +++ b/readme.md @@ -1836,7 +1836,7 @@ license-checker --summary --failOn BSD

## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions -:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use mySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of mySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that
From 7b8d32d93621b3bdce9e0ea1ee1f25425e40ebd7 Mon Sep 17 00:00:00 2001 From: Ian Germann Date: Mon, 12 Aug 2019 16:35:23 -0400 Subject: [PATCH 011/502] Fix link to eslint-plugin-security The link for eslint-plugin-security was incorrectly pointing to the npm page for eslint-plugin-promise --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index a8a78351..e4add139 100644 --- a/readme.md +++ b/readme.md @@ -1627,7 +1627,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues -:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash._map(…) +:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash._map(…)
From 85d65379f3aff2011cab215a5019287df39fa16e Mon Sep 17 00:00:00 2001 From: Hafez Date: Wed, 14 Aug 2019 18:53:54 +0200 Subject: [PATCH 012/502] Fix typos --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index a8a78351..91df2eb7 100644 --- a/readme.md +++ b/readme.md @@ -11,7 +11,7 @@ This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and ## 🚢 Advanced: Goes 10,000 miles beyond the basics -Hop into a journey that travells way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average +Hop into a journey that travels way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average ## 🌐 Full-stack: front, backend, CI, anything @@ -54,7 +54,7 @@ Watching the watchman - measuring test quality (4 bullets) * ### `Section 5: Continous Integration` -Guideliness for CI in the JS world (9 bullets) +Guidelines for CI in the JS world (9 bullets)

@@ -361,7 +361,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

-## ⚪ ️1.6 Don’t “foo”, use realistic input dataing +## ⚪ ️1.6 Don’t “foo”, use realistic input data :white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not instead) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? see next bullet (property-based testing).
@@ -1337,7 +1337,7 @@ beforeEach(setUser => () {
-## ⚪ ️ 3.9 Have one E2E smoke test that just travells across the site map +## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map :white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector From ad5c4b029d1772a50f1cd8ef491a557700cb82cc Mon Sep 17 00:00:00 2001 From: Ruxandra Fediuc Date: Thu, 15 Aug 2019 08:34:25 -0700 Subject: [PATCH 013/502] fix(typo): example section 1.11 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0bec4ca5..73fca186 100644 --- a/readme.md +++ b/readme.md @@ -683,7 +683,7 @@ it.only("When no product name, it throws error 400", async() => { //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. Excpectation - Use the default currency #sanity', function() { + test('Scenario - no currency was supplied. Expectation - Use the default currency #sanity', function() { //code logic here }); }); From b4e4600ae279d9db27c9844e5cb2014d9ac07a58 Mon Sep 17 00:00:00 2001 From: Ruxandra Fediuc Date: Thu, 15 Aug 2019 08:41:08 -0700 Subject: [PATCH 014/502] fix(typo): section 2.5 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 73fca186..0982de33 100644 --- a/readme.md +++ b/readme.md @@ -874,7 +874,7 @@ Credit:: Date: Thu, 15 Aug 2019 08:58:33 -0700 Subject: [PATCH 015/502] fix(typo): section 3.10 --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 0982de33..c26cb0fc 100644 --- a/readme.md +++ b/readme.md @@ -1448,11 +1448,11 @@ it('When doing smoke testing over all page, should load them all successfully',
-### :clap: Doing It Right Example: Describing tests in human-language using cocumber-js +### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js -![](https://img.shields.io/badge/🔨%20Example%20using%20Cocumber-blue.svg "Examples using Cocumber") +![](https://img.shields.io/badge/🔨%20Example%20using%20Cocumber-blue.svg "Examples using Cucumber") ```javascript -// this is how one can describe tests using cocumber: plain language that allows anyone to understand and collaborate +// this is how one can describe tests using cucumber: plain language that allows anyone to understand and collaborate Feature: Twitter new tweet From 52af0af60eb5533854e3e4307dfe9768807c7090 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 16 Aug 2019 17:27:12 +0800 Subject: [PATCH 016/502] Update readme.md ref the AAA pattern --- readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 85a65646..523044b9 100644 --- a/readme.md +++ b/readme.md @@ -594,7 +594,9 @@ 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); }); @@ -2033,4 +2035,4 @@ I0MDc3NSwyMTAzNDMwMTY2LC0zNzU2NjM4NCwtMTI4NjUzMTYw MCwtMjk3NTAyNjIzLDQzNTE5NTg4MCwxNzY1OTY3MTMwLDc5ND g4ODUxNywtMTgwMDU1NTAwNiw5MzUxMjQ4NzksNzc1NTYxMDE5 XX0= ---> \ No newline at end of file +--> From 0858b8b8f35488ac080ca0f4cd2dfe3c5cef2477 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 16 Aug 2019 17:37:55 +0800 Subject: [PATCH 017/502] fix the typo --- readme.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 523044b9..2aaefd44 100644 --- a/readme.md +++ b/readme.md @@ -4,6 +4,8 @@ # 👇 Why this guide can take your testing skills to the next level + +
## 📗 45+ best practices: Super-comprehensive and exhaustive @@ -76,7 +78,9 @@ Our minds are full with the main production code, we don't have 'headspace' for The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells we have two brain systems: system 1 which is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should *feel* as easy as modifying an HTML document and not like solving 2X(17 × 24). -This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. +This can be achieved by selectively cherry-picking techniques, too + +ls and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. ![alt text](/assets/headspace.png "We have no head room for additional complexity") @@ -623,13 +627,17 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e
-### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch +### :thumbsdown: + + + + ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") ```javascript -/it("When no product name, it throws error 400", async() => { +it("When no product name, it throws error 400", async() => { let errorWeExceptFor = null; try { const result = await addNewProduct({name:'nest'});} From ffc699e147dc3d5fe2a2ecb651113b9f182afd79 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 16 Aug 2019 17:40:28 +0800 Subject: [PATCH 018/502] Update readme.md --- readme.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index 2aaefd44..90972560 100644 --- a/readme.md +++ b/readme.md @@ -4,8 +4,6 @@ # 👇 Why this guide can take your testing skills to the next level - -
## 📗 45+ best practices: Super-comprehensive and exhaustive @@ -78,9 +76,7 @@ Our minds are full with the main production code, we don't have 'headspace' for The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells we have two brain systems: system 1 which is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should *feel* as easy as modifying an HTML document and not like solving 2X(17 × 24). -This can be achieved by selectively cherry-picking techniques, too - -ls and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. +This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. ![alt text](/assets/headspace.png "We have no head room for additional complexity") @@ -627,11 +623,7 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e
-### :thumbsdown: - - - - +### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") From c26b50bec77be900d184ad3839d2b8ae62b08100 Mon Sep 17 00:00:00 2001 From: Peter Carrero Date: Fri, 16 Aug 2019 12:49:21 -0500 Subject: [PATCH 019/502] Fix typo "continuous" --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 85a65646..5d22b854 100644 --- a/readme.md +++ b/readme.md @@ -55,7 +55,7 @@ Writing tests for web UI including component and E2E tests (11 bullets) Watching the watchman - measuring test quality (4 bullets) -* ### `Section 5: Continous Integration` +* ### `Section 5: Continuous Integration` Guideliness for CI in the JS world (9 bullets) @@ -2033,4 +2033,4 @@ I0MDc3NSwyMTAzNDMwMTY2LC0zNzU2NjM4NCwtMTI4NjUzMTYw MCwtMjk3NTAyNjIzLDQzNTE5NTg4MCwxNzY1OTY3MTMwLDc5ND g4ODUxNywtMTgwMDU1NTAwNiw5MzUxMjQ4NzksNzc1NTYxMDE5 XX0= ---> \ No newline at end of file +--> From c03db84334b244db191ccff1febb4d240dca74af Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Fri, 16 Aug 2019 21:52:03 +0300 Subject: [PATCH 020/502] Update readme.md --- readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 85a65646..8906b658 100644 --- a/readme.md +++ b/readme.md @@ -2000,6 +2000,8 @@ license-checker --summary --failOn BSD * [🐦 Twitter](https://twitter.com/goldbergyoni/) * [📞 Contact](https://testjavascript.com/contact-2/) +* [✉️ Newsletter](https://testjavascript.com/newsletter//) +


@@ -2033,4 +2035,4 @@ I0MDc3NSwyMTAzNDMwMTY2LC0zNzU2NjM4NCwtMTI4NjUzMTYw MCwtMjk3NTAyNjIzLDQzNTE5NTg4MCwxNzY1OTY3MTMwLDc5ND g4ODUxNywtMTgwMDU1NTAwNiw5MzUxMjQ4NzksNzc1NTYxMDE5 XX0= ---> \ No newline at end of file +--> From cbd3f1803651fc8ea33926bba71da636eb28745b Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Fri, 16 Aug 2019 22:11:50 +0300 Subject: [PATCH 021/502] readme.md updated from https://stackedit.io/ --- readme.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/readme.md b/readme.md index 8906b658..d24c3283 100644 --- a/readme.md +++ b/readme.md @@ -21,7 +21,7 @@ Start by understanding the ubiquitous testing practices that are the foundation ### Written By Yoni Goldberg * A JavaScript & Node.js consultant -* [My testing workshop](https://www.testjavascript.com) - Learn all these testing practices and techniques live +* ## 👨‍🏫 [My testing workshop](https://www.testjavascript.com) - learn about [my workshops](https://www.testjavascript.com) in Europe & US * [Follow me on Twitter ](https://twitter.com/goldbergyoni/) * Come hear me speak at [LA](https://js.la/), [Verona](https://2019.nodejsday.it/), [Kharkiv](https://kharkivjs.org/), [free webinar](https://zoom.us/webinar/register/1015657064375/WN_Lzvnuv4oQJOYey2jXNqX6A). Future events TBD * [My JavaScript Quality newsletter](https://testjavascript.com/newsletter/) - insights and content only on strategic matters @@ -2028,11 +2028,11 @@ Took care to revise, improve, lint and polish all the texts +eyJoaXN0b3J5IjpbLTE3MDExMjkwNTMsMTgyMzc3OTkyMCwtMT +IyNTQ2MjQyMiwtNjI3MjIwMDEsMTEzMDI2NzQ0NiwxNTg1ODY1 +NjMyLDI5ODA3MzcwMyw1ODM1NDY0NjgsLTM0ODY5OTIxNyw3OT +Q4MDk0NjcsMTU5NzI0MDc3NSwyMTAzNDMwMTY2LC0zNzU2NjM4 +NCwtMTI4NjUzMTYwMCwtMjk3NTAyNjIzLDQzNTE5NTg4MCwxNz +Y1OTY3MTMwLDc5NDg4ODUxNywtMTgwMDU1NTAwNiw5MzUxMjQ4 +NzldfQ== +--> \ No newline at end of file From 09bc858c9dbd9e08e230dd83df9ff89852e49937 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Fri, 16 Aug 2019 22:12:20 +0300 Subject: [PATCH 022/502] readme.md updated from https://stackedit.io/ --- readme.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme.md b/readme.md index d24c3283..224dd156 100644 --- a/readme.md +++ b/readme.md @@ -21,7 +21,7 @@ Start by understanding the ubiquitous testing practices that are the foundation ### Written By Yoni Goldberg * A JavaScript & Node.js consultant -* ## 👨‍🏫 [My testing workshop](https://www.testjavascript.com) - learn about [my workshops](https://www.testjavascript.com) in Europe & US +* 👨‍🏫 [My testing workshop](https://www.testjavascript.com) - learn about [my workshops](https://www.testjavascript.com) in Europe & US * [Follow me on Twitter ](https://twitter.com/goldbergyoni/) * Come hear me speak at [LA](https://js.la/), [Verona](https://2019.nodejsday.it/), [Kharkiv](https://kharkivjs.org/), [free webinar](https://zoom.us/webinar/register/1015657064375/WN_Lzvnuv4oQJOYey2jXNqX6A). Future events TBD * [My JavaScript Quality newsletter](https://testjavascript.com/newsletter/) - insights and content only on strategic matters @@ -2028,11 +2028,11 @@ Took care to revise, improve, lint and polish all the texts \ No newline at end of file From 2b8638d11aeda7d900844b5648bca499d89db2b9 Mon Sep 17 00:00:00 2001 From: Huhgawz Date: Fri, 16 Aug 2019 19:32:00 -0500 Subject: [PATCH 023/502] Fix typo "strcutured" --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 224dd156..11504572 100644 --- a/readme.md +++ b/readme.md @@ -162,7 +162,7 @@ describe('Products Service', function() {
-### :clap: Doing It Right Example: A test strcutured with the AAA pattern +### :clap: Doing It Right Example: A test structured with the AAA pattern ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg @@ -2035,4 +2035,4 @@ gwOTQ2NywxNTk3MjQwNzc1LDIxMDM0MzAxNjYsLTM3NTY2Mzg0 LC0xMjg2NTMxNjAwLC0yOTc1MDI2MjMsNDM1MTk1ODgwLDE3Nj U5NjcxMzAsNzk0ODg4NTE3LC0xODAwNTU1MDA2LDkzNTEyNDg3 OV19 ---> \ No newline at end of file +--> From 0d0b52ca56683b1767f490476323d506aa700faf Mon Sep 17 00:00:00 2001 From: Huhgawz Date: Fri, 16 Aug 2019 19:54:48 -0500 Subject: [PATCH 024/502] Remove unnecessary require --- readme.md | 1 - 1 file changed, 1 deletion(-) diff --git a/readme.md b/readme.md index 11504572..80ec0f1d 100644 --- a/readme.md +++ b/readme.md @@ -456,7 +456,6 @@ it("Better: When adding new valid product, get successful confirmation", async ( ```javascript require('mocha-testcheck').install(); const {expect} = require('chai'); -const faker = require('faker'); describe('Product service', () => { describe('Adding new', () => { From 078ae5d511f7e420bb65a598202ab600c8a91e6b Mon Sep 17 00:00:00 2001 From: Huhgawz Date: Fri, 16 Aug 2019 20:27:28 -0500 Subject: [PATCH 025/502] Fix typo "prdoction' --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 80ec0f1d..01cd1c1c 100644 --- a/readme.md +++ b/readme.md @@ -701,7 +701,7 @@ describe('Order service', function() { ## ⚪ ️1.12 Other generic good testing hygiene :white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a prdoction grade level, avoid any dependency on the environment (paths, OS, etc) +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc)
From e5f51871b20f819241cbc349b6d98f1621cc22c1 Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 18 Aug 2019 08:47:51 +0800 Subject: [PATCH 026/502] Remove repeated content --- readme.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 224dd156..03cfdaf4 100644 --- a/readme.md +++ b/readme.md @@ -324,8 +324,6 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response", :white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide an immense value (
[Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). However, the various techniques were not born equal: some of them, spies and stubs, are focused on testing the requirements but as an inevitable side-effect they also slightly touch the internals. Mocks, on the contrary side, are focused on testing the internals — this brings huge overhead as explained in the bullet “Stick to black box testing”.

-However, the various techniques were not born equal: some of them, spies and stubs, are focused on testing the requirements but as an inevitable side-effect they also slightly touch the internals. Mocks, on the contrary side, are focused on testing the internals — this brings huge overhead as explained in the bullet “Stick to black box testing”. - Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a smell of white-box testing. For example, if you want to test what your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently @@ -2035,4 +2033,4 @@ gwOTQ2NywxNTk3MjQwNzc1LDIxMDM0MzAxNjYsLTM3NTY2Mzg0 LC0xMjg2NTMxNjAwLC0yOTc1MDI2MjMsNDM1MTk1ODgwLDE3Nj U5NjcxMzAsNzk0ODg4NTE3LC0xODAwNTU1MDA2LDkzNTEyNDg3 OV19 ---> \ No newline at end of file +--> From 0020ea29950c9bb29cffde5fbb9a798d8fa10cc3 Mon Sep 17 00:00:00 2001 From: Adrien REDON Date: Mon, 19 Aug 2019 08:58:42 +0200 Subject: [PATCH 027/502] Fix typo --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 224dd156..3f222236 100644 --- a/readme.md +++ b/readme.md @@ -55,9 +55,9 @@ Writing tests for web UI including component and E2E tests (11 bullets) Watching the watchman - measuring test quality (4 bullets) -* ### `Section 5: Continous Integration` +* ### `Section 5: Continuous Integration` -Guideliness for CI in the JS world (9 bullets) +Guidelines for CI in the JS world (9 bullets)

@@ -2035,4 +2035,4 @@ gwOTQ2NywxNTk3MjQwNzc1LDIxMDM0MzAxNjYsLTM3NTY2Mzg0 LC0xMjg2NTMxNjAwLC0yOTc1MDI2MjMsNDM1MTk1ODgwLDE3Nj U5NjcxMzAsNzk0ODg4NTE3LC0xODAwNTU1MDA2LDkzNTEyNDg3 OV19 ---> \ No newline at end of file +--> From f66fe473935e75fe8b2badbf7edbed0a0ec7016b Mon Sep 17 00:00:00 2001 From: Scott Davis Date: Mon, 19 Aug 2019 08:45:18 -0600 Subject: [PATCH 028/502] fix: spelling typo --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 224dd156..54f9f6a4 100644 --- a/readme.md +++ b/readme.md @@ -37,7 +37,7 @@ A single advice that inspires all the others (1 special bullet) * ### `Section 1: The Test Anatomy` -The foundation - strucuturing clean tests (12 bullets) +The foundation - structuring clean tests (12 bullets) * ### `Section 2: Backend` @@ -2035,4 +2035,4 @@ gwOTQ2NywxNTk3MjQwNzc1LDIxMDM0MzAxNjYsLTM3NTY2Mzg0 LC0xMjg2NTMxNjAwLC0yOTc1MDI2MjMsNDM1MTk1ODgwLDE3Nj U5NjcxMzAsNzk0ODg4NTE3LC0xODAwNTU1MDA2LDkzNTEyNDg3 OV19 ---> \ No newline at end of file +--> From 3bd38831a5a8163cf320dbbaa8f5017ad000b95c Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 20 Aug 2019 21:07:56 +1200 Subject: [PATCH 029/502] Correct the text for section 5.6 --- readme.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index c54d7563..ab67a329 100644 --- a/readme.md +++ b/readme.md @@ -1900,11 +1900,9 @@ license-checker --summary --failOn BSD

## ⚪ ️5.6 Constantly inspect for vulnerable dependencies -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like license check and plagiarism check (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights -
- +:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build -❌ **Otherwise:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious
From 061abc979c83cb3d396fff77fc9c28167ecc42ab Mon Sep 17 00:00:00 2001 From: Olivier PASCAL Date: Tue, 20 Aug 2019 17:46:18 +0200 Subject: [PATCH 030/502] Fix section 4.3 markdown image path typo Missing `")` was causing error on image path --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 30d8ae30..b5774bc4 100644 --- a/readme.md +++ b/readme.md @@ -1698,7 +1698,7 @@ it("Test addNewOrder, don't use such test names", () => { ### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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) +![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)") @@ -2028,4 +2028,4 @@ Took care to revise, improve, lint and polish all the texts **Role:** Concept, design and great advice -**About:** A savvy frontend developer, CSS expert and emojis freak \ No newline at end of file +**About:** A savvy frontend developer, CSS expert and emojis freak From ee58a6e5395024043781d9b82c9d504dbea4221f Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Tue, 20 Aug 2019 18:56:15 +0300 Subject: [PATCH 031/502] Update readme.md --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 9c230375..0102fa1e 100644 --- a/readme.md +++ b/readme.md @@ -322,7 +322,7 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response", ## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies -:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide an immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). However, the various techniques were not born equal: some of them, spies and stubs, are focused on testing the requirements but as an inevitable side-effect they also slightly touch the internals. Mocks, on the contrary side, are focused on testing the internals — this brings huge overhead as explained in the bullet “Stick to black box testing”.

+:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide an immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). However, the various techniques were not born equal: some of them, spies and stubs, are focused on testing the requirements but as an inevitable side-effect they also slightly touch the internals. Mocks, on the contrary side, are focused on testing the internals — this brings huge overhead as explained in the bullet “Stick to black box testing”. @@ -2028,4 +2028,4 @@ Took care to revise, improve, lint and polish all the texts **Role:** Concept, design and great advice -**About:** A savvy frontend developer, CSS expert and emojis freak \ No newline at end of file +**About:** A savvy frontend developer, CSS expert and emojis freak From b1e51464c986a0f48f672dc42bbc2b2edd733e46 Mon Sep 17 00:00:00 2001 From: Idan Date: Wed, 21 Aug 2019 18:16:08 +0300 Subject: [PATCH 032/502] fixed links in table of contents --- readme.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/readme.md b/readme.md index e5a807d4..6691ba83 100644 --- a/readme.md +++ b/readme.md @@ -29,33 +29,32 @@ Start by understanding the ubiquitous testing practices that are the foundation

-# `Table of contents` +## Table of Contents -* ### `Section 0: The Golden Rule` +* ####[Section 0: The Golden Rule](#Section-0️⃣-:-The-Golden-Rule) A single advice that inspires all the others (1 special bullet) -* ### `Section 1: The Test Anatomy` +* ####[Section 1: The Test Anatomy](#Section-1:-The-Test-Anatomy) The foundation - structuring clean tests (12 bullets) - -* ### `Section 2: Backend` +* ####[Section 2: Backend](#Section-2️⃣-:-Backend-Testing) Writing backend and Microservices tests efficiently (8 bullets) -* ### `Section 3: Frontend, UI, E2E` +* ####[Section 3: Frontend](#Section-3️⃣:-Frontend-Testing) Writing tests for web UI including component and E2E tests (11 bullets) -* ### `Section 4: Measuring Tests Effectivenss` +* ####[Section 4: Measuring Tests Effectiveness](#Section-4️⃣:-Measuring-Test-Effectiveness) Watching the watchman - measuring test quality (4 bullets) -* ### `Section 5: Continuous Integration` +* ####[Section 5: Continuous Integration](#Section-5️⃣-CI-and-Other-Quality-Measures) Guidelines for CI in the JS world (9 bullets) @@ -87,7 +86,7 @@ Most of the advice below are derivatives of this principle.

-# Section 1. The Test Anatomy +# Section 1: The Test Anatomy
From 84bf22a9b7dcc7d92e38d12c0781403da5236972 Mon Sep 17 00:00:00 2001 From: jaimemendozadev Date: Wed, 21 Aug 2019 18:53:35 -0700 Subject: [PATCH 033/502] Make content/style changes in Sections 1.2 - 1.4 --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index e5a807d4..02a949a7 100644 --- a/readme.md +++ b/readme.md @@ -142,7 +142,7 @@ describe('Products Service', function() { ## ⚪ ️ 1.2 Structure tests by the AAA pattern -:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain CPU on understanding the test plan: +:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: 1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code @@ -154,7 +154,7 @@ describe('Products Service', function() {
-❌ **Otherwise:** Not only you spend long daily hours on understanding the main code, now also what should have been the simple part of the day (testing) stretches your brain +❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain
@@ -211,11 +211,11 @@ test('Should be classified as premium', () => { ## ⚪ ️1.3 Describe expectations in a product language: use BDD-style assertions -:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write an imperative code that is packed with conditional logic the reader is thrown away to an effortful mental mood. In that sense, code the expectation in a human-like language, declarative BDD style using expect or should and not using custom code. If Chai & Jest don’t include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/)
-❌ **Otherwise:** The team will write less test and decorate the annoying ones with .skip() +❌ **Otherwise:** The team will write less tests and decorate the annoying ones with .skip()
@@ -276,7 +276,7 @@ it("When asking for an admin, ensure only ordered admins in results" , ()={ ## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods -:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API deliver the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as behavioral testing. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine- this dramatically increases the maintenance burden +:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API deliver the right results, should you really invest the next 3 hours testing HOW it worked internally and then maintain those fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as behavioral testing. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine- this dramatically increases the maintenance burden
From cfab106acfbdd4d14f7bc138d565aca5cc4e1383 Mon Sep 17 00:00:00 2001 From: jaimemendozadev Date: Wed, 21 Aug 2019 19:01:10 -0700 Subject: [PATCH 034/502] Make more edits to Section 1.4 --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 02a949a7..91a79b96 100644 --- a/readme.md +++ b/readme.md @@ -276,11 +276,11 @@ it("When asking for an admin, ensure only ordered admins in results" , ()={ ## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods -:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API deliver the right results, should you really invest the next 3 hours testing HOW it worked internally and then maintain those fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as behavioral testing. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine- this dramatically increases the maintenance burden +:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API deliver the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden
-❌ **Otherwise:** Your test behaves like the [child who cries wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shoot out loud false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday a real bug will get ignored… +❌ **Otherwise:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored…
Code Examples From d7bb1d2a61852d264f4c83e491b778322225eab3 Mon Sep 17 00:00:00 2001 From: jaimemendozadev Date: Wed, 21 Aug 2019 19:16:26 -0700 Subject: [PATCH 035/502] Make edits to Section 1.5 --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 91a79b96..04590054 100644 --- a/readme.md +++ b/readme.md @@ -322,11 +322,11 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response", ## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies -:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide an immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). -Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a smell of white-box testing. +Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. -For example, if you want to test what your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently +For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently
From f1730193840a9bc0711513959ea5654ba060f336 Mon Sep 17 00:00:00 2001 From: jaimemendozadev Date: Wed, 21 Aug 2019 19:28:48 -0700 Subject: [PATCH 036/502] Make edits to Section 1.6 --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 04590054..f850989e 100644 --- a/readme.md +++ b/readme.md @@ -372,11 +372,11 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 Don’t “foo”, use realistic input data -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not instead) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? see next bullet (property-based testing). +:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing).
-❌ **Otherwise:** All your development testing will falsely seem green when you use synthetic inputs like “Foo” but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA”
From 93be9dc6a6728a979d58188ff7dc2aaa7fa20a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Evangelista?= Date: Thu, 22 Aug 2019 14:25:50 +0100 Subject: [PATCH 037/502] Correcting typos --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index e5a807d4..cd15ec71 100644 --- a/readme.md +++ b/readme.md @@ -228,7 +228,7 @@ test('Should be classified as premium', () => { ### :thumbsdown: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story ```javascript -test("When asking for an admin, ensure only ordered admins in results" , ()={ +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}); @@ -258,7 +258,7 @@ test("When asking for an admin, ensure only ordered admins in results" , ()={ ```javascript -it("When asking for an admin, ensure only ordered admins in results" , ()={ +it("When asking for an admin, ensure only ordered admins in results" , () => { //assuming we've added here two admins const allAdmins = getUsers({adminOnly:true}); From 6717f56bdb889587f0c15b2760bfa24de8094818 Mon Sep 17 00:00:00 2001 From: Idan Dagan Date: Thu, 22 Aug 2019 16:36:31 +0300 Subject: [PATCH 038/502] Update readme.md --- readme.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index 6691ba83..c4568b50 100644 --- a/readme.md +++ b/readme.md @@ -31,30 +31,30 @@ Start by understanding the ubiquitous testing practices that are the foundation ## Table of Contents -* ####[Section 0: The Golden Rule](#Section-0️⃣-:-The-Golden-Rule) +#### [Section 0: The Golden Rule](#Section-0️⃣-:-The-Golden-Rule) A single advice that inspires all the others (1 special bullet) -* ####[Section 1: The Test Anatomy](#Section-1:-The-Test-Anatomy) +#### [Section 1: The Test Anatomy](#Section-1:-The-Test-Anatomy) The foundation - structuring clean tests (12 bullets) -* ####[Section 2: Backend](#Section-2️⃣-:-Backend-Testing) +#### [Section 2: Backend](#Section-2️⃣-:-Backend-Testing) Writing backend and Microservices tests efficiently (8 bullets) -* ####[Section 3: Frontend](#Section-3️⃣:-Frontend-Testing) +#### [Section 3: Frontend](#Section-3️⃣:-Frontend-Testing) Writing tests for web UI including component and E2E tests (11 bullets) -* ####[Section 4: Measuring Tests Effectiveness](#Section-4️⃣:-Measuring-Test-Effectiveness) +#### [Section 4: Measuring Tests Effectiveness](#Section-4️⃣:-Measuring-Test-Effectiveness) Watching the watchman - measuring test quality (4 bullets) -* ####[Section 5: Continuous Integration](#Section-5️⃣-CI-and-Other-Quality-Measures) +#### [Section 5: Continuous Integration](#Section-5️⃣-CI-and-Other-Quality-Measures) Guidelines for CI in the JS world (9 bullets) From 4ec4b90243593e7388ff758e76a8af0e40fc6918 Mon Sep 17 00:00:00 2001 From: Idan Date: Thu, 22 Aug 2019 16:41:32 +0300 Subject: [PATCH 039/502] Update readme.md --- readme.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/readme.md b/readme.md index 6691ba83..8dfae9f5 100644 --- a/readme.md +++ b/readme.md @@ -31,30 +31,27 @@ Start by understanding the ubiquitous testing practices that are the foundation ## Table of Contents -* ####[Section 0: The Golden Rule](#Section-0️⃣-:-The-Golden-Rule) +#### [Section 0: The Golden Rule](#section-0️⃣---the-golden-rule) A single advice that inspires all the others (1 special bullet) -* ####[Section 1: The Test Anatomy](#Section-1:-The-Test-Anatomy) +#### [Section 1: The Test Anatomy](#section-1-the-test-anatomy-1) The foundation - structuring clean tests (12 bullets) -* ####[Section 2: Backend](#Section-2️⃣-:-Backend-Testing) +#### [Section 2: Backend](#section-2️⃣--backend-testing) Writing backend and Microservices tests efficiently (8 bullets) - -* ####[Section 3: Frontend](#Section-3️⃣:-Frontend-Testing) +#### [Section 3: Frontend](#section-3️⃣-frontend-testing) Writing tests for web UI including component and E2E tests (11 bullets) - -* ####[Section 4: Measuring Tests Effectiveness](#Section-4️⃣:-Measuring-Test-Effectiveness) +#### [Section 4: Measuring Tests Effectiveness](#section-4️⃣-measuring-test-effectiveness) Watching the watchman - measuring test quality (4 bullets) - -* ####[Section 5: Continuous Integration](#Section-5️⃣-CI-and-Other-Quality-Measures) +#### [Section 5: Continuous Integration](#section-5️⃣-ci-and-other-quality-measures) Guidelines for CI in the JS world (9 bullets) From fe471392c5e9e83c0901c945509ba53f34564aa3 Mon Sep 17 00:00:00 2001 From: Idan Date: Thu, 22 Aug 2019 16:48:21 +0300 Subject: [PATCH 040/502] fixed section titles --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 8dfae9f5..e41e852c 100644 --- a/readme.md +++ b/readme.md @@ -59,7 +59,7 @@ Guidelines for CI in the JS world (9 bullets)

-# Section 0️⃣ : The Golden Rule +# Section 0️⃣: The Golden Rule
@@ -706,7 +706,7 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr

-# Section 2️⃣ : Backend Testing +# Section 2️⃣: Backend Testing ## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid @@ -1736,7 +1736,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

-# Section 5️⃣ CI and Other Quality Measures +# Section 5️⃣: CI and Other Quality Measures

From 79a6f4061775410c0a03d59a935ad7690d4e78d0 Mon Sep 17 00:00:00 2001 From: Idan Date: Thu, 22 Aug 2019 16:50:36 +0300 Subject: [PATCH 041/502] fixed links --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index e41e852c..db7bc298 100644 --- a/readme.md +++ b/readme.md @@ -31,7 +31,7 @@ Start by understanding the ubiquitous testing practices that are the foundation ## Table of Contents -#### [Section 0: The Golden Rule](#section-0️⃣---the-golden-rule) +#### [Section 0: The Golden Rule](#section-0️⃣-the-golden-rule) A single advice that inspires all the others (1 special bullet) @@ -39,7 +39,7 @@ A single advice that inspires all the others (1 special bullet) The foundation - structuring clean tests (12 bullets) -#### [Section 2: Backend](#section-2️⃣--backend-testing) +#### [Section 2: Backend](#section-2️⃣-backend-testing) Writing backend and Microservices tests efficiently (8 bullets) From 937f6b804b54985ec46c9ab416641c46333298d9 Mon Sep 17 00:00:00 2001 From: Idan Dagan Date: Thu, 22 Aug 2019 18:11:26 +0300 Subject: [PATCH 042/502] Update readme.md --- readme.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index db7bc298..0935715c 100644 --- a/readme.md +++ b/readme.md @@ -31,27 +31,27 @@ Start by understanding the ubiquitous testing practices that are the foundation ## Table of Contents -#### [Section 0: The Golden Rule](#section-0️⃣-the-golden-rule) +#### [`Section 0: The Golden Rule`](#section-0️⃣-the-golden-rule) A single advice that inspires all the others (1 special bullet) -#### [Section 1: The Test Anatomy](#section-1-the-test-anatomy-1) +#### [`Section 1: The Test Anatomy`](#section-1-the-test-anatomy-1) The foundation - structuring clean tests (12 bullets) -#### [Section 2: Backend](#section-2️⃣-backend-testing) +#### [`Section 2: Backend`](#section-2️⃣-backend-testing) Writing backend and Microservices tests efficiently (8 bullets) -#### [Section 3: Frontend](#section-3️⃣-frontend-testing) +#### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) Writing tests for web UI including component and E2E tests (11 bullets) -#### [Section 4: Measuring Tests Effectiveness](#section-4️⃣-measuring-test-effectiveness) +#### [`Section 4: Measuring Tests Effectiveness`](#section-4️⃣-measuring-test-effectiveness) Watching the watchman - measuring test quality (4 bullets) -#### [Section 5: Continuous Integration](#section-5️⃣-ci-and-other-quality-measures) +#### [`Section 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) Guidelines for CI in the JS world (9 bullets) From 1211a2bf141bb708a96c4b1c15b8f11283c77a80 Mon Sep 17 00:00:00 2001 From: Idan Dagan Date: Thu, 22 Aug 2019 18:13:11 +0300 Subject: [PATCH 043/502] Added code ticks --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0935715c..44ca74c8 100644 --- a/readme.md +++ b/readme.md @@ -29,7 +29,7 @@ Start by understanding the ubiquitous testing practices that are the foundation

-## Table of Contents +## `Table of Contents` #### [`Section 0: The Golden Rule`](#section-0️⃣-the-golden-rule) From 55dffb07462068d8ef895ccc6a10034f67443f5f Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sat, 24 Aug 2019 05:46:57 -0700 Subject: [PATCH 044/502] Create questions-answers.md --- .operations/questions-answers.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .operations/questions-answers.md diff --git a/.operations/questions-answers.md b/.operations/questions-answers.md new file mode 100644 index 00000000..bb18840c --- /dev/null +++ b/.operations/questions-answers.md @@ -0,0 +1,18 @@ +# Common questions and answers + +### Q: How do I start a new translation? + +**Answer: ** + +welcome aboard , having a {language} translation would be awesome 🔥 + +Having a Hungarian translation would be great 👍 + +Before you start with this, we've prepared some basic workflow guidelines: + +Work on your own fork - fork, create a branch, translate & collaborate with other translators, then open a PR +Focus on translation, not content editing - the focus is on translation, should anyone want to modify the content or the graphics - let's PR a draft in English first and then translate to other languages. Also the format of the text should remain intact (same design) +Duplicate the readme and the inner pages - the content should be translated over at a duplicated page, step by step. As an example, README.md would be come README.{translated-language}.md (e.g. README.french.md), all other files should be duplicated similarly. So the number of English & translated pages should be the same +Collaborate - once you setup the translation foundation (branch, duplicate pages), we can announce the work on a new language and get others involved to support you in translating (if you wish so, of course) +We're here to help - let us know whether we can do anything to support you. We can Tweet about this work, etc. 🚀 + From 5a9f655c776a9ed04aae2910aa5e52ac52db1355 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sat, 24 Aug 2019 06:03:56 -0700 Subject: [PATCH 045/502] Update questions-answers.md --- .operations/questions-answers.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.operations/questions-answers.md b/.operations/questions-answers.md index bb18840c..d5e7b75d 100644 --- a/.operations/questions-answers.md +++ b/.operations/questions-answers.md @@ -1,18 +1,17 @@ # Common questions and answers -### Q: How do I start a new translation? +## Q: How do I start a new translation? -**Answer: ** +**Answer:** -welcome aboard , having a {language} translation would be awesome 🔥 +welcome aboard! Having a {language} translation would be awesome 🔥. I'll be glad to collaborate with you on this and help wherever I can -Having a Hungarian translation would be great 👍 +Before you start with this, I've prepared some basic workflow guidelines: -Before you start with this, we've prepared some basic workflow guidelines: +**Where to do the translation?** - Fork and work your own copy, create a readme-{language}.md file (e.g. readme-fr.md) and do the translation work over there -Work on your own fork - fork, create a branch, translate & collaborate with other translators, then open a PR -Focus on translation, not content editing - the focus is on translation, should anyone want to modify the content or the graphics - let's PR a draft in English first and then translate to other languages. Also the format of the text should remain intact (same design) -Duplicate the readme and the inner pages - the content should be translated over at a duplicated page, step by step. As an example, README.md would be come README.{translated-language}.md (e.g. README.french.md), all other files should be duplicated similarly. So the number of English & translated pages should be the same -Collaborate - once you setup the translation foundation (branch, duplicate pages), we can announce the work on a new language and get others involved to support you in translating (if you wish so, of course) -We're here to help - let us know whether we can do anything to support you. We can Tweet about this work, etc. 🚀 +**How to push changes?** - I will create a dedicated branch for you translations-{language}-staging (e.g. translations-fr-staging), whenever you want to save some changes or share with the team - just PR to this branch +**How & when to publish to master?** - The content can be published once it's 70% translated and 100% language proofed. Kindly run it through spell checker. Whenever you feel that the content stands to these guidelines, just a raise a flag and I'll merge the language branch into the master + +**Will I get credit for the translation work?** - Obviously! Your name will appear nearby the langauge flag in the main readme.md, added to the repo team, appear boldly at the top of the translation page - 'Translated, adapted and reviewed by {Your name}'. We will also publish a medium article with the translation with your name at the top From bb4fef26a8fa26f5468a1ff2ad8dcb574411d5b3 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sat, 24 Aug 2019 06:10:01 -0700 Subject: [PATCH 046/502] Update questions-answers.md --- .operations/questions-answers.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.operations/questions-answers.md b/.operations/questions-answers.md index d5e7b75d..67b42d7e 100644 --- a/.operations/questions-answers.md +++ b/.operations/questions-answers.md @@ -4,7 +4,7 @@ **Answer:** -welcome aboard! Having a {language} 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: @@ -14,4 +14,6 @@ Before you start with this, I've prepared some basic workflow guidelines: **How & when to publish to master?** - The content can be published once it's 70% translated and 100% language proofed. Kindly run it through spell checker. Whenever you feel that the content stands to these guidelines, just a raise a flag and I'll merge the language branch into the master -**Will I get credit for the translation work?** - Obviously! Your name will appear nearby the langauge flag in the main readme.md, added to the repo team, appear boldly at the top of the translation page - 'Translated, adapted and reviewed by {Your name}'. We will also publish a medium article with the translation with your name at the top +**Will I get credit for the translation work?** - Obviously! Your name will appear nearby the language flag in the main readme.md, added to the repo team, appear boldly at the top of the translation page - 'Translated, adapted and reviewed by {Your name}'. We will also publish a medium article with the translation with your name at the top + +Looking forward and excited to work on this! From e89e598f57bf53668333df2fdcd363ec821a606f Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Mon, 26 Aug 2019 00:06:33 +0900 Subject: [PATCH 047/502] Add readme file for Korean translation. - Just getting started. --- readme.korean.md | 2022 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2022 insertions(+) create mode 100644 readme.korean.md diff --git a/readme.korean.md b/readme.korean.md new file mode 100644 index 00000000..8ab281ec --- /dev/null +++ b/readme.korean.md @@ -0,0 +1,2022 @@ + + +
+ +# 👇 이 가이드가 당신의 테스트 기술을 한 단계 끌어 올리는 이유 + +
+ +## 📗 철저하고 매우 포괄적인 45가지 이상의 모범 사례 +JavaScript 및 Node.js에 대한 A부터 Z까지의 믿음직한 가이드입니다. 수십 가지 최고의 블로그 게시물, 서적 및 도구를 요약하고 정리합니다. + +## 🚢 기초를 뛰어넘어 고급으로 +운영중인 제품의 테스트, 돌연변이 테스트, 속성 기반 테스트 및 기타 여러 전략적 & 전문 도구와 같은 고급 주제로 넘어가는 여정을 경험하십시오. +이 가이드의 모든 단어를 읽으면 당신의 테스트 기술이 평균보다 높아질 수 있습니다. + +## 🌐 Full-stack: 프론트, 백엔드, CI, 무엇이든 +모든 응용프로그램 계층의 기초가 되는 유비쿼터스 테스트 방법을 이해하는 것으로부터 시작하십시오. 그런 다음 프론트엔드/UI, 백엔드, CI 혹은 이 모든것을 공부하세요. + +
+ +### Yoni Goldberg 작성 +* JavaScript & Node.js 컨설턴트 +* 👨‍🏫 [나의 테스팅 워크샵](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/) - 인사이트와 오직 전략적인 문제에 대한 내용 + +

+ +## `목차` + +#### [`섹션 0: 황금률`](#section-0️⃣-the-golden-rule) + +모든 모든 사람들에게 영감을 주는 하나의 조언(하나의 특수한 항목) + +#### [`섹션 1: 테스트 해부`](#section-1-the-test-anatomy-1) + +기초 - 깔끔한 테스트 구성하기(12개) + +#### [`섹션 2: 백엔드`](#section-2️⃣-backend-testing) + +백엔드 및 마이크로서비스 테스트 효율적으로 작성하기(8개) + +#### [`섹션 3: 프론트엔드`](#section-3️⃣-frontend-testing) + +컴포넌트 및 E2E 테스트를 포함한 웹 UI에 대한 테스트 작성하기(11개) + +#### [`섹션 4: 테스트 효과 측정`](#section-4️⃣-measuring-test-effectiveness) + +감시자를 감시하기 - 테스트 품질 측정(4개) + + +#### [`섹션 5: 지속적인 통합`](#section-5️⃣-ci-and-other-quality-measures) + +자바스크립트 세계에서 CI에 대한 지침(9개) + +

+ +# Section 0️⃣: The Golden Rule + +
+ +## ⚪️ 0. The Golden Rule: Design for lean testing + +:white_check_mark: **Do:** +Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. + +Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. + +The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells we have two brain systems: system 1 which is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should *feel* as easy as modifying an HTML document and not like solving 2X(17 × 24). + +This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. + +![alt text](/assets/headspace.png "We have no head room for additional complexity") + +Most of the advice below are derivatives of this principle. + +### Ready to start? + + +

+ +# Section 1: The Test Anatomy + +
+ +## ⚪ ️ 1.1 Include 3 parts in each test name + +:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: + +(1) What is being tested? For example, the ProductsService.addNewProduct method + +(2) Under what circumstances and scenario? For example, no price is passed to the method + +(3) What is the expected result? For example, the new product is not approved + +
+ + +❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? + +
+ +**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +
Code Examples + +
+ +### :clap: Doing It Right Example: A test name that constitutes 3 parts + +![](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: Doing It Right Example: A test name that constitutes 3 parts +![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") + +
+ +

+ +## ⚪ ️ 1.2 Structure tests by the AAA pattern + +:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain CPU on understanding the test plan: + +1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code + +2nd A - Act: Execute the unit under test. Usually 1 line of code + +3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code + + +
+ + +❌ **Otherwise:** Not only you spend long daily hours on understanding the main code, now also what should have been the simple part of the day (testing) stretches your brain + +
+ +
Code Examples + +
+ +### :clap: Doing It Right Example: A test structured with the AAA pattern + +![](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 Jest") + +```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: Anti Pattern Example: No separation, one bulk, harder to interpret + +```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 Describe expectations in a product language: use BDD-style assertions + +:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write an imperative code that is packed with conditional logic the reader is thrown away to an effortful mental mood. In that sense, code the expectation in a human-like language, declarative BDD style using expect or should and not using custom code. If Chai & Jest don’t include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +
+ + +❌ **Otherwise:** The team will write less test and decorate the annoying ones with .skip() + +
+ +
Code Examples
+ +![](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: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story + +```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}); + + const 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: Doing It Right Example: Skimming through the following declarative test is a breeze + + +```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 Stick to black-box testing: Test only public methods + +:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API deliver the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as behavioral testing. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine- this dramatically increases the maintenance burden +
+ + +❌ **Otherwise:** Your test behaves like the [child who cries wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shoot out loud false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday a real bug will get ignored… + +
+
Code Examples + +
+ +### :thumbsdown: Anti Pattern Example: A test case is testing the internals for no good reason +![](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 + calculateVAT(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; + } +} + + +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 Choose the right test doubles: Avoid mocks in favor of stubs and spies + +:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide an immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). + +Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a smell of white-box testing. + +For example, if you want to test what your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently +
+ + +❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend + +
+ +
Code Examples + +
+ +### :thumbsdown: Anti-pattern example: Mocks focus on the internals +![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg + "Examples with Mocha & Chai") +```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:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals + +```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) +}); +``` + +
+ + + +

+ +## ⚪ ️1.6 Don’t “foo”, use realistic input data + +:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not instead) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? see next bullet (property-based testing). +
+ + +❌ **Otherwise:** All your development testing will falsely seem green when you use synthetic inputs like “Foo” but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” + + +
+ +
Code Examples + +
+ +### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg + "Examples with Jest") + + +```javascript +const addProduct = (name, price) =>{ + const productNameRegexNoSpace = /^\S*$/;//no white-space allowd + + 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:Doing It Right Example: Randomizing realistic input +```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 Test many input combinations using Property-based testing + +:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +
+ + +❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs + + +
+ +
Code Examples + +
+ +### :clap: Doing It Right Example: Testing many input permutations with “mocha-testcheck” + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with Jest") + +```javascript +require('mocha-testcheck').install(); +const {expect} = require('chai'); + +describe('Product service', () => { + describe('Adding new', () => { + //this will run 100 times with different random properties + check.it('Add new product with random yet valid properties, always successful', + gen.int, gen.string, (id, name) => { + expect(addNewProduct(id, name).status).to.equal('approved'); + }); + }) +}); + +``` + +
+ + + + +

+ +## ⚪ ️ 1.8 If needed, use only short & inline snapshots + +:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. + +On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much + +It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +
+ +❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... + +
+ +
Code Examples + +
+ +### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code + +![](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 < /DisplayPage>) +.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: Doing It Right Example: Expectations are visible and focused +```javascript +it('When visiting TestJavaScript.com home page, a menu is displayed', () => { +//Arrange + +//Act +receivedPage tree = renderer +.create( Test JavaScript < /DisplayPage>) +.toJSON(); + +//Assert + +const menu = receivedPage.content.menu; +expect(menu).toMatchInlineSnapshot(` +
    +
  • Home
  • +
  • About
  • +
  • Contact
  • +
+`); +}); +``` + +
+ + +

+ +## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
+ + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + + +
+ +
Code Examples + +
+ +### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with Jest") + +```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 :[ +}); + +``` +
+ +### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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 Don’t catch errors, expect them +:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations + +A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +
+ + +❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong + + +
+ +
Code Examples + +
+ +### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with Jest") + +```javascript +it("When no product name, it throws error 400", async() => { +let errorWeExceptFor = null; +try { + const result = await addNewProduct({name:'nest'});} +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: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM + +```javascript +it.only("When no product name, it throws error 400", async() => { + expect(addNewProduct)).to.eventually.throw(AppError).with.property('code', "InvalidInput"); +}); + +``` + +
+ + + + +

+ +## ⚪ ️ 1.11 Tag your tests + +:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +
+ + +❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests + + +
+ +
Code Examples + +
+ +### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) + +![](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 Other generic good testing hygiene +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +
+ + +❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades + +

+ + +# Section 2️⃣: Backend Testing + +## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid + +:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? + +Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. + +It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks + +A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] + +
+ + +❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes + + +
+ +
Code Examples + +
+ +### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘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 Component testing might be your best affair + +:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. + +Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +
+ + +❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage + + +
+ +
Code Examples + +
+ +### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) + +![](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)") + +
+ +

+ +## ⚪ ️2.3 Ensure new releases don’t break the API using + +:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +
+ + +❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear + + +
+ +
Code Examples + +
+ +### :clap: Doing It Right Example: + +![](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 Test your middlewares in isolation + +:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +
+ + +❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests + + +
+ +
Code Examples + +
+ +### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine + +![](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 Measure and refactor using static analysis tools +:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) + +Credit:: [Keith Holliday](https://github.com/TheHollidayInn) + +
+ + +❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix + + +
+ +
Code Examples + +
+ +### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: + +![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg + "Examples with CodeClimate") + +![alt text](assets/bp-16-yoni-goldberg-quality.png " CodeClimat, a commercial tool that can identify complex methods:") + +
+ + + + +

+ +## ⚪ ️ 2.6 Check your readiness for Node-related chaos +:white_check_mark: **Do:** Weirdly, most software testings are about logic & data only, but some of the worst things that happen (and are really hard to mitigate ) are infrastructural issues. For example, did you ever test what happens when your process memory is overloaded, or when the server/process dies, or does your monitoring system realizes when the API becomes 50% slower?. To test and mitigate these type of bad things — [Chaos engineering](https://principlesofchaos.org/) was born by Netflix. It aims to provide awareness, frameworks and tools for testing our app resiliency for chaotic issues. For example, one of its famous tools, [the chaos monkey](https://github.com/Netflix/chaosmonkey), randomly kills servers to ensure that our service can still serve users and not relying on a single server (there is also a Kubernetes version, [kube-monkey](https://github.com/asobti/kube-monkey), that kills pods). All these tools work on the hosting/platform level, but what if you wish to test and generate pure Node chaos like check how your Node process copes with uncaught errors, unhandled promise rejection, v8 memory overloaded with the max allowed of 1.7GB or whether your UX stays satisfactory when the event loop gets blocked often? to address this I’ve written, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) which provides all sort of Node-related chaotic acts +
+ + +❌ **Otherwise:** No escape here, Murphy’s law will hit your production without mercy + + +
+ +
Code Examples + +
+ +### :clap: Doing It Right Example: : Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to 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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
+ + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + + +
+ +
Code Examples + +
+ +### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with 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 :[ +}); + +``` +
+ +### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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️⃣: Frontend Testing + +## ⚪ ️ 3.1. Separate UI from functionality + +:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI + + +
+ +❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation + + +
+ +
Code Examples + +
+ +### :clap: Doing It Right Example: Separating out the UI details + +![](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: Anti Pattern Example: Assertion mix UI details and data +```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 HTML elements based on attributes that are unlikely to change + +:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed + +
    + +❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing + +![](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: Anti-Pattern Example: Relying on 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 Whenever possible, test with a realistic and fully rendered component + +:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake + +With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children + +
    + +❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Working realstically with a fully rendered component + +![](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: Anti-Pattern Example: Mocking the reality with 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 Don't sleep, use frameworks built-in support for async events. Also try to speed things up + +:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +
    + +❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) + +![](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 +// 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: Doing It Right Example: Testing library that waits for DOM elements + +```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: Anti-Pattern Example: custom sleep code +```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. Watch how the content is served over the network + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg + "Examples with Lighthouse") + +✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 + +
    + +❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration + +
    + +
    Code Examples + +### :clap: Doing It Right Example: Lighthouse page load inspection report + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + + +
    + + +
    + +## ⚪ ️ 3.6 Stub flakky and slow resources like backend APIs + +:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests + +
    + +❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower + + +
    + +
    Code Examples + +
    + +### :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%20Jest-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 Have very few end-to-end tests that spans the whole system + +:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment + +
    + +❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very differnt than expected + +
    + +## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials + +:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). + +
    + +❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Logging-in before-all and not 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 Have one E2E smoke test that just travels across the site map + +:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector + +
    + +❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Smoke travelling across all 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 Expose the tests as a live collaborative document + +:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. + +❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cocumber-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: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + + +
    + + + + +## ⚪ ️ 3.11 Detect visual issues with automated tools + + +:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS]([https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue + +
    + +❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? + + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A typical visual regression - right content that is served badly + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + + +### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots + +![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg + "Using Cypress to illustrate the idea") + +``` +​# 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: Doing It Right Example: Using Applitools to get snapshot comaprison and other advanced features + +![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg + "Using Cypress to illustrate the idea") ![](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️⃣: Measuring Test Effectiveness + +

    + +## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number + +:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. + +Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets + +
    + + +❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down + + +
    + +
    Code Examples + +
    + +### :clap: Example: A typical coverage report +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: Doing It Right Example: Setting up coverage per component (using Jest) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg + "Using Cypress to illustrate the idea") + +![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest) + +
    + + + +

    + +## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities + +:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +
    + + +❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from + + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: 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) + +
    + + +

    + +## ⚪ ️ 4.3 Measure logical coverage using mutation testing + +:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. + +Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: + +(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations + +(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. + +Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +
    + + +❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: 100% coverage, 0% testing + +![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg + "Using Cypress to illustrate the idea") +```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({asignee: "John@mailer.com",price: 120}); +});//Triggers 100% code coverage, but it doesn't check anything + +``` +
    + +### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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 Preventing test code issues with Test linters + +:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) + +
    + + +❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation + + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A test case full of errors, luckily all are caught by 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 and Other Quality Measures + +

    + +## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues + +:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash._map(…) +
    + + +❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day + + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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 Shorten the feedback loop with local developer-CI + +:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. + +Practically, some CI vendors (Example: [CircleCI load CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +
    + + +❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new 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 Perform e2e testing over a true production-mirror + +:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. + +The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +
    + + +❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated + + +
    + +
    Code Examples + +
    + +### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([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 Parallelize test execution +:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes + + +❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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 Stay away from legal issues using license and plagiarism check +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights + +❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: +```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 Constantly inspect for vulnerable dependencies +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like license check and plagiarism check (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights +
    + + +❌ **Otherwise:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build + + +
    + +
    Code Examples + +
    + +### :clap: Example: NPM Audit result +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + + + + +

    + +## ⚪ ️5.7 Automate dependency updates +:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: + +(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. + +(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). + +An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +
    + + +❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky + + +
    + +
    Code Examples + +
    + +### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions +![alt text](assets/bp-27-yoni-goldberg-npm.png "Nncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions") + + +
    + + +

    + +## ⚪ ️ 5.8 Other, non-Node related, CI tips +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +
    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
    +
    + + +❌ **Otherwise:** You‘ll miss years of wisdom + +

    + +## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions +:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +
    + + +❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? + + +
    + +
    Code Examples + +
    + +### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +
    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 500 fortune corporates 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) + +
    + +**Workshop:** 👨‍🏫 Want to learn all these practices and techniques at your offices (Europe & USA)? [Register here for my testing workshop](https://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 From cded6f0c55f18c027b474a298111b045551f3d21 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Tue, 27 Aug 2019 00:25:40 +0900 Subject: [PATCH 048/502] Translate into Korean from 1.1 to 1.2 --- readme.korean.md | 76 ++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/readme.korean.md b/readme.korean.md index 8ab281ec..778f65dc 100644 --- a/readme.korean.md +++ b/readme.korean.md @@ -75,95 +75,93 @@ This can be achieved by selectively cherry-picking techniques, tools and test ta Most of the advice below are derivatives of this principle. -### Ready to start? - +### 시작할 준비 되셨나요?

    -# Section 1: The Test Anatomy +# 섹션 1: 테스트 해부
    -## ⚪ ️ 1.1 Include 3 parts in each test name +## ⚪ ️ 1.1 각 테스트 이름은 세 부분으로 구성된다. -:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: +:white_check_mark: **이렇게 해라:** 테스트는 현재 애플리케이션의 개정판이 요구 사항을 충족하는지 여부를 다음과 같은 사람들에게 알려야합니다: 배포를 할 테스터, DevOps 엔지니어, 2년 후의 미래에 코드가 익숙하지 않은 사람. 테스트가 요구 사항 수준에서 작성되어 있고 세 부분으로 구성되어 있다면, 목적을 이룰 수 있습니다: -(1) What is being tested? For example, the ProductsService.addNewProduct method +(1) 무엇을 테스트하고 있는가? 예) 제품서비스.새제품추가 메서드 -(2) Under what circumstances and scenario? For example, no price is passed to the method +(2) 어떤 상황과 시나리오에서? 예) 메서드에 가격이 전달되지 않는다. -(3) What is the expected result? For example, the new product is not approved +(3) 예상되는 결과는 무엇인가? 예) 신제품은 승인되지 않는다.
    - -❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? +❌ **그렇지 않으면:** 배포에 실패하였고 "제품 추가" 라는 테스트에 실패하였다. 이것이 정확히 어떤 오작동 인지를 알려주나요?
    -**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand -
    Code Examples +**👇 주의:** 각 글에는 코드 예제가 있으며 때로는 이미지도 있습니다. 클릭하여 확장 + +
    코드 예제
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :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', ()=> { +//1. 단위 테스트 +describe('제품 서비스', function() { + describe('새 제품 추가', function() { + //2. 시나리오 3. 예상 + it('가격을 지정하지 않으면 제품 상태는 승인 대기중이다.', ()=> { const newProduct = new ProductService().add(...); - expect(newProduct.status).to.equal('pendingApproval'); + expect(newProduct.status).to.equal('승인 대기'); }); }); }); - ``` +
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :clap: 올바른 예: 세 부분으로 구성된 테스트 이름 + ![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts")


    -## ⚪ ️ 1.2 Structure tests by the AAA pattern +## ⚪ ️ 1.2 AAA 패턴에 의한 테스트 구조 -:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain CPU on understanding the test plan: +:white_check_mark: **이렇게 해라:** 3개의 잘 잘 구분된 섹션 AAA(Arrange, Act, Assert)으로 테스트를 구성하십시오. 이 구조를 따르면 테스트를 쉽게 읽을 수 있습니다: -1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code +첫번째 A - Arrange(준비): 테스트가 목표로 하는 시나리오에 필요한 시스템을 제공하기 위한 모든 설정 코드. 여기에는 테스트 생성자의 단위 인스턴스화, DB 데이터 추가, 객체에 대한 mock/stub 및 기타 준비 코드가 포함될 수 있습니다. -2nd A - Act: Execute the unit under test. Usually 1 line of code - -3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code +두번째 A - Act(행동): 단위 테스트를 실행. 일반적으로 코드 한줄 +세번째 A - Assert(주장, 예상): 받은 예상값이 충족하는지 확인하십시오. 일반적으로 코드 한줄
    - -❌ **Otherwise:** Not only you spend long daily hours on understanding the main code, now also what should have been the simple part of the day (testing) stretches your brain +❌ **그렇지 않으면:** 테스트는 오늘 일의 아주 단순한 부분에 불과하지만, 메인 코드를 이해하는데 많은 시간을 낭비 할 것입니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: A test structured with the AAA pattern +### :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 Jest") - + ```javascript -describe('Customer classifier', () => { - test('When customer spent more than 500$, should be classified as premium', () => { +describe('고객 분류기', () => { + test('고객이 500달러 이상을 소비한 경우 프리미엄으로 분류해야 합니다.', () => { //Arrange const customerToClassify = {spent:505, joined: new Date(), id:1} const DBStub = sinon.stub(dataAccess, "getCustomer") @@ -180,10 +178,10 @@ describe('Customer classifier', () => {
    -### :thumbsdown: Anti Pattern Example: No separation, one bulk, harder to interpret +### :thumbsdown: 올바르지 않은 예: 분리가 없고 한 벌로 작성되어 있어 해석하기 어렵다. ```javascript -test('Should be classified as premium', () => { +test('프리미엄으로 분류해야 합니다.', () => { const customerToClassify = {spent:505, joined: new Date(), id:1} const DBStub = sinon.stub(dataAccess, "getCustomer") .reply({id:1, classification: 'regular'}); @@ -192,16 +190,10 @@ test('Should be classified as premium', () => { }); ``` -
    - -

    - - - ## ⚪ ️1.3 Describe expectations in a product language: use BDD-style assertions :white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write an imperative code that is packed with conditional logic the reader is thrown away to an effortful mental mood. In that sense, code the expectation in a human-like language, declarative BDD style using expect or should and not using custom code. If Chai & Jest don’t include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) From e7913fb30b234dbfb6a2149a6b434a035fb26d85 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Wed, 28 Aug 2019 21:45:10 +0900 Subject: [PATCH 049/502] Translate into Korean from 1.3 to 1.4 - Rename readme.korean.md to readme.kr.md --- readme.korean.md => readme.kr.md | 97 +++++++++++++++----------------- 1 file changed, 44 insertions(+), 53 deletions(-) rename readme.korean.md => readme.kr.md (95%) diff --git a/readme.korean.md b/readme.kr.md similarity index 95% rename from readme.korean.md rename to readme.kr.md index 778f65dc..c4070bcc 100644 --- a/readme.korean.md +++ b/readme.kr.md @@ -19,6 +19,7 @@ JavaScript 및 Node.js에 대한 A부터 Z까지의 믿음직한 가이드입니
    ### Yoni Goldberg 작성 + * JavaScript & Node.js 컨설턴트 * 👨‍🏫 [나의 테스팅 워크샵](https://www.testjavascript.com) - 유럽과 미국에서의 [제 워크샵](https://www.testjavascript.com)에 대해서 알아보십시오. * [트위터 팔로우 하기](https://twitter.com/goldbergyoni/) @@ -178,50 +179,47 @@ describe('고객 분류기', () => {
    -### :thumbsdown: 올바르지 않은 예: 분리가 없고 한 벌로 작성되어 있어 해석하기 어렵다. +### :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'); - }); + 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 Describe expectations in a product language: use BDD-style assertions +## ⚪ ️1.3 제품의 언어로 예상값을 설명: BDD 스타일의 Assertion을 사용 +테스트를 선언적 스타일로 작성하면 읽는 사람이 즉시 파악할 수 있습니다. 조건부 논리로 채워진 명령형 코드로 작성하면 테스트를 읽기가 쉽지 않습니다. 그런 의미에서 임의의 사용자 정의 코드를 사용하지 말고, 선언적 BDD 스타일의 expect 또는 should를 사용하여 인간과 같은 언어로 테스트를 작성하십시오. Chai & Jest에 원하는 Assertion이 포함되어 있지 않고 반복성이 높은 경우 [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) 혹은 [custom Chai plugin](https://www.chaijs.com/guide/plugins/) 작성을 고려하십시오. -:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write an imperative code that is packed with conditional logic the reader is thrown away to an effortful mental mood. In that sense, code the expectation in a human-like language, declarative BDD style using expect or should and not using custom code. If Chai & Jest don’t include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/)
    - -❌ **Otherwise:** The team will write less test and decorate the annoying ones with .skip() +❌ **그렇지 않으면:** 팀은 테스트를 덜 작성하고 성가신 것들을 .skip() 으로 장식합니다.
    -
    Code Examples
    +
    코드 예제
    ![](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: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story + ### :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" +test("관리자 요청이 들어오면 정렬된 관리자 목록만 결과에 포함된다." , () => { + // 여기에 두 명의 관리자 "admin1", "admin2" 및 "user1" 을 추가했다고 가정합니다. const allAdmins = getUsers({adminOnly:true}); - const admin1Found, adming2Found = false; - allAdmins.forEach(aSingleUser => { if(aSingleUser === "user1"){ - assert.notEqual(aSingleUser, "user1", "A user was found and not admin"); + assert.notEqual(aSingleUser, "user1", "관리자가 아닌 사용자를 찾았다."); } if(aSingleUser==="admin1"){ admin1Found = true; @@ -230,79 +228,72 @@ test("When asking for an admin, ensure only ordered admins in results" , () => { admin2Found = true; } }); - if(!admin1Found || !admin2Found ){ - throw new Error("Not all admins were returned"); + throw new Error("모든 관리자가 반환되지 않았다."); } }); - ``` -
    -### :clap: Doing It Right Example: Skimming through the following declarative test is a breeze +
    +### :clap: 올바른 예: 다음과 같은 선언적 테스트는 이해하기 쉽습니다. ```javascript -it("When asking for an admin, ensure only ordered admins in results" , () => { - //assuming we've added here two admins +it("관리자 요청이 들어오면 정렬된 관리자 목록만 결과에 포함된다." , () => { + // 여기에 두 명의 관리자를 추가했다고 가정합니다. const allAdmins = getUsers({adminOnly:true}); - expect(allAdmins).to.include.ordered.members(["admin1" , "admin2"]) - .but.not.include.ordered.members(["user1"]); + .but.not.include.ordered.members(["user1"]); }); - ```
    -

    +## ⚪ ️ 1.4 블랙박스 테스트에 충실: public method만 테스트 -## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods +내부테스트는 거의 아무것도 하지 않는 엄청난 오버헤드를 발생시킵니다. 만약 당신의 코드 혹은 API가 올바른 결과를 반환한다면, 내부적으로 어떻게 동작했는지의 테스트에 3시간을 투자해야 합니까? 깨지기 쉬운 테스트를 유지해야 합니까? public method가 잘 동작할 때마다 private method 또한 암시적으로 테스트가 되고, 특정 문제(예. 잘못된 출력)가 있는 경우에만 테스트가 깨집니다. 이 접근법은 행동 테스트라고도 합니다. 다른 한편으로 당신은 내부 테스트를 해야합니까?(화이트박스 접근) - 컴포넌트를 설계하는 것에서 핵심 세부 사항으로 초점이 이동하거나 작은 코드의 리펙토링으로 인해 테스트가 중단 될 수 있지만, 결과는 훌륭합니다. - 이는 유지보수 부담을 크게 증가시킵니다. -:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API deliver the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as behavioral testing. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine- this dramatically increases the maintenance burden
    - -❌ **Otherwise:** Your test behaves like the [child who cries wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shoot out loud false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday a real bug will get ignored… +❌ **그렇지 않으면:** 당신의 테스트는 다음과 같이 동작합니다. [양치기 소년](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): 늑대가 나타났다!(예. private 변수가 변경되어 테스트에 실패하였습니다). 당연히 사람들은, 언젠가 진짜 버그가 무시될 때 까지 CI 알람을 무시하기 시작할 것입니다...
    -
    Code Examples + +
    코드 예제
    -### :thumbsdown: Anti Pattern Example: A test case is testing the internals for no good reason +### :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 - calculateVAT(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; - } + // 이 method 는 내부에서만 사용됩니다. + // 이 이름을 변경하면 테스트가 실패합니다. + calculateVAT(priceWithoutVAT){ + return {finalPrice: priceWithoutVAT * 1.2}; + // 결과 형식이나 키 이름을 변경하면 테스트가 실패합니다. + } + // public method + getPrice(productId){ + const desiredProduct= DB.getProduct(productId); + finalPrice = this.calculateVATAdd(desiredProduct.price).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 +it("화이트박스 테스트: 내부 method가 VAT 0을 받으면 0을 반환합니다.", async () => { + // 사용자가 VAT를 계산할 수 있게 하는 요구사항은 없으며, 최종 가격만 표시합니다. + // 그럼에도 불구하고 여기에서 내부 테스트 수행 expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0); }); - ```
    - - -

    ## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies From a820165ceecb6f323c1ea399f41a1cbdbf92e525 Mon Sep 17 00:00:00 2001 From: Kyle Martin Date: Sat, 31 Aug 2019 15:49:35 +1200 Subject: [PATCH 050/502] add to team --- readme.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/readme.md b/readme.md index ec445572..af368428 100644 --- a/readme.md +++ b/readme.md @@ -2023,3 +2023,9 @@ Took care to revise, improve, lint and polish all the texts **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. From 8b2854eb586539e9d6944148451b2c26135668c6 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Sun, 1 Sep 2019 23:35:47 +0900 Subject: [PATCH 051/502] Translate into Korean section 0. --- readme.kr.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index c4070bcc..6577d1e0 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -57,24 +57,23 @@ JavaScript 및 Node.js에 대한 A부터 Z까지의 믿음직한 가이드입니

    -# Section 0️⃣: The Golden Rule +# 섹션 0️⃣: 황금률
    -## ⚪️ 0. The Golden Rule: Design for lean testing +## ⚪ ️ 0. 황금률: 린 테스트를 위한 설계 -:white_check_mark: **Do:** -Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. +:white_check_mark: **이렇게 해라:** 테스트 코드는 제품 코드와 다릅니다. 단순하고, 짧고, 추상화가 없고, 무난하고, 작업하기에 편리하고, 린하게 디자인 하십시오. 테스트를 보고 즉시 의미를 알아챌 수 있어야 합니다. -Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. - -The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells we have two brain systems: system 1 which is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should *feel* as easy as modifying an HTML document and not like solving 2X(17 × 24). +우리 머리속은 제품 코드로 가득하고 부가적인 복잡한 것들을 생각할 여유가 없습니다. 또 다른 어려운 코드를 억지로 생각해내려고 한다면, 팀의 속도를 늦추게 되어 우리가 테스트를 하는 이유가 무색해 질 것입니다. 실제로 많은 팀들이 이런 이유를 테스트를 포기합니다. + +테스트는 친절하고 웃는 동료와 함께 일하는 것이 즐거울 수 있는 기회이고, 적은 투자로 큰 가치를 제공하는 것입니다. 과학은 우리에게 두 개의 뇌 시스템이 있다고 말합니다. 빈 도로에서 자동차를 운전하는 등의 간편한 활동에 사용되는 시스템 1, 그리고 수학 방정식을 푸는 것과 같이 복잡하고 의식적인 연산을 위한 시스템 2. 테스트 코드를 볼 때 수학 문제를 푸는 것 같은게 아닌, HTML 문서를 수정하는 것만 큼 쉬워야하는 시스템 1에 맞게 테스트를 설계하십시오. -This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. +선택적인 체리픽 기술, 툴 그리고 비용-효율적이고 뛰어난 ROI를 제공하는 테스트 대상 선정으로 목적을 이러한 달성할 수 있습니다. 필요한 만큼의 테스트, 융통성 있게 유자하려는 노력, 때로는 애자일함과 단순성을 위해 일부 테스트와 신뢰성을 포기하는 것도 가치가 있습니다. -![alt text](/assets/headspace.png "We have no head room for additional complexity") +![alt text](/assets/headspace.png "우리 머리속은 부가적인 복잡한 것들을 생각할 여유가 없습니다.") -Most of the advice below are derivatives of this principle. +아래 대부분의 조언은 이 원칙의 파생입니다. ### 시작할 준비 되셨나요? @@ -195,8 +194,8 @@ test('프리미엄으로 분류해야 합니다.', () => {

    -## ⚪ ️1.3 제품의 언어로 예상값을 설명: BDD 스타일의 Assertion을 사용 -테스트를 선언적 스타일로 작성하면 읽는 사람이 즉시 파악할 수 있습니다. 조건부 논리로 채워진 명령형 코드로 작성하면 테스트를 읽기가 쉽지 않습니다. 그런 의미에서 임의의 사용자 정의 코드를 사용하지 말고, 선언적 BDD 스타일의 expect 또는 should를 사용하여 인간과 같은 언어로 테스트를 작성하십시오. Chai & Jest에 원하는 Assertion이 포함되어 있지 않고 반복성이 높은 경우 [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) 혹은 [custom Chai plugin](https://www.chaijs.com/guide/plugins/) 작성을 고려하십시오. +## ⚪ ️ 1.3 제품의 언어로 예상값을 설명: BDD 스타일의 Assertion을 사용 +:white_check_mark: **이렇게 해라:** 테스트를 선언적 스타일로 작성하면 읽는 사람이 즉시 파악할 수 있습니다. 조건부 논리로 채워진 명령형 코드로 작성하면 테스트를 읽기가 쉽지 않습니다. 그런 의미에서 임의의 사용자 정의 코드를 사용하지 말고, 선언적 BDD 스타일의 expect 또는 should를 사용하여 인간과 같은 언어로 테스트를 작성하십시오. Chai & Jest에 원하는 Assertion이 포함되어 있지 않고 반복성이 높은 경우 [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) 혹은 [custom Chai plugin](https://www.chaijs.com/guide/plugins/) 작성을 고려하십시오.
    @@ -210,7 +209,7 @@ test('프리미엄으로 분류해야 합니다.', () => { "Examples with Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") - ### :thumbsdown: 올바르지 않은 예: 읽는 사람은 테스트 스토리를 이해하기 위해 짧지않은 명령형 코드를 훑어봐야 합니다. +### :thumbsdown: 올바르지 않은 예: 읽는 사람은 테스트 스토리를 이해하기 위해 짧지않은 명령형 코드를 훑어봐야 합니다. ```javascript test("관리자 요청이 들어오면 정렬된 관리자 목록만 결과에 포함된다." , () => { @@ -251,9 +250,9 @@ it("관리자 요청이 들어오면 정렬된 관리자 목록만 결과에 포

    -## ⚪ ️ 1.4 블랙박스 테스트에 충실: public method만 테스트 +## ⚪ ️ 1.4 블랙박스 테스트에 충실: public method만 테스트 -내부테스트는 거의 아무것도 하지 않는 엄청난 오버헤드를 발생시킵니다. 만약 당신의 코드 혹은 API가 올바른 결과를 반환한다면, 내부적으로 어떻게 동작했는지의 테스트에 3시간을 투자해야 합니까? 깨지기 쉬운 테스트를 유지해야 합니까? public method가 잘 동작할 때마다 private method 또한 암시적으로 테스트가 되고, 특정 문제(예. 잘못된 출력)가 있는 경우에만 테스트가 깨집니다. 이 접근법은 행동 테스트라고도 합니다. 다른 한편으로 당신은 내부 테스트를 해야합니까?(화이트박스 접근) - 컴포넌트를 설계하는 것에서 핵심 세부 사항으로 초점이 이동하거나 작은 코드의 리펙토링으로 인해 테스트가 중단 될 수 있지만, 결과는 훌륭합니다. - 이는 유지보수 부담을 크게 증가시킵니다. +:white_check_mark: **이렇게 해라:** 내부테스트는 거의 아무것도 하지 않는 엄청난 오버헤드를 발생시킵니다. 만약 당신의 코드 혹은 API가 올바른 결과를 반환한다면, 내부적으로 어떻게 동작했는지의 테스트에 3시간을 투자해야 합니까? 깨지기 쉬운 테스트를 유지해야 합니까? public method가 잘 동작할 때마다 private method 또한 암시적으로 테스트가 되고, 특정 문제(예. 잘못된 출력)가 있는 경우에만 테스트가 깨집니다. 이 접근법은 행동 테스트라고도 합니다. 다른 한편으로 당신은 내부 테스트를 해야합니까?(화이트박스 접근) - 컴포넌트를 설계하는 것에서 핵심 세부 사항으로 초점이 이동하거나 작은 코드의 리펙토링으로 인해 테스트가 중단 될 수 있지만, 결과는 훌륭합니다. - 이는 유지보수 부담을 크게 증가시킵니다.
    From 866d25f07eafd9d00ccb764e3011219bd61ff476 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Fri, 6 Sep 2019 00:30:37 +0900 Subject: [PATCH 052/502] Translate into Korean 1.5 - Fix typo. --- readme.kr.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 6577d1e0..be8617d7 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -69,7 +69,7 @@ JavaScript 및 Node.js에 대한 A부터 Z까지의 믿음직한 가이드입니 테스트는 친절하고 웃는 동료와 함께 일하는 것이 즐거울 수 있는 기회이고, 적은 투자로 큰 가치를 제공하는 것입니다. 과학은 우리에게 두 개의 뇌 시스템이 있다고 말합니다. 빈 도로에서 자동차를 운전하는 등의 간편한 활동에 사용되는 시스템 1, 그리고 수학 방정식을 푸는 것과 같이 복잡하고 의식적인 연산을 위한 시스템 2. 테스트 코드를 볼 때 수학 문제를 푸는 것 같은게 아닌, HTML 문서를 수정하는 것만 큼 쉬워야하는 시스템 1에 맞게 테스트를 설계하십시오. -선택적인 체리픽 기술, 툴 그리고 비용-효율적이고 뛰어난 ROI를 제공하는 테스트 대상 선정으로 목적을 이러한 달성할 수 있습니다. 필요한 만큼의 테스트, 융통성 있게 유자하려는 노력, 때로는 애자일함과 단순성을 위해 일부 테스트와 신뢰성을 포기하는 것도 가치가 있습니다. +선택적인 체리픽 기술, 툴 그리고 비용-효율적이고 뛰어난 ROI를 제공하는 테스트 대상 선정으로 이러한 목적을 달성할 수 있습니다. 필요한 만큼의 테스트, 융통성 있게 유지하려는 노력, 때로는 애자일함과 단순성을 위해 일부 테스트와 신뢰성을 포기하는 것도 가치가 있습니다. ![alt text](/assets/headspace.png "우리 머리속은 부가적인 복잡한 것들을 생각할 여유가 없습니다.") @@ -295,54 +295,55 @@ it("화이트박스 테스트: 내부 method가 VAT 0을 받으면 0을 반환

    -## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies +## ⚪ ️ 1.5 올바른 테스트 더블 선택: Stub과 Spy를 위한 Mock을 피하십시오. -:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide an immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **이렇게 해라:** 테스트 더블은 어플리케이션 내부에 연결되어 있기때문에 필요악이지만 일부는 엄청난 가치를 제공합니다([테스트 더블에 대한 알림: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). -Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a smell of white-box testing. +테스트 더블을 사용하기 전에 간단한 질문: 요구사항 문서에 있거나 있을 수 있는 기능을 테스트하는 데 테스트 더블을 사용합니까? 만약 아니라면 화이트박스 테스트 낌새가 보입니다. -For example, if you want to test what your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently +예를 들어, 결제 서비스가 중단되었을 때 앱이 적절하게 작동하는 것을 테스트하려는 경우, 테스트중인 단위가 올바른 값을 반환하도록, 결제 서비스를 stub하고 '응답 없음' 반환을 트리거 할 수 있습니다. +이것은 특정 시나리오에서 애플리케이션의 동작/응답/결과를 확인합니다. 그리고 spy를 사용하여 해당 서비스가 중단되었을 때 메일이 보내지는지를 assert 할 수 있습니다. 이것은 다시 요구사항 문서에 있을 수 있는 행동에 대한 점검입니다(결제가 저장되지 않으면 메일은 보낸다). 반대로, 결제 서비스를 mock 하고 올바른 JavaScript 타입으로 호출 되었는지를 확인한다면 - 당신의 테스트는 애플리케이션 기능에 전혀 영향을 받지 않고 자주 변경될 수 있는 내부 구현에 초점을 둔 경우입니다.
    - -❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend +❌ **그렇지 않으면:** 코드를 리펙토링 할 때, 모든 mock을 찾아서 수정해야 합니다. 테스트가 도움이 아닌 부담이 됩니다.
    -
    Code Examples +
    코드 예제
    -### :thumbsdown: Anti-pattern example: Mocks focus on the internals +### :thumbsdown: 올바르지 않은 예: 내부에 초점을 둔 mock + ![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg "Examples with Mocha & Chai") + ```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 +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:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals +### :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 +it("유효한 제품을 삭제하려고 할 때, 메일을 보낸다", async () => { + // 이미 제품을 추가했다고 가정 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) + // 좋음: 우리는 내부를 다루는가? 그렇다, 그러나 요구사항(이메일을 보낸다)에 대한 테스트의 side-effect이다. }); ```
    - -

    ## ⚪ ️1.6 Don’t “foo”, use realistic input data From da13678a605d06bf7a01fdfa66fc4b548edbb97d Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Thu, 5 Sep 2019 23:30:39 -0300 Subject: [PATCH 053/502] First steps First steps to translate this guide to pt-br --- readme-pt-br.md | 2025 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2025 insertions(+) create mode 100644 readme-pt-br.md diff --git a/readme-pt-br.md b/readme-pt-br.md new file mode 100644 index 00000000..ca49910c --- /dev/null +++ b/readme-pt-br.md @@ -0,0 +1,2025 @@ + + +
    + +# 👇 Por que este guia pode levar suas habilidades de teste para o próximo nível + +
    + +## 📗 45+ boas práticas: Super abrangente e exaustivo +Este é um guia para a confiabilidade JavaScript & Node.js da A-Z. Ele resume e organiza para você dezenas das melhores publicações, livros, ferramentas e postagens de blogs que o mercado tem a oferecer + + +## 🚢 Avançado: vai 10.000 milhas além do básico +Entre em uma jornada que vai muito além do básico, para tópicos avançados como testes em produção, testes de mutação, testes baseados em propriedades e muitas outras ferramentas estratégicas e profissionais. Se você ler todas as palavras deste guia, é provável que suas habilidades de teste superem a média + + +## 🌐 Full-stack: front, backend, CI(Integração Contínua), qualquer coisa +Comece entendendo as práticas de teste onipresentes que são a base para qualquer camada de aplicativo. Em seguida, mergulhe na sua área de escolha: front-end/UI, back-end, CI(Integração Contínua) ou talvez todos eles? + +
    + +### Escrito por Yoni Goldberg +* Um consultor JavaScript & Node.js +* 👨‍🏫 [Minha oficina de testes](https://www.testjavascript.com) - aprenda sobre [meus workshops](https://www.testjavascript.com) na Europe & Estados Unidos +* [Me siga no twitter ](https://twitter.com/goldbergyoni/) +* Venha me ouvir falar em [LA](https://js.la/), [Verona](https://2019.nodejsday.it/), [Kharkiv](https://kharkivjs.org/), [free webinar](https://zoom.us/webinar/register/1015657064375/WN_Lzvnuv4oQJOYey2jXNqX6A). Eventos futuros TBD +* [Newsletter informativo de qualidade sobre JavaScript](https://testjavascript.com/newsletter/) - insights e conteúdo apenas em assuntos estratégicos + + +

    + +## `Índice` + +#### [`Seção 0: A Regra de ouro`](#section-0️⃣-the-golden-rule) + +Um único conselho que inspira todos os outros (1 marcador especial) + +#### [`Section 1: The Test Anatomy`](#section-1-the-test-anatomy-1) + +The foundation - structuring clean tests (12 bullets) + +#### [`Section 2: Backend`](#section-2️⃣-backend-testing) + +Writing backend and Microservices tests efficiently (8 bullets) + +#### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) + +Writing tests for web UI including component and E2E tests (11 bullets) + +#### [`Section 4: Measuring Tests Effectiveness`](#section-4️⃣-measuring-test-effectiveness) + +Watching the watchman - measuring test quality (4 bullets) + +#### [`Section 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) + +Guidelines for CI in the JS world (9 bullets) + + +

    + + +# Section 0️⃣: The Golden Rule + +
    + +## ⚪️ 0. The Golden Rule: Design for lean testing + +:white_check_mark: **Do:** +Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. + +Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. + +The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells we have two brain systems: system 1 which is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should *feel* as easy as modifying an HTML document and not like solving 2X(17 × 24). + +This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. + +![alt text](/assets/headspace.png "We have no head room for additional complexity") + +Most of the advice below are derivatives of this principle. + +### Ready to start? + + +

    + +# Section 1: The Test Anatomy + +
    + +## ⚪ ️ 1.1 Include 3 parts in each test name + +:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: + +(1) What is being tested? For example, the ProductsService.addNewProduct method + +(2) Under what circumstances and scenario? For example, no price is passed to the method + +(3) What is the expected result? For example, the new product is not approved + +
    + + +❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? + +
    + +**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +
    Code Examples + +
    + +### :clap: Doing It Right Example: A test name that constitutes 3 parts + +![](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: Doing It Right Example: A test name that constitutes 3 parts +![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") + +
    + +

    + +## ⚪ ️ 1.2 Structure tests by the AAA pattern + +:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain CPU on understanding the test plan: + +1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code + +2nd A - Act: Execute the unit under test. Usually 1 line of code + +3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code + + +
    + + +❌ **Otherwise:** Not only you spend long daily hours on understanding the main code, now also what should have been the simple part of the day (testing) stretches your brain + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: A test structured with the AAA pattern + +![](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 Jest") + +```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: Anti Pattern Example: No separation, one bulk, harder to interpret + +```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 Describe expectations in a product language: use BDD-style assertions + +:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write an imperative code that is packed with conditional logic the reader is thrown away to an effortful mental mood. In that sense, code the expectation in a human-like language, declarative BDD style using expect or should and not using custom code. If Chai & Jest don’t include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +
    + + +❌ **Otherwise:** The team will write less test and decorate the annoying ones with .skip() + +
    + +
    Code Examples
    + +![](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: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story + +```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}); + + const 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: Doing It Right Example: Skimming through the following declarative test is a breeze + + +```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 Stick to black-box testing: Test only public methods + +:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API deliver the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as behavioral testing. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine- this dramatically increases the maintenance burden +
    + + +❌ **Otherwise:** Your test behaves like the [child who cries wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shoot out loud false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday a real bug will get ignored… + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A test case is testing the internals for no good reason +![](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 + calculateVAT(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; + } +} + + +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 Choose the right test doubles: Avoid mocks in favor of stubs and spies + +:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide an immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). + +Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a smell of white-box testing. + +For example, if you want to test what your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently +
    + + +❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-pattern example: Mocks focus on the internals +![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg + "Examples with Mocha & Chai") +```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:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals + +```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) +}); +``` + +
    + + + +

    + +## ⚪ ️1.6 Don’t “foo”, use realistic input data + +:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not instead) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? see next bullet (property-based testing). +
    + + +❌ **Otherwise:** All your development testing will falsely seem green when you use synthetic inputs like “Foo” but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” + + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data + +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg + "Examples with Jest") + + +```javascript +const addProduct = (name, price) =>{ + const productNameRegexNoSpace = /^\S*$/;//no white-space allowd + + 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:Doing It Right Example: Randomizing realistic input +```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 Test many input combinations using Property-based testing + +:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +
    + + +❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Testing many input permutations with “mocha-testcheck” + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with Jest") + +```javascript +require('mocha-testcheck').install(); +const {expect} = require('chai'); + +describe('Product service', () => { + describe('Adding new', () => { + //this will run 100 times with different random properties + check.it('Add new product with random yet valid properties, always successful', + gen.int, gen.string, (id, name) => { + expect(addNewProduct(id, name).status).to.equal('approved'); + }); + }) +}); + +``` + +
    + + + + +

    + +## ⚪ ️ 1.8 If needed, use only short & inline snapshots + +:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. + +On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much + +It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +
    + +❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code + +![](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 < /DisplayPage>) +.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: Doing It Right Example: Expectations are visible and focused +```javascript +it('When visiting TestJavaScript.com home page, a menu is displayed', () => { +//Arrange + +//Act +receivedPage tree = renderer +.create( Test JavaScript < /DisplayPage>) +.toJSON(); + +//Assert + +const menu = receivedPage.content.menu; +expect(menu).toMatchInlineSnapshot(` +
      +
    • Home
    • +
    • About
    • +
    • Contact
    • +
    +`); +}); +``` + +
    + + +

    + +## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with Jest") + +```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 :[ +}); + +``` +
    + +### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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 Don’t catch errors, expect them +:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations + +A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +
    + + +❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong + + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with Jest") + +```javascript +it("When no product name, it throws error 400", async() => { +let errorWeExceptFor = null; +try { + const result = await addNewProduct({name:'nest'});} +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: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM + +```javascript +it.only("When no product name, it throws error 400", async() => { + expect(addNewProduct)).to.eventually.throw(AppError).with.property('code', "InvalidInput"); +}); + +``` + +
    + + + + +

    + +## ⚪ ️ 1.11 Tag your tests + +:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +
    + + +❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) + +![](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 Other generic good testing hygiene +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +
    + + +❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades + +

    + + +# Section 2️⃣: Backend Testing + +## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid + +:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? + +Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. + +It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks + +A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] + +
    + + +❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘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 Component testing might be your best affair + +:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. + +Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +
    + + +❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) + +![](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)") + +
    + +

    + +## ⚪ ️2.3 Ensure new releases don’t break the API using + +:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +
    + + +❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: + +![](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 Test your middlewares in isolation + +:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +
    + + +❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests + + +
    + +
    Code Examples + +
    + +### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine + +![](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 Measure and refactor using static analysis tools +:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) + +Credit:: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + + +❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: + +![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg + "Examples with CodeClimate") + +![alt text](assets/bp-16-yoni-goldberg-quality.png " CodeClimat, a commercial tool that can identify complex methods:") + +
    + + + + +

    + +## ⚪ ️ 2.6 Check your readiness for Node-related chaos +:white_check_mark: **Do:** Weirdly, most software testings are about logic & data only, but some of the worst things that happen (and are really hard to mitigate ) are infrastructural issues. For example, did you ever test what happens when your process memory is overloaded, or when the server/process dies, or does your monitoring system realizes when the API becomes 50% slower?. To test and mitigate these type of bad things — [Chaos engineering](https://principlesofchaos.org/) was born by Netflix. It aims to provide awareness, frameworks and tools for testing our app resiliency for chaotic issues. For example, one of its famous tools, [the chaos monkey](https://github.com/Netflix/chaosmonkey), randomly kills servers to ensure that our service can still serve users and not relying on a single server (there is also a Kubernetes version, [kube-monkey](https://github.com/asobti/kube-monkey), that kills pods). All these tools work on the hosting/platform level, but what if you wish to test and generate pure Node chaos like check how your Node process copes with uncaught errors, unhandled promise rejection, v8 memory overloaded with the max allowed of 1.7GB or whether your UX stays satisfactory when the event loop gets blocked often? to address this I’ve written, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) which provides all sort of Node-related chaotic acts +
    + + +❌ **Otherwise:** No escape here, Murphy’s law will hit your production without mercy + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: : Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to 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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with 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 :[ +}); + +``` +
    + +### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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️⃣: Frontend Testing + +## ⚪ ️ 3.1. Separate UI from functionality + +:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI + + +
    + +❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Separating out the UI details + +![](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: Anti Pattern Example: Assertion mix UI details and data +```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 HTML elements based on attributes that are unlikely to change + +:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed + +
    + +❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing + +![](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: Anti-Pattern Example: Relying on 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 Whenever possible, test with a realistic and fully rendered component + +:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake + +With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children + +
    + +❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Working realstically with a fully rendered component + +![](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: Anti-Pattern Example: Mocking the reality with 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 Don't sleep, use frameworks built-in support for async events. Also try to speed things up + +:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +
    + +❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) + +![](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 +// 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: Doing It Right Example: Testing library that waits for DOM elements + +```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: Anti-Pattern Example: custom sleep code +```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. Watch how the content is served over the network + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg + "Examples with Lighthouse") + +✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 + +
    + +❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration + +
    + +
    Code Examples + +### :clap: Doing It Right Example: Lighthouse page load inspection report + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + + +
    + + +
    + +## ⚪ ️ 3.6 Stub flakky and slow resources like backend APIs + +:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests + +
    + +❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower + + +
    + +
    Code Examples + +
    + +### :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%20Jest-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 Have very few end-to-end tests that spans the whole system + +:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment + +
    + +❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very differnt than expected + +
    + +## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials + +:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). + +
    + +❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Logging-in before-all and not 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 Have one E2E smoke test that just travels across the site map + +:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector + +
    + +❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Smoke travelling across all 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 Expose the tests as a live collaborative document + +:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. + +❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cocumber-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: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + + +
    + + + + +## ⚪ ️ 3.11 Detect visual issues with automated tools + + +:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS]([https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue + +
    + +❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? + + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A typical visual regression - right content that is served badly + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + + +### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots + +![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg + "Using Cypress to illustrate the idea") + +``` +​# 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: Doing It Right Example: Using Applitools to get snapshot comaprison and other advanced features + +![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg + "Using Cypress to illustrate the idea") ![](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️⃣: Measuring Test Effectiveness + +

    + +## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number + +:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. + +Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets + +
    + + +❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down + + +
    + +
    Code Examples + +
    + +### :clap: Example: A typical coverage report +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: Doing It Right Example: Setting up coverage per component (using Jest) + +![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg + "Using Cypress to illustrate the idea") + +![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest) + +
    + + + +

    + +## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities + +:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +
    + + +❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from + + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: 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) + +
    + + +

    + +## ⚪ ️ 4.3 Measure logical coverage using mutation testing + +:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. + +Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: + +(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations + +(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. + +Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +
    + + +❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: 100% coverage, 0% testing + +![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg + "Using Cypress to illustrate the idea") +```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({asignee: "John@mailer.com",price: 120}); +});//Triggers 100% code coverage, but it doesn't check anything + +``` +
    + +### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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 Preventing test code issues with Test linters + +:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) + +
    + + +❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation + + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A test case full of errors, luckily all are caught by 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 and Other Quality Measures + +

    + +## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues + +:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash._map(…) +
    + + +❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day + + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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 Shorten the feedback loop with local developer-CI + +:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. + +Practically, some CI vendors (Example: [CircleCI load CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +
    + + +❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new 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 Perform e2e testing over a true production-mirror + +:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. + +The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +
    + + +❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated + + +
    + +
    Code Examples + +
    + +### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([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 Parallelize test execution +:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes + + +❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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 Stay away from legal issues using license and plagiarism check +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights + +❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues + + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: +```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 Constantly inspect for vulnerable dependencies +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like license check and plagiarism check (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights +
    + + +❌ **Otherwise:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build + + +
    + +
    Code Examples + +
    + +### :clap: Example: NPM Audit result +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + + + + +

    + +## ⚪ ️5.7 Automate dependency updates +:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: + +(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. + +(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). + +An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +
    + + +❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky + + +
    + +
    Code Examples + +
    + +### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions +![alt text](assets/bp-27-yoni-goldberg-npm.png "Nncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions") + + +
    + + +

    + +## ⚪ ️ 5.8 Other, non-Node related, CI tips +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +
    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
    +
    + + +❌ **Otherwise:** You‘ll miss years of wisdom + +

    + +## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions +:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +
    + + +❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? + + +
    + +
    Code Examples + +
    + +### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +
    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 500 fortune corporates 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) + +
    + +**Workshop:** 👨‍🏫 Want to learn all these practices and techniques at your offices (Europe & USA)? [Register here for my testing workshop](https://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 From 50f35fb38630fd86c468dfaf3c9fe51ddf29ed95 Mon Sep 17 00:00:00 2001 From: devori Date: Fri, 6 Sep 2019 15:20:35 +0900 Subject: [PATCH 054/502] Translation-ko_KR(1.6) --- readme.kr.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index be8617d7..3c61d2cd 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -346,22 +346,22 @@ it("유효한 제품을 삭제하려고 할 때, 메일을 보낸다", async ()

    -## ⚪ ️1.6 Don’t “foo”, use realistic input data +## ⚪ ️1.6 의미없는 인풋 데이터를 사용하지 말고, 실제와 같은 인풋 데이터를 사용해라 -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not instead) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? see next bullet (property-based testing). +:white_check_mark: **이렇게 해라:** 흔히 제품의 버그들은 매우 특수한 인풋데이터를 통해 나타납니다 - 테스트 인풋이 혈실적일 수록 버그를 조기에 발견할 가능성이 높아집니다. 실제 데이터와 다양성 및 형태가 유사한 데이터를 생성해 주는 [Faker](https://www.npmjs.com/package/faker) 같은 전용 라이브러리들을 사용하십시오. 이런 라이브러리들은 실제같은 전화번호, 사용자 이름, 신용카드, 회사명 그리고 심지어 'lorem ipsum'같은 문자등을 생성할 수도 있습니다. 당신은 가상의 데이터를 사용하여 테스트(단위 테스트 위에서)를 무작위화 하거나 심지어 실제 환경으로부터의 실제 데이터를 임포트 할수도 있습니다. 다음 단계를 얻기를 원하십니까? 그렇다면 아래로 가십시오 (property-based testing).
    -❌ **Otherwise:** All your development testing will falsely seem green when you use synthetic inputs like “Foo” but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **그렇지 않다면:** "Foo"와 같은 인풋을 사용하면 당신의 모든 테스트가 모두 통과한것 처럼 표시되지만, 실제 환경에서는 해커가 “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” 같은 인풋을 전달해 실패 할수도 있습니다.
    -
    Code Examples +
    코드 예제
    -### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data +### :thumbsdown: 올바르지 않은 예: 현실적이지 않은 데이터 때문에 통과하는 테스트 ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -369,34 +369,33 @@ it("유효한 제품을 삭제하려고 할 때, 메일을 보낸다", async () ```javascript const addProduct = (name, price) =>{ - const productNameRegexNoSpace = /^\S*$/;//no white-space allowd + const productNameRegexNoSpace = /^\S*$/;// 공백은 허용되지 않음 if(!productNameRegexNoSpace.test(name)) - return false;//this path never reached due to dull input + return false;//도달하지 않는 곳 //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 +test("잘못된 예제: 유효한 속성과 함께 제품을 추가한다면, 성공을 얻는다.", async () => { + //모든 테스트에서 false 가 리턴되지 않는 "Foo" 인풋을 사용 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:Doing It Right Example: Randomizing realistic input +### :clap:올바른 예: 무작위한 현실적인 인풋Randomizing realistic input ```javascript -it("Better: When adding new valid product, get successful confirmation", async () => { +it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.", async () => { const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); - //Generated random input: {'Sleek Cotton Computer', 85481} + //생성된 무작위 인풋: {'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! + //테스트는 실패한다, 무작위 인풋은 우리가 계획하지 않은 일이 일어나도록 만든다. + //우리는 조기에 버그를 발견했다! }); ``` From 71e7813a092af0ec19fa930c7239510e6c5d9e55 Mon Sep 17 00:00:00 2001 From: devori Date: Fri, 6 Sep 2019 15:23:04 +0900 Subject: [PATCH 055/502] fix typo remove english sentence that translated into ko-KR --- readme.kr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.kr.md b/readme.kr.md index 3c61d2cd..927f5871 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -388,7 +388,7 @@ test("잘못된 예제: 유효한 속성과 함께 제품을 추가한다면, ```
    -### :clap:올바른 예: 무작위한 현실적인 인풋Randomizing realistic input +### :clap:올바른 예: 무작위한 현실적인 인풋 ```javascript it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.", async () => { const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); From b6182259d5fe7659def70f315b56ad5c66544dc6 Mon Sep 17 00:00:00 2001 From: sury Date: Fri, 6 Sep 2019 15:40:28 +0900 Subject: [PATCH 056/502] translation 1.7 ko/kr --- readme.kr.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 927f5871..d8c03f0d 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -406,13 +406,15 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",

    -## ⚪ ️ 1.7 Test many input combinations using Property-based testing +## ⚪ ️ 1.7  프로퍼티 기반(Property-based) 테스트를 통해 다양한 인풋 값 조합으로 테스를 하십시오. -:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +:white_check_mark: **이렇게 해라:** 우리는 일반적으로 적은 수의 인풋 샘플 데이터를 가지고 테스트를 합니다. 심지어 인풋 데이터 형식이 실제 데이터와 비슷할 때에도 다음과 같이 제한된 인풋 조합으로만 테스트를 커버합니다.(method(‘’, true, 1), method(“string” , false” , 0)) 하지만, 운영시에는 5개의 파라미터를 가지는 API는 수 천 개의 다른 조합의 파라미터로 호출 될 수 있고, 이 중 하나가 우리의 시스템을 다운시킬 수도 있습니다. 그렇다면 만약 1000 가지 조합의 인풋값을 자동으로 생성하고 올바른 응답을 반환하지 못하는 인풋값을 찾아내는 단일 테스트를 작성할 수 있다면 어떨까요? +프로퍼티 기반 테스트는 유닛 테스트에 모든 가능한 인풋 조합을 사용하여 생각하지 못 한 버그를 찾을 확률을 높여줍니다. 예를들어, 다음의 메소드가 주어졌을 때 — addNewProduct(id, name, isDiscount) — 프로퍼티 기반 테스트 라이브러리들은 다양한 파라미터 (number, string, boolean) 조합으로 - (1, “iPhone”, false), (2, “Galaxy”, true) - 이 메소드를 호출합니다. [js-verify](https://github.com/jsverify/jsverify) 나 [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation) 같은 라이브러리를 지원하는 테스트 러너들 (Mocha, Jest, etc) 중 당신이 가장 선호하는 방법을 통해 프로퍼티 기반 테스트를 할 수 있습니다. +업데이트 : Nicolas Dubien가 코멘트를 통해 더 많은 부가적인 기능들을 제공하고 활발하게 유지보수되고 있는 라이브러리 [fast-check](https://github.com/dubzzz/fast-check#readme)를 추천해 주었습니다.
    -❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs +❌ **그렇지 않으면:** 의심할 여지 없이 당신은 오직 코드가 잘 동작하는 테스트 인풋을 사용할 것입니다. 불행하게도 이러한 방식은 버그를 찾는 도구로써의 테스트 효율성을 떨어뜨릴 것 입니다.
    @@ -421,7 +423,7 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",
    -### :clap: Doing It Right Example: Testing many input permutations with “mocha-testcheck” +### :clap: 올바른 예: “mocha-testcheck”를 사용하여 다양한 인풋 조합으로 테스트 하십시오. ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") @@ -432,7 +434,7 @@ const {expect} = require('chai'); describe('Product service', () => { describe('Adding new', () => { - //this will run 100 times with different random properties + //서로 다른 무작위 값으로 100회 호출됩니다. check.it('Add new product with random yet valid properties, always successful', gen.int, gen.string, (id, name) => { expect(addNewProduct(id, name).status).to.equal('approved'); From d9b3d87098fdd23b024d74afc91355550189cbd3 Mon Sep 17 00:00:00 2001 From: sury Date: Fri, 6 Sep 2019 15:46:03 +0900 Subject: [PATCH 057/502] fix 1.7 tranlation to Korean --- readme.kr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.kr.md b/readme.kr.md index d8c03f0d..3ea34a81 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -419,7 +419,7 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",
    -
    Code Examples +
    코드 예제
    From e3bf782b5694ffadb6996846d6fca67128862ba9 Mon Sep 17 00:00:00 2001 From: sury Date: Fri, 6 Sep 2019 16:01:06 +0900 Subject: [PATCH 058/502] =?UTF-8?q?fix=20used=20word=20(=EB=8B=A8=EC=9D=BC?= =?UTF-8?q?=20->=20=EB=8B=A8=EC=9C=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.kr.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 3ea34a81..317d1671 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -406,10 +406,10 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",

    -## ⚪ ️ 1.7  프로퍼티 기반(Property-based) 테스트를 통해 다양한 인풋 값 조합으로 테스를 하십시오. +## ⚪ ️ 1.7  프로퍼티 기반(Property-based) 테스트를 통해 다양한 인풋 값 조합으로 테스트를 하십시오. -:white_check_mark: **이렇게 해라:** 우리는 일반적으로 적은 수의 인풋 샘플 데이터를 가지고 테스트를 합니다. 심지어 인풋 데이터 형식이 실제 데이터와 비슷할 때에도 다음과 같이 제한된 인풋 조합으로만 테스트를 커버합니다.(method(‘’, true, 1), method(“string” , false” , 0)) 하지만, 운영시에는 5개의 파라미터를 가지는 API는 수 천 개의 다른 조합의 파라미터로 호출 될 수 있고, 이 중 하나가 우리의 시스템을 다운시킬 수도 있습니다. 그렇다면 만약 1000 가지 조합의 인풋값을 자동으로 생성하고 올바른 응답을 반환하지 못하는 인풋값을 찾아내는 단일 테스트를 작성할 수 있다면 어떨까요? -프로퍼티 기반 테스트는 유닛 테스트에 모든 가능한 인풋 조합을 사용하여 생각하지 못 한 버그를 찾을 확률을 높여줍니다. 예를들어, 다음의 메소드가 주어졌을 때 — addNewProduct(id, name, isDiscount) — 프로퍼티 기반 테스트 라이브러리들은 다양한 파라미터 (number, string, boolean) 조합으로 - (1, “iPhone”, false), (2, “Galaxy”, true) - 이 메소드를 호출합니다. [js-verify](https://github.com/jsverify/jsverify) 나 [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation) 같은 라이브러리를 지원하는 테스트 러너들 (Mocha, Jest, etc) 중 당신이 가장 선호하는 방법을 통해 프로퍼티 기반 테스트를 할 수 있습니다. +:white_check_mark: **이렇게 해라:** 우리는 일반적으로 적은 수의 인풋 샘플 데이터를 가지고 테스트를 합니다. 심지어 인풋 데이터 형식이 실제 데이터와 비슷할 때에도 다음과 같이 제한된 인풋 조합으로만 테스트를 커버합니다.(method(‘’, true, 1), method(“string” , false” , 0)) 하지만, 운영시에는 5개의 파라미터를 가지는 API는 수 천 개의 다른 조합의 파라미터로 호출 될 수 있고, 이 중 하나가 우리의 시스템을 다운시킬 수도 있습니다. 그렇다면 만약 1000 가지 조합의 인풋값을 자동으로 생성하고 올바른 응답을 반환하지 못하는 인풋값을 찾아내는 단위 테스트를 작성할 수 있다면 어떨까요? +프로퍼티 기반 테스트는 단위 테스트에 모든 가능한 인풋 조합을 사용하여 생각하지 못 한 버그를 찾을 확률을 높여줍니다. 예를들어, 다음의 메소드가 주어졌을 때 — addNewProduct(id, name, isDiscount) — 프로퍼티 기반 테스트 라이브러리들은 다양한 파라미터 (number, string, boolean) 조합으로 - (1, “iPhone”, false), (2, “Galaxy”, true) - 이 메소드를 호출합니다. [js-verify](https://github.com/jsverify/jsverify) 나 [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation) 같은 라이브러리를 지원하는 테스트 러너들 (Mocha, Jest, etc) 중 당신이 가장 선호하는 방법을 통해 프로퍼티 기반 테스트를 할 수 있습니다. 업데이트 : Nicolas Dubien가 코멘트를 통해 더 많은 부가적인 기능들을 제공하고 활발하게 유지보수되고 있는 라이브러리 [fast-check](https://github.com/dubzzz/fast-check#readme)를 추천해 주었습니다.
    From a4b06bea9f8714b58fa2ad31a5492fbbc34982b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurelijus=20Ro=C5=BE=C4=97nas?= Date: Fri, 6 Sep 2019 14:35:24 +0300 Subject: [PATCH 059/502] Fixed mistype. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c459d651..3b2d801a 100644 --- a/readme.md +++ b/readme.md @@ -1338,7 +1338,7 @@ test('When no products exist, show the appropriate message', () => {
    -❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very differnt than expected +❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected
    From bd3d1630104fb750470080a4f7c428757796c799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurelijus=20Ro=C5=BE=C4=97nas?= Date: Fri, 6 Sep 2019 15:36:09 +0300 Subject: [PATCH 060/502] Added closing parentheses. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 3b2d801a..c03fdfa2 100644 --- a/readme.md +++ b/readme.md @@ -1948,7 +1948,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c ## ⚪ ️ 5.8 Other, non-Node related, CI tips :white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known -
    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
    +
    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

    From 0637e6e5a0d75d6f007bb55dbd66dfddb5b98f3a Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Sat, 7 Sep 2019 16:59:39 +0900 Subject: [PATCH 061/502] GPG signature test. --- readme.kr.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 317d1671..33ea8c9c 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -346,15 +346,14 @@ it("유효한 제품을 삭제하려고 할 때, 메일을 보낸다", async ()

    -## ⚪ ️1.6 의미없는 인풋 데이터를 사용하지 말고, 실제와 같은 인풋 데이터를 사용해라 +## ⚪ ️ 1.6 의미없는 인풋 데이터를 사용하지 말고, 실제와 같은 인풋 데이터를 사용해라 :white_check_mark: **이렇게 해라:** 흔히 제품의 버그들은 매우 특수한 인풋데이터를 통해 나타납니다 - 테스트 인풋이 혈실적일 수록 버그를 조기에 발견할 가능성이 높아집니다. 실제 데이터와 다양성 및 형태가 유사한 데이터를 생성해 주는 [Faker](https://www.npmjs.com/package/faker) 같은 전용 라이브러리들을 사용하십시오. 이런 라이브러리들은 실제같은 전화번호, 사용자 이름, 신용카드, 회사명 그리고 심지어 'lorem ipsum'같은 문자등을 생성할 수도 있습니다. 당신은 가상의 데이터를 사용하여 테스트(단위 테스트 위에서)를 무작위화 하거나 심지어 실제 환경으로부터의 실제 데이터를 임포트 할수도 있습니다. 다음 단계를 얻기를 원하십니까? 그렇다면 아래로 가십시오 (property-based testing). -
    +
    ❌ **그렇지 않다면:** "Foo"와 같은 인풋을 사용하면 당신의 모든 테스트가 모두 통과한것 처럼 표시되지만, 실제 환경에서는 해커가 “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” 같은 인풋을 전달해 실패 할수도 있습니다. -
    코드 예제 @@ -384,11 +383,12 @@ test("잘못된 예제: 유효한 속성과 함께 제품을 추가한다면, expect(addProductResult).toBe(true); //거짓된 성공: 공백을 포함하는 문자열을 사용하지 않았기 때문에 테스트는 성공한다. }); - ``` +
    ### :clap:올바른 예: 무작위한 현실적인 인풋 + ```javascript it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.", async () => { const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); @@ -401,9 +401,6 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",
    - - -

    ## ⚪ ️ 1.7  프로퍼티 기반(Property-based) 테스트를 통해 다양한 인풋 값 조합으로 테스트를 하십시오. @@ -411,12 +408,11 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.", :white_check_mark: **이렇게 해라:** 우리는 일반적으로 적은 수의 인풋 샘플 데이터를 가지고 테스트를 합니다. 심지어 인풋 데이터 형식이 실제 데이터와 비슷할 때에도 다음과 같이 제한된 인풋 조합으로만 테스트를 커버합니다.(method(‘’, true, 1), method(“string” , false” , 0)) 하지만, 운영시에는 5개의 파라미터를 가지는 API는 수 천 개의 다른 조합의 파라미터로 호출 될 수 있고, 이 중 하나가 우리의 시스템을 다운시킬 수도 있습니다. 그렇다면 만약 1000 가지 조합의 인풋값을 자동으로 생성하고 올바른 응답을 반환하지 못하는 인풋값을 찾아내는 단위 테스트를 작성할 수 있다면 어떨까요? 프로퍼티 기반 테스트는 단위 테스트에 모든 가능한 인풋 조합을 사용하여 생각하지 못 한 버그를 찾을 확률을 높여줍니다. 예를들어, 다음의 메소드가 주어졌을 때 — addNewProduct(id, name, isDiscount) — 프로퍼티 기반 테스트 라이브러리들은 다양한 파라미터 (number, string, boolean) 조합으로 - (1, “iPhone”, false), (2, “Galaxy”, true) - 이 메소드를 호출합니다. [js-verify](https://github.com/jsverify/jsverify) 나 [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation) 같은 라이브러리를 지원하는 테스트 러너들 (Mocha, Jest, etc) 중 당신이 가장 선호하는 방법을 통해 프로퍼티 기반 테스트를 할 수 있습니다. 업데이트 : Nicolas Dubien가 코멘트를 통해 더 많은 부가적인 기능들을 제공하고 활발하게 유지보수되고 있는 라이브러리 [fast-check](https://github.com/dubzzz/fast-check#readme)를 추천해 주었습니다. -
    +
    ❌ **그렇지 않으면:** 의심할 여지 없이 당신은 오직 코드가 잘 동작하는 테스트 인풋을 사용할 것입니다. 불행하게도 이러한 방식은 버그를 찾는 도구로써의 테스트 효율성을 떨어뜨릴 것 입니다. -
    코드 예제 @@ -441,14 +437,10 @@ describe('Product service', () => { }); }) }); - ```
    - - -

    ## ⚪ ️ 1.8 If needed, use only short & inline snapshots From 6e17cc3a7a5d5a3e9f51c507cdf637c74a2c4f60 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Mon, 9 Sep 2019 00:09:05 +0900 Subject: [PATCH 062/502] Translate into Korean 1.8 --- readme.kr.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 33ea8c9c..a79db949 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -443,30 +443,31 @@ describe('Product service', () => {

    -## ⚪ ️ 1.8 If needed, use only short & inline snapshots +## ⚪ ️ 1.8 필요한 경우 짧거나 인라인 스냅샷만 사용하십시오. -:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. +:white_check_mark: **이렇게 해라:** [스냅샷 테스트](https://jestjs.io/docs/en/snapshot-testing)가 필요한 경우 외부 파일이 아닌 테스트의 일부 ([인라인 스냅샷](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots))에 포함 된 짧고 집중된 스냅샷(3~7 라인)만 사용하십시오. 이 지침을 따르면 따로 설명이 필요없고 잘 깨지지 않는 테스트가 됩니다. -On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much +반면에, '고전적인 스냅샷' 튜토리얼 및 도구는 외부에 큰 파일(예: 구성 요소 랜더링 마크업, API JSON 결과)를 저장하고, 테스트를 실행할 때 마다 수신된 결과를 저장된 버전과 비교하기를 권장합니다. 예를 들어, 이것은 1,000 라인(우리가 절대 읽지 않고 추론하지 않을 3,000개의 데이터 값을 가진)의 코드를 우리 테스트에 암시적으로 연결할 수 있습니다. 왜 이것이 잘못 되었을까요? 이렇게하면 테스트에 실패할 1,000 가지 이유가 생깁니다. 한줄만 변경되어도 스냅샷이 유효하지 않게 되고, 이런일이 일어날 가능성이 높습니다. 얼마나 자주? 모든 공백, 주석에서 혹은 사소한 CSS/HTML 변경에 대해서. 뿐만 아니라 테스트 이름은 1,000 라인이 변경되지 않았는지를 나타내기 때분에, 실패에 대한 단서를 제공하지 않습니다. 또한 테스트 작성자가 긴 문서(검사하고 확인할 수 없는)를 받아들이게끔 합니다. 이 모든 것은 초점이 맞지않고 너무 많은 것을 달성하려는 모호하고 간절한 테스트 증상입니다. + +긴 외부 스냅샷이 허용되는 경우가 거의 없다는 점은 주목할 가치가 있습니다 - 데이터가 아닌 스키마를 assert 할 때(값 추출 및 필드에 집중) 또는 수신된 문서가 거의 변경되지 않는 경우 -It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes
    -❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **그렇지 않다면:** UI 테스트가 실패합니다. 코드가 문제없어 보이고 화면이 완벽한 픽셀을 렌더링합니다. 어떻게 되었습니까? 스냅샷 테스트에서 원본 문서와 현재 수신된 문서와의 차이점을 발견했습니다. 빈칸 하나가 마크 다운에 추가되었습니다...
    -
    Code Examples +
    코드 예제
    -### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code +### :thumbsdown: 올바르지 않은 예: 보이지 않는 2,000 라인의 코드를 우리 테스트에 연결 ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ```javascript -it('TestJavaScript.com is renderd correctly', () => { +it('TestJavaScript.com 이 올바르게 랜더링 된다.', () => { //Arrange @@ -477,16 +478,18 @@ const receivedPage = renderer //Assert expect(receivedPage).toMatchSnapshot(); -//We now implicitly maintain a 2000 lines long document -//every additional line break or comment - will break this test +// 이제 2,000 라인의 문서를 암묵적으로 유지합니다. +// 모든 줄바꿈 또는 주석이 테스트를 망가뜨립니다. }); ``` +
    -### :clap: Doing It Right Example: Expectations are visible and focused +### :clap: 올바른 예: expectation이 잘 보이고 집중된다. + ```javascript -it('When visiting TestJavaScript.com home page, a menu is displayed', () => { +it('TestJavaScript.com 홈페이지를 방문하면 메뉴가 보인다.', () => { //Arrange //Act @@ -509,7 +512,6 @@ expect(menu).toMatchInlineSnapshot(`
    -

    ## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test From 1822ed387a28d6f64d09f59bf8b0f6671e5f533e Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Mon, 9 Sep 2019 09:45:12 +0900 Subject: [PATCH 063/502] Translate into Korean 1.9 --- readme.kr.md | 55 +++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index a79db949..e9de9fbd 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -99,9 +99,9 @@ JavaScript 및 Node.js에 대한 A부터 Z까지의 믿음직한 가이드입니
    -**👇 주의:** 각 글에는 코드 예제가 있으며 때로는 이미지도 있습니다. 클릭하여 확장 +**👇 주의:** 각 글에는 예제 코드가 있으며 때로는 이미지도 있습니다. 클릭하여 확장 -
    코드 예제 +
    예제 코드
    @@ -149,7 +149,7 @@ describe('제품 서비스', function() {
    -
    코드 예제 +
    예제 코드
    @@ -203,7 +203,7 @@ test('프리미엄으로 분류해야 합니다.', () => {
    -
    코드 예제
    +
    예제 코드
    ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg @@ -260,7 +260,7 @@ it("관리자 요청이 들어오면 정렬된 관리자 목록만 결과에 포
    -
    코드 예제 +
    예제 코드
    @@ -309,7 +309,7 @@ it("화이트박스 테스트: 내부 method가 VAT 0을 받으면 0을 반환
    -
    코드 예제 +
    예제 코드
    @@ -356,7 +356,7 @@ it("유효한 제품을 삭제하려고 할 때, 메일을 보낸다", async ()
    -
    코드 예제 +
    예제 코드
    @@ -415,7 +415,7 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",
    -
    코드 예제 +
    예제 코드
    @@ -457,7 +457,7 @@ describe('Product service', () => {
    -
    코드 예제 +
    예제 코드
    @@ -514,51 +514,50 @@ expect(menu).toMatchInlineSnapshot(`

    -## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test - -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) -
    +## ⚪ ️ 1.9 테스트 데이터를 글로벌로 하지말고 테스트별로 따로 추가하라. +:white_check_mark: **이렇게 해라:** 황금률에 따르면(섹션 0), 각 테스트는 커플링을 방지하고 테스트 흐름을 쉽게 추론하기 위해 자체 DB 데이터를 추가하고 실행해야 합니다. 실제로 성능 향상(테스트를 실행하기 전에 DB 데이터를 준비(['테스트 픽스쳐'라고도 합니다](https://en.wikipedia.org/wiki/Test_fixture)))을 위해 이를 위반하는 테스터들이 많습니다. 성능은 실제로 유효한 문제이지만 완화될 수 있습니다(2.2 컴포넌트 테스트 참고). 그러나 테스트 복잡성은 대부분의 다른 고려사항들을 통제해야 하는 고통을 수반합니다. 각 테스트에 필요한 DB 레코드를 명시적으로 추가하고, 해당 데이터에 대해서만 테스트를 수행하십시오. 성능이 중요한 문제가 되는 경우 - 데이터를 변경하지 않는 테스트 모음(예: 쿼리)에 대해서 데이터를 준비하는 형태로 타협할 수 있습니다. -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +
    +❌ **그렇지 않으면:** 테스트 실패, 배포 중단으로 팀원들이 귀중한 시간을 소비할 것입니다. 버그가 있습니까? 조사해보니 '없습니다' - 두 테스트에서 동일한 테스트 데이터를 변겨안 것으로 보입니다.
    -
    Code Examples +
    예제 코드
    -### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :thumbsdown: 올바르지 않은 예: 테스트는 독립적이지 않으며 글로벌 훅에 의한 DB 데이터에 의존 ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") ```javascript before(() => { - //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + // 사이트 및 관리자 데이터를 DB에 추가. 데이터는 어디에 있습니까? 외부에. 외부 JSON 또는 마이그레이션 프레임워크에 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 +it("사이트 이름을 업데이트 할 때, 성공을 확인한다.", async () => { + // 사이트 이름 "portal"이 존재한다는 것을 알고있습니다. 시드파일에서 봤습니다. 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 +it("사이트 이름을 쿼리할 때, 올바른 사이트 이름을 얻는다.", async () => { + // 사이트 이름 "portal"이 존재한다는 것을 알고있습니다. 시드파일에서 봤습니다. const siteToCheck = await SiteService.getSiteByName("Portal"); - expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ + expect(siteToCheck.name).to.be.equal("Portal"); // 실패! 이전 테스트에서 이름이 변경되었습니다. ㅠㅠ }); - ``` +
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: 올바른 예: 우리는 테스트 내부에만 머물 수 있으며, 각 테스트는 자체 데이터 세트에서 동작합니다. ```javascript -it("When updating site name, get successful confirmation", async () => { - //test is adding a fresh new records and acting on the records only +it("사이트 이름을 업데이트 할 때, 성공을 확인한다.", async () => { + // 테스트는 새로운 레코드를 새로 추가하고 해당 레코드에 대해서만 동작합니다. const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); @@ -567,13 +566,11 @@ it("When updating site name, get successful confirmation", async () => { expect(updateNameResult).to.be(true); }); - ```
    - -
    +

    ## ⚪ ️ 1.10 Don’t catch errors, expect them :white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations From 9847377306f0afb2fef4a094a04286c56ca182fa Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Wed, 11 Sep 2019 00:24:47 +0900 Subject: [PATCH 064/502] Translate into Korean 1.10 to 1.11 --- readme.kr.md | 76 +++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index e9de9fbd..fce412a0 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -572,98 +572,88 @@ it("사이트 이름을 업데이트 할 때, 성공을 확인한다.", async ()

    -## ⚪ ️ 1.10 Don’t catch errors, expect them -:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations +## ⚪ ️ 1.10 오류를 catch 하지말고 expect 하십시오. -A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user -
    +:white_check_mark: **이렇게 해라:** 오류를 발생시키는 입력값을 assert 할 때, try-catch-finally를 사용하고 catch 블럭에서 assert 하는게 맞아 보일수도 있습니다. 아래 예는 테스트 의도와 결과 expectation을 숨기는 어색하고 장황한 테스트 사례입니다. +보다 우아한 대안은 한줄짜리 Chai assertion을 사용하는 것 입니다: expect(method).to.throw (혹은 Jest: expect(method).toThrow()). 오류 유형을 알려주는 속성이 예외에 포함되어야 합니다. 그렇지 않고 일반적인 오류를 발생시키면 어플리케이션은 사용자에게 실망스러운 메시지를 표시하는 것 밖에 할 수 없습니다. -❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +
    +❌ **그렇지 않으면:** 무엇이 잘못되었는지 테스트 보고서(예: CI 보고서)에서 추론하기 어려울 것입니다.
    -
    Code Examples +
    예제 코드
    -### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch +### :thumbsdown: 올바르지 않은 예: try-catch로 오류가 존재한다고 assert 하는 긴 테스트 사례 ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") ```javascript -it("When no product name, it throws error 400", async() => { -let errorWeExceptFor = null; -try { - const result = await addNewProduct({name:'nest'});} -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 +it("제품명이 없으면 400 오류를 던진다.", async() => { + let errorWeExceptFor = null; + try { + const result = await addNewProduct({name:'nest'});} + catch (error) { + expect(error.code).to.equal('InvalidInput'); + errorWeExceptFor = error; + } + expect(errorWeExceptFor).not.to.be.null; + // 이 asserting이 실패하면, 테스트 결과에서 누락된 입력값에 대한 단어는 알 수 없고 + // 입력값이 null 이라는 것만 알 수 있습니다. }); - ``` +
    -### :clap: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM +### :clap: 올바른 예: QA나 PM이라도 쉽게 이해할 수 있고 읽기 쉬운 expectation ```javascript -it.only("When no product name, it throws error 400", async() => { +it.only("제품명이 없으면 400 오류를 던진다.", async() => { expect(addNewProduct)).to.eventually.throw(AppError).with.property('code', "InvalidInput"); }); - ```
    - - -

    -## ⚪ ️ 1.11 Tag your tests - -:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ -
    +## ⚪ ️ 1.11 테스트에 태깅하십시오. +:white_check_mark: **이렇게 해라:** 다른 테스트는 꼭 다른 시나리오에서 실행해야 합니다: 개발자가 파일을 저장하거나 커밋을 할 때 빠르고, IO가 많이 없는 테스트를 실행해야 합니다. 전체 end-to-end 테스트는 일반적으로 새로운 Pull Request가 제출되었을 때 실행됩니다. 등.. 이러한 경우에 #cold #api #sanity와 같은 키워드로 테스트에 태깅하면 테스트를 효율적으로 grep 할 수 있고, 원하는 하위세트를 호출할 수 있습니다. 예) Mocha를 이용해서 sanity 테스트 그룹만 실행하는 방법입니다: mocha - grep 'sanity' -❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests +
    +❌ **그렇지 앟으면:** 개발자가 작은 변경을 할 때마다 수십 개의 DB 쿼리를 수행하는 테스트를 포함한 모든 테스트를 실행한다면, 속도가 매우 느려져 개발자가 테스트를 수행하지 않게 만들 것입니다.
    -
    Code Examples +
    예제 코드
    -### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) +### :clap: 올바른 예: 테스트를 '#cold-test'로 태깅하면 테스트를 수행하는 사람이 빠른 테스트만 실행할 수 있습니다(IO를 수행하지 않고 개발자가 코딩하는 중에도 자주 실행할 수 있는 테스트 cold === quick). ![](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 +// 이 테스트는 빠르고(DB 없음) 현재 사용자/CI가 자주 실행할 수 있는 태그를 지정하고 있습니다. +describe('주문 서비스', function() { + describe('새 주문 추가 #cold-test #sanity', function() { + test('시나리오 - 통화가 제공되지 않음. 예외 - 기본 통화 사용 #sanity', function() { + // code logic here }); }); }); - - ```
    - - -

    ## ⚪ ️1.12 Other generic good testing hygiene From 49bdd5e52c43ee06e875ecc2874138d7a94738eb Mon Sep 17 00:00:00 2001 From: sury Date: Thu, 12 Sep 2019 11:50:06 +0900 Subject: [PATCH 065/502] translate into Korean 2.1 --- readme.kr.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index fce412a0..02b5d02a 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -670,32 +670,30 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr # Section 2️⃣: Backend Testing -## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid +## ⚪ ️2.1 당신의 테스트 포트폴리오를 풍부하게 하십시오: 단위 테스트와 피라미드를 넘어서세요. -:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **이렇게 해라:** 10년이 넘은 모델인 [테스트 피라미드](https://martinfowler.com/bliki/TestPyramid.html)는 세 가지 테스트 유형을 제시하고 대다수 개발자의 테스트 전략에 영향을 주는 훌륭한 모델입니다. 동시에, 몇 가지 반짝이는 새로운 테스트 기술들이 등장하였지만 모두 테스트 피라미드의 그림자 뒤로 사라졌습니다. 우리가 최근 10년간 보아 온 극적인 기술의 변화들(Microservices, cloud, serverless)을 고려할 때, 아주 오래된 모델 하나가 *모든* 어플리케이션 유형에 적합하다는 것이 가능한가요? 테스트 세계는 새로운 기술을 받아들이는 것을 고려하지 않나요? -Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. +오해는 하지 마세요. 2019 테스트 피라미드에서 TDD와 단위 테스트는 여전히 강력한 기술이고 아마도 많은 어플리케이션에 가장 어울리는 기술입니다. 다른 모델과 마찬가지로, 테스트 미라미드는 유용하지만 [그것이 항상 맞는 것은 아닙니다](https://en.wikipedia.org/wiki/All_models_are_wrong). 예를 들어, 어떤 IOT 어플리케이션을 생각해 봅시다. 이 어플리케이션은 다수의 이벤트를 Kafka/RabbitMQ 같은 메세지 버스로 보내고 다시 데이터 웨어하우스로 흘려보냅니다. 그리고 이 데이터들은 어떤 분석 UI에서 조회됩니다. 우리는 정말 우리의 테스트 예산의 50%를 통합 중심적(intergration-centric)이고 로직이 거의 없는 어플리케이션의 단위 테스트를 작성하는데 할애해야 할까요? 어플리케이션 유형들이 다양해질 수록(bots, crypto, Alexa-skills) 테스트 피라미드가 적합하지 않은 시나리오들을 발견할 가능성이 커집니다. -It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks +지금이 당신의 테스트 포트폴리오를 넓히고 더 많은 테스트 유형들에 익숙해질 시간입니다. (다음 총알에서 몇 가지 아이디어들을 제안합니다.) 테스트 피라미드 같은 모델들도 염두에 둘 뿐만 아니라 당신이 직면하고 있는 현실 세계의 문제들에 적합한 테스트 유형들을 찾으세요. ("우리 API 깨졌어. Consumer-driven contract 테스트 작성하자!" 처럼요.) 위험성 분석을 기반으로 포르폴리오를 구축하는 투자자처럼 당신의 테스트를 다양화하세요 - 문제가 발생할 수 있는 부분을 가늠하고 잠재적 위험성을 줄일 수 있는 예방 방법을 찾으세요. -A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] +주의 사항 : 소프트웨어 세계에서의 TDD 논쟁은 전형적인 잘못된 이분법입니다. 어떤 사람들은 TDD를 모든 곳에 적용하라고 주장하지만, 다른 일부는 TDD를 악마라고 생각합니다. 절대적으로 한쪽만 주장하는 사람들은 모두 틀렸습니다 :]
    -❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes - +❌ **그렇지 않으면:** 당신은 굉장한 RIO를 주는 몇 가지 툴들을 놓칠 것입니다. Fuzz, lint, mutation 테스트들은 단 10분만에 당신에게 가치를 제공할 수 있습니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘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’") +### :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) +예제: [YouTube: “Beyond Unit Tests: 5 Shiny Node.JS Test Types (2018)” (Yoni Goldberg)](https://www.youtube.com/watch?v=-2zP494wdUY&feature=youtu.be)
    From fd9ce8fd91a4d14e4278c9dd4a616f828bb126d8 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Sat, 14 Sep 2019 01:18:42 +0900 Subject: [PATCH 066/502] Translate into Korean 1.12 - Fix typo. --- readme.kr.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 02b5d02a..74855db3 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -656,21 +656,21 @@ describe('주문 서비스', function() {

    -## ⚪ ️1.12 Other generic good testing hygiene -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +## ⚪ ️ 1.12 일반적인 좋은 테스트 기법들 + +:white_check_mark: **이렇게 해라:** 이 글은 Node.js와 관련이 있거나 최소한 Node.js로 예를 들 수 있는 테스트 조언에 중점을두고 있습니다. 그러나 이번에는 Node.js가 아니지만 잘 알려진 팁들을 포함하고 있습니다. -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) -
    +[TDD 원칙](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/)을 배우고 연습하십시오 - 많은 사람들에게 매우 가치가 있지만, 자신의 스타일에 맞지 않을 수 있습니다. [실패-성공-리페토링 스타일](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html)로 코드 작성 전에 테스트를 작성하는 것을 고려하십시오. 버그를 발견하면 각 테스트에서 정확히 한 가지만 확인하도록 하십시오. 수정하기 전에 앞으로 이 버그를 발견 할 테스트를 작성하십시오. 테스트가 성공하기 전에 각 테스트가 한번 이상 실패하도록 하십시오. 테스트를 만족시키는 간단한 코드를 작성하여 빠르게 모듈을 시작하십시오 - 점신적으로 리펙토링하여 프로덕션 등급의 수준으로 가져가십시오. 환경(경로, OS 등)에 대한 종속성을 피하십시오. +
    -❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades +❌ **그렇지 않으면:** 수십 년 동안 수집 된 아주 소중한 조언을 놓치게 될 것입니다.

    +# 섹션 2️⃣: 백엔드 테스트 -# Section 2️⃣: Backend Testing - -## ⚪ ️2.1 당신의 테스트 포트폴리오를 풍부하게 하십시오: 단위 테스트와 피라미드를 넘어서세요. +## ⚪ ️ 2.1 당신의 테스트 포트폴리오를 풍부하게 하십시오: 단위 테스트와 피라미드를 넘어서세요. :white_check_mark: **이렇게 해라:** 10년이 넘은 모델인 [테스트 피라미드](https://martinfowler.com/bliki/TestPyramid.html)는 세 가지 테스트 유형을 제시하고 대다수 개발자의 테스트 전략에 영향을 주는 훌륭한 모델입니다. 동시에, 몇 가지 반짝이는 새로운 테스트 기술들이 등장하였지만 모두 테스트 피라미드의 그림자 뒤로 사라졌습니다. 우리가 최근 10년간 보아 온 극적인 기술의 변화들(Microservices, cloud, serverless)을 고려할 때, 아주 오래된 모델 하나가 *모든* 어플리케이션 유형에 적합하다는 것이 가능한가요? 테스트 세계는 새로운 기술을 받아들이는 것을 고려하지 않나요? @@ -682,8 +682,7 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr
    - -❌ **그렇지 않으면:** 당신은 굉장한 RIO를 주는 몇 가지 툴들을 놓칠 것입니다. Fuzz, lint, mutation 테스트들은 단 10분만에 당신에게 가치를 제공할 수 있습니다. +❌ **그렇지 않으면:** 당신은 굉장한 ROI를 주는 몇 가지 툴들을 놓칠 것입니다. Fuzz, lint, mutation 테스트들은 단 10분만에 당신에게 가치를 제공할 수 있습니다.
    @@ -699,12 +698,8 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr ![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "A test name that constitutes 3 parts") -
    - - -

    ## ⚪ ️2.2 Component testing might be your best affair From 3ef90bd495584c05ad402d141ff31351b004d3a8 Mon Sep 17 00:00:00 2001 From: Aaron <42848750+aaronshivers@users.noreply.github.com> Date: Sat, 14 Sep 2019 21:31:45 -0500 Subject: [PATCH 067/502] fixed typo in 1.4 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c03fdfa2..59995f31 100644 --- a/readme.md +++ b/readme.md @@ -272,7 +272,7 @@ it("When asking for an admin, ensure only ordered admins in results" , () => { ## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods -:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API deliver the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden +:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden
    From 04090966421af5c1faf2bb0ac8b463a0d9798073 Mon Sep 17 00:00:00 2001 From: leo lee Date: Sun, 15 Sep 2019 17:00:37 +0900 Subject: [PATCH 068/502] - Translation into Korean(3.1, 3.2) --- readme.kr.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 74855db3..6ea44346 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -917,32 +917,32 @@ it("When updating site name, get successful confirmation", async () => {

    -# Section 3️⃣: Frontend Testing +# 섹션 3️⃣: 프론트엔드 테스트 -## ⚪ ️ 3.1. Separate UI from functionality +## ⚪ ️ 3.1. 기능으로부터 화면을 분리하십시오 -:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI +:white_check_mark: **이렇게 해라:** 컴포넌트 로직을 테스트할때, 화면의 세부사항들은 제외되어야할 노이즈가 됩니다. 그것을 제외함으로써 당신의 테스트들은 순수한 데이터에 집중할 수 있습니다. 실제로, 그래픽 구현에 너무 결합되지 않는 추상적인 방법을 통해 요구되어지는 데이터를 마크업으로부터 추출하십시오. 그리고 느리게 만드는 애니메이션들을 제외한 오직 순수한 데이터를 검증하십시오(vs HTML/CSS 화면 세부사항). 당신은 렌더링하는 것을 피하고 오직 화면의 뒷부분(서비스, 액션, 스토어등과 같은)만을 테스트 하려고 할 수도 있습니다. 하지만, 이것은 실제와 같지도 않으며 심지어 화면에 올바른 데이터가 도달하지 않은 경우를 나타내지도 않는 가짜 테스트에서의 결과가 될 것 입니다.
    -❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation +❌ **그렇지 않으면:** 당신의 테스트의 순수하게 계산된 데이터는 10ms 내에 준비될수도 있지만, 전체 테스트는 화려하고 불필요한 애니메이션 때문에 500ms(100 테스트 = 1분) 동안 지속될 것 입니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Separating out the UI details +### :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', () => { +test('오직 VIP를 보기위해 사용자목록을 표시했을때, 오직 VIP 멤버들만 보여져야 한다', () => { // Arrange const allUsers = [ { id: 1, name: 'Yoni Goldberg', vip: false }, @@ -952,19 +952,19 @@ test('When users-list is flagged to show only VIP, should display only VIP membe // Act const { getAllByTestId } = render(); - // Assert - Extract the data from the UI first + // 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 + expect(allRenderedUsers).toEqual(allRealVIPUsers); // 화면에 아닌 데이터를 비교 }); ```
    -### :thumbsdown: Anti Pattern Example: Assertion mix UI details and data +### :thumbsdown: 잘못된 예: 화면 세부사항들과 데이터를 섞어서 검증 ```javascript -test('When flagging to show only VIP, should display only VIP members', () => { +test('오직 VIP를 보기위해 사용자목록을 표시했을때, 오직 VIP 멤버들만 보여져야 한다', () => { // Arrange const allUsers = [ {id: 1, name: 'Yoni Goldberg', vip: false }, @@ -974,7 +974,7 @@ test('When flagging to show only VIP, should display only VIP members', () => { // Act const { getAllByTestId } = render(); - // Assert - Mix UI & data in assertion + // Assert - 화면과 데이터를 섞어서 검증 expect(getAllByTestId('user')).toEqual('[
  • John Doe
  • ]'); }); @@ -988,21 +988,21 @@ test('When flagging to show only VIP, should display only VIP members', () => {

    -## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change +## ⚪ ️ 3.2 변하지 않은 요소들에 기반해서 HTML 엘리먼트들을 찾으십시오 -:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +:white_check_mark: **이렇게 해라:** CSS 검색자들과 다르게 양식 레이블들과 같이 그래픽 변경에도 살아남을 요소들을 기반으로 HTML 엘리먼트들을 찾으십시오. 만약 설계된 엘리먼트가 이와 같은 요소들을 가지고 있지 않다면, 'test-id-submit-button' 과 같이 테스트에 한정된 요소를 만드십시오. 이 방법은 당신의 기능/로직 테스트들이 룩앤필때문에 절대 망가지지 않을 것을 보장할 뿐만 아니라, 이 엘리먼트와 요소가 테스트에 의해 사용되어지고 제거되어서는 안된다는것을 팀 전체에게 명확하게 합니다.
    -❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **그렇지 않으면:** 당신은 로그인 기능을 테스트하기를 원합니다. 이 기능은 많은 컴포넌트들, 로직 그리고 서비스들에 걸쳐져 있고 모든 것은 완벽하게 준비되어 있습니다 - 스텁, 스파이, Ajax 호출은 격리되어져 있습니다. 모든것은 완벽한 것 처럼 보입니다. 그렇지만, 이 테스트는 디자이너에 의해 div 클래스 이름이 'thick-border' 에서 'thin-border'로 바뀌었기 때문에 실패합니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing +### :clap: 올바른 예: 테스트를 위해 한정된 요소를 사용해서 엘리먼트를 찾으십시오 ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") @@ -1017,8 +1017,8 @@ test('When flagging to show only VIP, should display only VIP members', () => { ``` ```javascript -// this example is using react-testing-library - test('Whenever no data is passed to metric, show 0 as default', () => { +// react-testing-library를 사용한 예제 + test('metric에 데이터가 전달되지 않으면, 0을 기본값으로 보여준다', () => { // Arrange const metricValue = undefined; @@ -1032,15 +1032,15 @@ test('When flagging to show only VIP, should display only VIP members', () => {
    -### :thumbsdown: Anti-Pattern Example: Relying on CSS attributes +### :thumbsdown: 잘못된 예: CSS 요소들에 의존 ```html -{value} +{value} ``` ```javascript -// this exammple is using enzyme -test('Whenever no data is passed, error metric shows zero', () => { +// enzyme을 사용한 예제 +test('데이터가 전달되지 않으면, 0을 보여준다', () => { // ... expect(wrapper.find("[className='d-flex-column']").text()).toBe("0"); From 4e82e7084e8a2b3a3ecd97a7ccbcef62978b2b37 Mon Sep 17 00:00:00 2001 From: sury Date: Wed, 18 Sep 2019 18:33:16 +0900 Subject: [PATCH 069/502] Translation into Korean (2.2,2.3) --- readme.kr.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 6ea44346..47f0dde0 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -702,24 +702,23 @@ describe('주문 서비스', function() {

    -## ⚪ ️2.2 Component testing might be your best affair +## ⚪ ️2.2 컴포넌트 테스트가 최선의 방법일 수 있다. -:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +:white_check_mark: **이렇게 해라:** 각각의 단위 테스트는 어플리케이션의 매우 작은 부분만을 커버하고 전체를 모두 커버하기에는 비용이 많이 듭니다. 반면에, end-to-end 테스트는 간단하게 많은 부분을 커버할 수 있지만 깊이가 얕고 더 느립니다. 그렇다면 균형 잡힌 접근법을 적용하여 단위 테스트보다는 크지만 end-to-end 테스트보다는 작은 테스트를 작성하는 것은 어떨까요? 컴포넌트 테스트는 테스트 세계에서 잘 알려지지 않은 방법입니다. - 컴포넌트 테스트는 다음의 두 가지 이점을 모두 제공합니다: 합리적인 성능과 TDD 패턴을 적용할 수 있는 가능성 + 현실적이면서 훌륭한 커버리지 -Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +컴포넌트 테스트는 마이크로 서비스 '단위'에 중점을 두고 API에 대하여 동작합니다. 마이크로서비스 그 자체에 속한 것들 (예를들면, 실제 DB 또는 해당 DB의 인-메모리 버전)은 모킹(Mock)하지 않고, 다른 마이크로서비스 호출과 같은 외부적인 것은 스텁(Stub)합니다. 그렇게 함으로써 우리는 우리가 배포하는 것을 테스트하고 어플리케이션의 바깥쪽에서 안쪽으로 접근하며, 적당한 시간 안에서 큰 자신감을 얻을 수 있습니다.
    -❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage - +❌ **그렇지 않으면:** 시스템 커버리지가 20%에 불과하다는 것을 깨닫기까지 단위 테스트를 작성하는 데 오랜 시간이 걸릴 수 있습니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) +### :clap: 올바른 예: Supertest를 통해 프로세스 내 Express API에 접근할 수 있습니다. (빠르고 다양한 계층을 커버함) ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") @@ -730,22 +729,23 @@ Component tests focus on the Microservice ‘unit’, they work against the API,

    -## ⚪ ️2.3 Ensure new releases don’t break the API using +## ⚪ ️2.3 신규 릴리즈가 API 사용을 깨지게 하지 마십시오. -:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration -
    +:white_check_mark: **이렇게 해라:** 당신의 마이크로서비스는 다수의 클라이언트를 가지고 있고 호환성의 이유로 여러 버전의 서비스를 운영합니다 (모든 사람을 만족시키기 위해서). 그런 상황에서 당신이 일부 필드를 변경하면 이 필드를 믿고 사용하던 일부 중요한 클라이언트는 화가 날 것입니다. 이것은 통합(integration) 세계에서 해결하기 어려운 진퇴양난에 놓인 문제입니다: 서버 사이드가 여러 클라이언트들의 모든 기댓값을 고려하는 것은 매우 어려운 일입니다. - 반면에, 서버가 릴리즈 날짜를 결정하기 때문에 클라이언트는 어떠한 테스트도 수행할 수 없습니다. +[소비자 주도 계약 테스트(Consumer-driven contracts)와 PACT 프레임워크](https://docs.pact.io/)는 매우 파괴적인 방법으로 이러한 프로세스를 표준화하기 위해 나타났습니다. - 서버가 서버의 테스트 계획을 결정하지 않고, 클라이언트가 서버의 테스트를 결정합니다! PACT는 클라이언트의 기댓값을 기록하여 "브로커"라는 공유된 위치에 올려둘 수 있습니다. 그러면 서버는 그 기댓값을 당겨 받을 수 있고 빌드할 때마다 PACT 라이브러리를 사용하여 깨진 계약(contract - 충족되지 않은 클라이언트의 기댓값)을 감지할 수 있습니다. 이렇게 함으로써, 모든 서버-클라이언트 API 간 일치하지 않은 것들을 빌드/CI 환경에서 조기에 잡을 수 있고 당신의 큰 절망감을 줄여줄 수 있을 것입니다. +
    -❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear +❌ **그렇지 않으면:** 대안은 수동 배포나 배포에 대한 두려움을 안고 가는 것 뿐입니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: +### :clap: 올바른 예: ![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") From 4c0528389eb6742ca13d44301d664f6623487751 Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Wed, 18 Sep 2019 14:29:09 -0300 Subject: [PATCH 070/502] begin translation in section 0 and section 1 --- readme-pt-br.md | 76 ++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/readme-pt-br.md b/readme-pt-br.md index ca49910c..0b6c8bc2 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -35,81 +35,81 @@ Comece entendendo as práticas de teste onipresentes que são a base para qualqu Um único conselho que inspira todos os outros (1 marcador especial) -#### [`Section 1: The Test Anatomy`](#section-1-the-test-anatomy-1) +#### [`Seção 1: A Anatomia do Teste`](#section-1-the-test-anatomy-1) -The foundation - structuring clean tests (12 bullets) +A fundação - estruturando testes limpos (12 marcadores) -#### [`Section 2: Backend`](#section-2️⃣-backend-testing) +#### [`Seção 2: Backend`](#section-2️⃣-backend-testing) -Writing backend and Microservices tests efficiently (8 bullets) +Escrevendo testes de back-end e microsserviços com eficiência (8 marcadores) -#### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) +#### [`Seção 3: Frontend`](#section-3️⃣-frontend-testing) -Writing tests for web UI including component and E2E tests (11 bullets) +Escrevendo testes para interface do usuário da web, incluindo testes de componentes e E2E (11 marcadores) -#### [`Section 4: Measuring Tests Effectiveness`](#section-4️⃣-measuring-test-effectiveness) +#### [`Seção 4: Metrificando Testes Efetivamente`](#section-4️⃣-measuring-test-effectiveness) -Watching the watchman - measuring test quality (4 bullets) +Observando o vigia - medindo a qualidade do teste (4 marcadores) -#### [`Section 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) +#### [`Seção 5: Integração Contínua`](#section-5️⃣-ci-and-other-quality-measures) -Guidelines for CI in the JS world (9 bullets) +Diretrizes para CI no mundo JS (9 marcadores)

    -# Section 0️⃣: The Golden Rule +# Seção 0️⃣: A Regra de Ouro
    -## ⚪️ 0. The Golden Rule: Design for lean testing +## ⚪️ 0. A Regra de Ouro: Design para testes enxutos -:white_check_mark: **Do:** -Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. +:white_check_mark: **Faça:** +O código de teste não é como o código de produção - projete-o para ser simples, curto, sem abstrações, plano, agradável de se trabalhar, enxuto. Deve-se olhar para um teste e obter a intenção instantaneamente. -Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. - -The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells we have two brain systems: system 1 which is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should *feel* as easy as modifying an HTML document and not like solving 2X(17 × 24). +Nossas mentes estão cheias com o código principal de produção, não temos 'espaço de sobra' para complexidade adicional. Se tentarmos espremer outro código desafiador em nosso cérebro fraco, a equipe ficará mais lenta, o que vai de encontro com a razão pela qual fazemos os testes. Praticamente é aqui que muitas equipes abandonam os testes. + +Os testes são uma oportunidade para outra coisa - um assistente amigável e sorridente, que é agradável de trabalhar e oferece grande valor para um investimento tão pequeno. A ciência diz que temos dois sistemas cerebrais: o sistema 1, usado para atividades sem esforço, como dirigir um carro em uma estrada vazia, e o sistema 2, destinado a operações complexas e conscientes, como resolver uma equação matemática. Projete seu teste para o sistema 1, ao analisar o código de teste, ele deve parecer tão fácil quanto modificar um documento HTML e não como resolver um equação 2X (17 × 24). -This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. +Isso pode ser alcançado através de técnicas, ferramentas e alvos de teste selecionados de forma econômica, que são econômicos e proporcionam um ótimo ROI. Teste apenas o necessário, esforce-se para mantê-lo ágil, às vezes vale a pena abandonar alguns testes e trocar a confiabilidade por agilidade e simplicidade. ![alt text](/assets/headspace.png "We have no head room for additional complexity") -Most of the advice below are derivatives of this principle. +A maioria dos conselhos abaixo são derivados desse princípio. -### Ready to start? +### Pronto para começar?

    -# Section 1: The Test Anatomy +# Seção 1: A Anatomia do Teste
    -## ⚪ ️ 1.1 Include 3 parts in each test name +## ⚪ ️ 1.1 Inclua 3 partes em cada nome de teste -:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: +:white_check_mark: **Faça:** Um relatório de teste deve informar se a revisão atual do aplicativo atende aos requisitos para as pessoas que não estão necessariamente familiarizadas com o código: o testador, o engenheiro DevOps que está implantando e você daqui a dois anos. Isso pode ser melhor alcançado se os testes falarem no nível de requisitos e incluirem 3 partes: -(1) What is being tested? For example, the ProductsService.addNewProduct method +(1) O que está sendo testado? Por exemplo, o método ProductsService.addNewProduct -(2) Under what circumstances and scenario? For example, no price is passed to the method +(2) Sob que circunstâncias e cenário? Por exemplo, nenhum preço é passado para o método -(3) What is the expected result? For example, the new product is not approved +(3) Qual é o resultado esperado? Por exemplo, o novo produto não é aprovado
    -❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? +❌ **De outra forma:** Uma implantação acabou de falhar, um teste chamado "Adicionar produto" falhou. Isso diz o que exatamente está com defeito?
    -**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand -
    Code Examples +**👇 Nota:** Cada marcador possui exemplos de código e alguns tem ilustrações. Clique para expandir +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :clap: Exemplo: um nome de teste que constitui 3 partes ![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Using Mocha to illustrate the idea") @@ -129,32 +129,32 @@ describe('Products Service', function() { ```
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :clap: Exemplo: um nome de teste que constitui 3 partes ![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts")


    -## ⚪ ️ 1.2 Structure tests by the AAA pattern +## ⚪ ️ 1.2 Testes de estrutura pelo padrão em inglês AAA -:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain CPU on understanding the test plan: +:white_check_mark: **Faça:** Estruture seus testes com 3 seções bem separadas: Organizar, Atuar e Afirmar (OAA). Seguir essa estrutura garante que o leitor não gaste CPU do cérebro na compreensão do plano de teste: -1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code +1st O - Organizar: todo o código de configuração para levar o sistema ao cenário que o teste pretende simular. Isso pode incluir instanciar a unidade sob o construtor de teste, adicionar registros de banco de dados, mockar/stubbing de objetos e qualquer outro código de preparação -2nd A - Act: Execute the unit under test. Usually 1 line of code +2nd A - Ato: Execute teste em unidade. Geralmente 1 linha de código -3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code +3rd A - Afirmar: Garanta que o valor recebido satisfaça a expectativa. Geralmente 1 linha de código
    -❌ **Otherwise:** Not only you spend long daily hours on understanding the main code, now also what should have been the simple part of the day (testing) stretches your brain +❌ **De outra forma:** Você não gata apenas longas horas diárias para entender o código principal, agora também o que deveria ter sido a parte simples do dia (teste) estica seu cérebro
    -
    Code Examples +
    Códigos de Exemplo
    From 4bc7fed98cdbb0fd10f63d9718f2ad50de114cdc Mon Sep 17 00:00:00 2001 From: sury Date: Thu, 19 Sep 2019 13:25:51 +0900 Subject: [PATCH 071/502] Translation into Korean(2.4,2.5) --- readme.kr.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 47f0dde0..374e163b 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -760,32 +760,32 @@ describe('주문 서비스', function() {

    -## ⚪ ️ 2.4 Test your middlewares in isolation +## ⚪ ️ 2.4 당신의 미들웨어를 독립적으로 테스트 하십시오. -:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +:white_check_mark: **Do:** 많은 사람들은 미들웨어(Middleware) 테스트를 피합니다. 왜냐하면 미들웨어 테스트는 시스템의 작은 부분일 뿐이고 라이브 Express 서버가 필요하기 때문입니다. 하지만 두 가지 이유 모두 틀렸습니다. - 미들웨어는 작지만 모든 요청 또는 대부분의 요청에 영향을 미치고, {req,res} JS 객체를 가지는 순수한 함수로 쉽게 테스트할 수 있기 때문입니다. 미들웨어 함수를 테스트하기 위해서는 단지 함수를 불러오고 함수가 올바르게 동작하는 것을 확인하기 위해 {req, res} 객체에 대한 인터렉션을 스파이(spy)([예를들어 Sinon을 사용](https://www.npmjs.com/package/sinon))하면 됩니다. 라이브러리 [node-mock-http](https://www.npmjs.com/package/node-mocks-http)는 더 나아가서 행위에 대한 스파이와 함께 {req, res} 객체도 테스트할 수 있습니다. 예를 들어, response 객체의 http 상태가 기대했던 값과 일치하는지 여부를 확인(assert)할 수 있습니다. (아래 예제를 보세요)
    -❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests +❌ **Otherwise:** Express 미들웨어에서의 버그 === 모든 요청 또는 대부분의 요청에서의 버그
    -
    Code Examples +
    코드 예제
    -### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :clap:올바른 예: 네트워크 호출 없이 전체 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', () => { +//Jest 문법으로 Mocha의 describe() & it()과 동일 +test('헤더에 인증정보가 없는 요청은, http status 403을 리턴해야한다.', () => { const request = httpMocks.createRequest({ method: 'GET', url: '/user/42', @@ -807,24 +807,24 @@ test('A request without authentication header, should return http status 403', (

    -## ⚪ ️2.5 Measure and refactor using static analysis tools -:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) +## ⚪ ️2.5 정적 분석 도구를 사용하여 측정하고 리팩토링 하십시오. +:white_check_mark: **이렇게 해라:** 정적 분석 도구를 사용하면 코드 품질을 개선하고 코드를 유지 관리할 수 있는 객관적인 방법을 제공할 수 있습니다. 정적 분석 도구를 당신의 CI 빌드에 추가하여 코드 냄새(code smell)가 발견되면 중단되도록 할 수 있습니다. 정적 분석 도구가 일반적인 린트(lint) 도구보다 더 좋은 점은 여러 파일들의 컨텍스트 안에서 품질을 검사하고(예: 중복 탐지), 고급 분석(예: 코드 복잡성)을 할 수 있으며 코드 이슈에 대한 히스토리와 프로세스를 추적할 수 있다는 것입니다. 사용할 수 있는 정적 분석 도구 두 가지는 [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube))와 [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate))입니다. Credit:: [Keith Holliday](https://github.com/TheHollidayInn)
    -❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix +❌ **그렇지 않으면:** 코드 품질이 좋지 않으면 버그와 성능은 빛나는 새 라이브러리나 최신 기능으로 해결할 수 없는 문제가 될 것입니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: +### :clap: 올바른 예: 복잡도가 높은 함수를 찾아내는 상용 도구인 CodeClimate: ![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg "Examples with CodeClimate") From 754094868211ff1ec1a970b893e4aed1dc7532e1 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Fri, 6 Sep 2019 00:30:37 +0900 Subject: [PATCH 072/502] Translate into Korean 1.5 - Fix typo. --- readme.kr.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 6577d1e0..be8617d7 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -69,7 +69,7 @@ JavaScript 및 Node.js에 대한 A부터 Z까지의 믿음직한 가이드입니 테스트는 친절하고 웃는 동료와 함께 일하는 것이 즐거울 수 있는 기회이고, 적은 투자로 큰 가치를 제공하는 것입니다. 과학은 우리에게 두 개의 뇌 시스템이 있다고 말합니다. 빈 도로에서 자동차를 운전하는 등의 간편한 활동에 사용되는 시스템 1, 그리고 수학 방정식을 푸는 것과 같이 복잡하고 의식적인 연산을 위한 시스템 2. 테스트 코드를 볼 때 수학 문제를 푸는 것 같은게 아닌, HTML 문서를 수정하는 것만 큼 쉬워야하는 시스템 1에 맞게 테스트를 설계하십시오. -선택적인 체리픽 기술, 툴 그리고 비용-효율적이고 뛰어난 ROI를 제공하는 테스트 대상 선정으로 목적을 이러한 달성할 수 있습니다. 필요한 만큼의 테스트, 융통성 있게 유자하려는 노력, 때로는 애자일함과 단순성을 위해 일부 테스트와 신뢰성을 포기하는 것도 가치가 있습니다. +선택적인 체리픽 기술, 툴 그리고 비용-효율적이고 뛰어난 ROI를 제공하는 테스트 대상 선정으로 이러한 목적을 달성할 수 있습니다. 필요한 만큼의 테스트, 융통성 있게 유지하려는 노력, 때로는 애자일함과 단순성을 위해 일부 테스트와 신뢰성을 포기하는 것도 가치가 있습니다. ![alt text](/assets/headspace.png "우리 머리속은 부가적인 복잡한 것들을 생각할 여유가 없습니다.") @@ -295,54 +295,55 @@ it("화이트박스 테스트: 내부 method가 VAT 0을 받으면 0을 반환

    -## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies +## ⚪ ️ 1.5 올바른 테스트 더블 선택: Stub과 Spy를 위한 Mock을 피하십시오. -:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide an immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **이렇게 해라:** 테스트 더블은 어플리케이션 내부에 연결되어 있기때문에 필요악이지만 일부는 엄청난 가치를 제공합니다([테스트 더블에 대한 알림: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). -Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a smell of white-box testing. +테스트 더블을 사용하기 전에 간단한 질문: 요구사항 문서에 있거나 있을 수 있는 기능을 테스트하는 데 테스트 더블을 사용합니까? 만약 아니라면 화이트박스 테스트 낌새가 보입니다. -For example, if you want to test what your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently +예를 들어, 결제 서비스가 중단되었을 때 앱이 적절하게 작동하는 것을 테스트하려는 경우, 테스트중인 단위가 올바른 값을 반환하도록, 결제 서비스를 stub하고 '응답 없음' 반환을 트리거 할 수 있습니다. +이것은 특정 시나리오에서 애플리케이션의 동작/응답/결과를 확인합니다. 그리고 spy를 사용하여 해당 서비스가 중단되었을 때 메일이 보내지는지를 assert 할 수 있습니다. 이것은 다시 요구사항 문서에 있을 수 있는 행동에 대한 점검입니다(결제가 저장되지 않으면 메일은 보낸다). 반대로, 결제 서비스를 mock 하고 올바른 JavaScript 타입으로 호출 되었는지를 확인한다면 - 당신의 테스트는 애플리케이션 기능에 전혀 영향을 받지 않고 자주 변경될 수 있는 내부 구현에 초점을 둔 경우입니다.
    - -❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend +❌ **그렇지 않으면:** 코드를 리펙토링 할 때, 모든 mock을 찾아서 수정해야 합니다. 테스트가 도움이 아닌 부담이 됩니다.
    -
    Code Examples +
    코드 예제
    -### :thumbsdown: Anti-pattern example: Mocks focus on the internals +### :thumbsdown: 올바르지 않은 예: 내부에 초점을 둔 mock + ![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg "Examples with Mocha & Chai") + ```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 +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:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals +### :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 +it("유효한 제품을 삭제하려고 할 때, 메일을 보낸다", async () => { + // 이미 제품을 추가했다고 가정 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) + // 좋음: 우리는 내부를 다루는가? 그렇다, 그러나 요구사항(이메일을 보낸다)에 대한 테스트의 side-effect이다. }); ```
    - -

    ## ⚪ ️1.6 Don’t “foo”, use realistic input data From 4d9363138b7178402f2cec94321e7da62914b4f6 Mon Sep 17 00:00:00 2001 From: devori Date: Fri, 6 Sep 2019 15:20:35 +0900 Subject: [PATCH 073/502] Translation-ko_KR(1.6) --- readme.kr.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index be8617d7..3c61d2cd 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -346,22 +346,22 @@ it("유효한 제품을 삭제하려고 할 때, 메일을 보낸다", async ()

    -## ⚪ ️1.6 Don’t “foo”, use realistic input data +## ⚪ ️1.6 의미없는 인풋 데이터를 사용하지 말고, 실제와 같은 인풋 데이터를 사용해라 -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not instead) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? see next bullet (property-based testing). +:white_check_mark: **이렇게 해라:** 흔히 제품의 버그들은 매우 특수한 인풋데이터를 통해 나타납니다 - 테스트 인풋이 혈실적일 수록 버그를 조기에 발견할 가능성이 높아집니다. 실제 데이터와 다양성 및 형태가 유사한 데이터를 생성해 주는 [Faker](https://www.npmjs.com/package/faker) 같은 전용 라이브러리들을 사용하십시오. 이런 라이브러리들은 실제같은 전화번호, 사용자 이름, 신용카드, 회사명 그리고 심지어 'lorem ipsum'같은 문자등을 생성할 수도 있습니다. 당신은 가상의 데이터를 사용하여 테스트(단위 테스트 위에서)를 무작위화 하거나 심지어 실제 환경으로부터의 실제 데이터를 임포트 할수도 있습니다. 다음 단계를 얻기를 원하십니까? 그렇다면 아래로 가십시오 (property-based testing).
    -❌ **Otherwise:** All your development testing will falsely seem green when you use synthetic inputs like “Foo” but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **그렇지 않다면:** "Foo"와 같은 인풋을 사용하면 당신의 모든 테스트가 모두 통과한것 처럼 표시되지만, 실제 환경에서는 해커가 “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” 같은 인풋을 전달해 실패 할수도 있습니다.
    -
    Code Examples +
    코드 예제
    -### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data +### :thumbsdown: 올바르지 않은 예: 현실적이지 않은 데이터 때문에 통과하는 테스트 ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -369,34 +369,33 @@ it("유효한 제품을 삭제하려고 할 때, 메일을 보낸다", async () ```javascript const addProduct = (name, price) =>{ - const productNameRegexNoSpace = /^\S*$/;//no white-space allowd + const productNameRegexNoSpace = /^\S*$/;// 공백은 허용되지 않음 if(!productNameRegexNoSpace.test(name)) - return false;//this path never reached due to dull input + return false;//도달하지 않는 곳 //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 +test("잘못된 예제: 유효한 속성과 함께 제품을 추가한다면, 성공을 얻는다.", async () => { + //모든 테스트에서 false 가 리턴되지 않는 "Foo" 인풋을 사용 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:Doing It Right Example: Randomizing realistic input +### :clap:올바른 예: 무작위한 현실적인 인풋Randomizing realistic input ```javascript -it("Better: When adding new valid product, get successful confirmation", async () => { +it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.", async () => { const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); - //Generated random input: {'Sleek Cotton Computer', 85481} + //생성된 무작위 인풋: {'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! + //테스트는 실패한다, 무작위 인풋은 우리가 계획하지 않은 일이 일어나도록 만든다. + //우리는 조기에 버그를 발견했다! }); ``` From aa318282f343f6ee5ce983d28a17be8c7b79e255 Mon Sep 17 00:00:00 2001 From: devori Date: Fri, 6 Sep 2019 15:23:04 +0900 Subject: [PATCH 074/502] fix typo remove english sentence that translated into ko-KR --- readme.kr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.kr.md b/readme.kr.md index 3c61d2cd..927f5871 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -388,7 +388,7 @@ test("잘못된 예제: 유효한 속성과 함께 제품을 추가한다면, ```
    -### :clap:올바른 예: 무작위한 현실적인 인풋Randomizing realistic input +### :clap:올바른 예: 무작위한 현실적인 인풋 ```javascript it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.", async () => { const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); From 2998176bc61468fc93e64191a88ed9316ee25475 Mon Sep 17 00:00:00 2001 From: sury Date: Fri, 6 Sep 2019 15:40:28 +0900 Subject: [PATCH 075/502] translation 1.7 ko/kr --- readme.kr.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 927f5871..d8c03f0d 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -406,13 +406,15 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",

    -## ⚪ ️ 1.7 Test many input combinations using Property-based testing +## ⚪ ️ 1.7  프로퍼티 기반(Property-based) 테스트를 통해 다양한 인풋 값 조합으로 테스를 하십시오. -:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +:white_check_mark: **이렇게 해라:** 우리는 일반적으로 적은 수의 인풋 샘플 데이터를 가지고 테스트를 합니다. 심지어 인풋 데이터 형식이 실제 데이터와 비슷할 때에도 다음과 같이 제한된 인풋 조합으로만 테스트를 커버합니다.(method(‘’, true, 1), method(“string” , false” , 0)) 하지만, 운영시에는 5개의 파라미터를 가지는 API는 수 천 개의 다른 조합의 파라미터로 호출 될 수 있고, 이 중 하나가 우리의 시스템을 다운시킬 수도 있습니다. 그렇다면 만약 1000 가지 조합의 인풋값을 자동으로 생성하고 올바른 응답을 반환하지 못하는 인풋값을 찾아내는 단일 테스트를 작성할 수 있다면 어떨까요? +프로퍼티 기반 테스트는 유닛 테스트에 모든 가능한 인풋 조합을 사용하여 생각하지 못 한 버그를 찾을 확률을 높여줍니다. 예를들어, 다음의 메소드가 주어졌을 때 — addNewProduct(id, name, isDiscount) — 프로퍼티 기반 테스트 라이브러리들은 다양한 파라미터 (number, string, boolean) 조합으로 - (1, “iPhone”, false), (2, “Galaxy”, true) - 이 메소드를 호출합니다. [js-verify](https://github.com/jsverify/jsverify) 나 [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation) 같은 라이브러리를 지원하는 테스트 러너들 (Mocha, Jest, etc) 중 당신이 가장 선호하는 방법을 통해 프로퍼티 기반 테스트를 할 수 있습니다. +업데이트 : Nicolas Dubien가 코멘트를 통해 더 많은 부가적인 기능들을 제공하고 활발하게 유지보수되고 있는 라이브러리 [fast-check](https://github.com/dubzzz/fast-check#readme)를 추천해 주었습니다.
    -❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs +❌ **그렇지 않으면:** 의심할 여지 없이 당신은 오직 코드가 잘 동작하는 테스트 인풋을 사용할 것입니다. 불행하게도 이러한 방식은 버그를 찾는 도구로써의 테스트 효율성을 떨어뜨릴 것 입니다.
    @@ -421,7 +423,7 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",
    -### :clap: Doing It Right Example: Testing many input permutations with “mocha-testcheck” +### :clap: 올바른 예: “mocha-testcheck”를 사용하여 다양한 인풋 조합으로 테스트 하십시오. ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") @@ -432,7 +434,7 @@ const {expect} = require('chai'); describe('Product service', () => { describe('Adding new', () => { - //this will run 100 times with different random properties + //서로 다른 무작위 값으로 100회 호출됩니다. check.it('Add new product with random yet valid properties, always successful', gen.int, gen.string, (id, name) => { expect(addNewProduct(id, name).status).to.equal('approved'); From f4a29a1a736fb827619ce739852fb808db4bebf5 Mon Sep 17 00:00:00 2001 From: sury Date: Fri, 6 Sep 2019 15:46:03 +0900 Subject: [PATCH 076/502] fix 1.7 tranlation to Korean --- readme.kr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.kr.md b/readme.kr.md index d8c03f0d..3ea34a81 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -419,7 +419,7 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",
    -
    Code Examples +
    코드 예제
    From 41d444b468fb224bb42e5a7997a566522b91b1e3 Mon Sep 17 00:00:00 2001 From: sury Date: Fri, 6 Sep 2019 16:01:06 +0900 Subject: [PATCH 077/502] =?UTF-8?q?fix=20used=20word=20(=EB=8B=A8=EC=9D=BC?= =?UTF-8?q?=20->=20=EB=8B=A8=EC=9C=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.kr.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 3ea34a81..317d1671 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -406,10 +406,10 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",

    -## ⚪ ️ 1.7  프로퍼티 기반(Property-based) 테스트를 통해 다양한 인풋 값 조합으로 테스를 하십시오. +## ⚪ ️ 1.7  프로퍼티 기반(Property-based) 테스트를 통해 다양한 인풋 값 조합으로 테스트를 하십시오. -:white_check_mark: **이렇게 해라:** 우리는 일반적으로 적은 수의 인풋 샘플 데이터를 가지고 테스트를 합니다. 심지어 인풋 데이터 형식이 실제 데이터와 비슷할 때에도 다음과 같이 제한된 인풋 조합으로만 테스트를 커버합니다.(method(‘’, true, 1), method(“string” , false” , 0)) 하지만, 운영시에는 5개의 파라미터를 가지는 API는 수 천 개의 다른 조합의 파라미터로 호출 될 수 있고, 이 중 하나가 우리의 시스템을 다운시킬 수도 있습니다. 그렇다면 만약 1000 가지 조합의 인풋값을 자동으로 생성하고 올바른 응답을 반환하지 못하는 인풋값을 찾아내는 단일 테스트를 작성할 수 있다면 어떨까요? -프로퍼티 기반 테스트는 유닛 테스트에 모든 가능한 인풋 조합을 사용하여 생각하지 못 한 버그를 찾을 확률을 높여줍니다. 예를들어, 다음의 메소드가 주어졌을 때 — addNewProduct(id, name, isDiscount) — 프로퍼티 기반 테스트 라이브러리들은 다양한 파라미터 (number, string, boolean) 조합으로 - (1, “iPhone”, false), (2, “Galaxy”, true) - 이 메소드를 호출합니다. [js-verify](https://github.com/jsverify/jsverify) 나 [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation) 같은 라이브러리를 지원하는 테스트 러너들 (Mocha, Jest, etc) 중 당신이 가장 선호하는 방법을 통해 프로퍼티 기반 테스트를 할 수 있습니다. +:white_check_mark: **이렇게 해라:** 우리는 일반적으로 적은 수의 인풋 샘플 데이터를 가지고 테스트를 합니다. 심지어 인풋 데이터 형식이 실제 데이터와 비슷할 때에도 다음과 같이 제한된 인풋 조합으로만 테스트를 커버합니다.(method(‘’, true, 1), method(“string” , false” , 0)) 하지만, 운영시에는 5개의 파라미터를 가지는 API는 수 천 개의 다른 조합의 파라미터로 호출 될 수 있고, 이 중 하나가 우리의 시스템을 다운시킬 수도 있습니다. 그렇다면 만약 1000 가지 조합의 인풋값을 자동으로 생성하고 올바른 응답을 반환하지 못하는 인풋값을 찾아내는 단위 테스트를 작성할 수 있다면 어떨까요? +프로퍼티 기반 테스트는 단위 테스트에 모든 가능한 인풋 조합을 사용하여 생각하지 못 한 버그를 찾을 확률을 높여줍니다. 예를들어, 다음의 메소드가 주어졌을 때 — addNewProduct(id, name, isDiscount) — 프로퍼티 기반 테스트 라이브러리들은 다양한 파라미터 (number, string, boolean) 조합으로 - (1, “iPhone”, false), (2, “Galaxy”, true) - 이 메소드를 호출합니다. [js-verify](https://github.com/jsverify/jsverify) 나 [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation) 같은 라이브러리를 지원하는 테스트 러너들 (Mocha, Jest, etc) 중 당신이 가장 선호하는 방법을 통해 프로퍼티 기반 테스트를 할 수 있습니다. 업데이트 : Nicolas Dubien가 코멘트를 통해 더 많은 부가적인 기능들을 제공하고 활발하게 유지보수되고 있는 라이브러리 [fast-check](https://github.com/dubzzz/fast-check#readme)를 추천해 주었습니다.
    From 19a263f893dfd1bb3ecbf3295410eab9d996b0f4 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Sat, 7 Sep 2019 16:59:39 +0900 Subject: [PATCH 078/502] GPG signature test. --- readme.kr.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 317d1671..33ea8c9c 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -346,15 +346,14 @@ it("유효한 제품을 삭제하려고 할 때, 메일을 보낸다", async ()

    -## ⚪ ️1.6 의미없는 인풋 데이터를 사용하지 말고, 실제와 같은 인풋 데이터를 사용해라 +## ⚪ ️ 1.6 의미없는 인풋 데이터를 사용하지 말고, 실제와 같은 인풋 데이터를 사용해라 :white_check_mark: **이렇게 해라:** 흔히 제품의 버그들은 매우 특수한 인풋데이터를 통해 나타납니다 - 테스트 인풋이 혈실적일 수록 버그를 조기에 발견할 가능성이 높아집니다. 실제 데이터와 다양성 및 형태가 유사한 데이터를 생성해 주는 [Faker](https://www.npmjs.com/package/faker) 같은 전용 라이브러리들을 사용하십시오. 이런 라이브러리들은 실제같은 전화번호, 사용자 이름, 신용카드, 회사명 그리고 심지어 'lorem ipsum'같은 문자등을 생성할 수도 있습니다. 당신은 가상의 데이터를 사용하여 테스트(단위 테스트 위에서)를 무작위화 하거나 심지어 실제 환경으로부터의 실제 데이터를 임포트 할수도 있습니다. 다음 단계를 얻기를 원하십니까? 그렇다면 아래로 가십시오 (property-based testing). -
    +
    ❌ **그렇지 않다면:** "Foo"와 같은 인풋을 사용하면 당신의 모든 테스트가 모두 통과한것 처럼 표시되지만, 실제 환경에서는 해커가 “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” 같은 인풋을 전달해 실패 할수도 있습니다. -
    코드 예제 @@ -384,11 +383,12 @@ test("잘못된 예제: 유효한 속성과 함께 제품을 추가한다면, expect(addProductResult).toBe(true); //거짓된 성공: 공백을 포함하는 문자열을 사용하지 않았기 때문에 테스트는 성공한다. }); - ``` +
    ### :clap:올바른 예: 무작위한 현실적인 인풋 + ```javascript it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.", async () => { const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); @@ -401,9 +401,6 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",
    - - -

    ## ⚪ ️ 1.7  프로퍼티 기반(Property-based) 테스트를 통해 다양한 인풋 값 조합으로 테스트를 하십시오. @@ -411,12 +408,11 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.", :white_check_mark: **이렇게 해라:** 우리는 일반적으로 적은 수의 인풋 샘플 데이터를 가지고 테스트를 합니다. 심지어 인풋 데이터 형식이 실제 데이터와 비슷할 때에도 다음과 같이 제한된 인풋 조합으로만 테스트를 커버합니다.(method(‘’, true, 1), method(“string” , false” , 0)) 하지만, 운영시에는 5개의 파라미터를 가지는 API는 수 천 개의 다른 조합의 파라미터로 호출 될 수 있고, 이 중 하나가 우리의 시스템을 다운시킬 수도 있습니다. 그렇다면 만약 1000 가지 조합의 인풋값을 자동으로 생성하고 올바른 응답을 반환하지 못하는 인풋값을 찾아내는 단위 테스트를 작성할 수 있다면 어떨까요? 프로퍼티 기반 테스트는 단위 테스트에 모든 가능한 인풋 조합을 사용하여 생각하지 못 한 버그를 찾을 확률을 높여줍니다. 예를들어, 다음의 메소드가 주어졌을 때 — addNewProduct(id, name, isDiscount) — 프로퍼티 기반 테스트 라이브러리들은 다양한 파라미터 (number, string, boolean) 조합으로 - (1, “iPhone”, false), (2, “Galaxy”, true) - 이 메소드를 호출합니다. [js-verify](https://github.com/jsverify/jsverify) 나 [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation) 같은 라이브러리를 지원하는 테스트 러너들 (Mocha, Jest, etc) 중 당신이 가장 선호하는 방법을 통해 프로퍼티 기반 테스트를 할 수 있습니다. 업데이트 : Nicolas Dubien가 코멘트를 통해 더 많은 부가적인 기능들을 제공하고 활발하게 유지보수되고 있는 라이브러리 [fast-check](https://github.com/dubzzz/fast-check#readme)를 추천해 주었습니다. -
    +
    ❌ **그렇지 않으면:** 의심할 여지 없이 당신은 오직 코드가 잘 동작하는 테스트 인풋을 사용할 것입니다. 불행하게도 이러한 방식은 버그를 찾는 도구로써의 테스트 효율성을 떨어뜨릴 것 입니다. -
    코드 예제 @@ -441,14 +437,10 @@ describe('Product service', () => { }); }) }); - ```
    - - -

    ## ⚪ ️ 1.8 If needed, use only short & inline snapshots From f127f4cec5699ec610b47d4e56ad13741c28f027 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Mon, 9 Sep 2019 00:09:05 +0900 Subject: [PATCH 079/502] Translate into Korean 1.8 --- readme.kr.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 33ea8c9c..a79db949 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -443,30 +443,31 @@ describe('Product service', () => {

    -## ⚪ ️ 1.8 If needed, use only short & inline snapshots +## ⚪ ️ 1.8 필요한 경우 짧거나 인라인 스냅샷만 사용하십시오. -:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. +:white_check_mark: **이렇게 해라:** [스냅샷 테스트](https://jestjs.io/docs/en/snapshot-testing)가 필요한 경우 외부 파일이 아닌 테스트의 일부 ([인라인 스냅샷](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots))에 포함 된 짧고 집중된 스냅샷(3~7 라인)만 사용하십시오. 이 지침을 따르면 따로 설명이 필요없고 잘 깨지지 않는 테스트가 됩니다. -On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much +반면에, '고전적인 스냅샷' 튜토리얼 및 도구는 외부에 큰 파일(예: 구성 요소 랜더링 마크업, API JSON 결과)를 저장하고, 테스트를 실행할 때 마다 수신된 결과를 저장된 버전과 비교하기를 권장합니다. 예를 들어, 이것은 1,000 라인(우리가 절대 읽지 않고 추론하지 않을 3,000개의 데이터 값을 가진)의 코드를 우리 테스트에 암시적으로 연결할 수 있습니다. 왜 이것이 잘못 되었을까요? 이렇게하면 테스트에 실패할 1,000 가지 이유가 생깁니다. 한줄만 변경되어도 스냅샷이 유효하지 않게 되고, 이런일이 일어날 가능성이 높습니다. 얼마나 자주? 모든 공백, 주석에서 혹은 사소한 CSS/HTML 변경에 대해서. 뿐만 아니라 테스트 이름은 1,000 라인이 변경되지 않았는지를 나타내기 때분에, 실패에 대한 단서를 제공하지 않습니다. 또한 테스트 작성자가 긴 문서(검사하고 확인할 수 없는)를 받아들이게끔 합니다. 이 모든 것은 초점이 맞지않고 너무 많은 것을 달성하려는 모호하고 간절한 테스트 증상입니다. + +긴 외부 스냅샷이 허용되는 경우가 거의 없다는 점은 주목할 가치가 있습니다 - 데이터가 아닌 스키마를 assert 할 때(값 추출 및 필드에 집중) 또는 수신된 문서가 거의 변경되지 않는 경우 -It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes
    -❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **그렇지 않다면:** UI 테스트가 실패합니다. 코드가 문제없어 보이고 화면이 완벽한 픽셀을 렌더링합니다. 어떻게 되었습니까? 스냅샷 테스트에서 원본 문서와 현재 수신된 문서와의 차이점을 발견했습니다. 빈칸 하나가 마크 다운에 추가되었습니다...
    -
    Code Examples +
    코드 예제
    -### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code +### :thumbsdown: 올바르지 않은 예: 보이지 않는 2,000 라인의 코드를 우리 테스트에 연결 ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ```javascript -it('TestJavaScript.com is renderd correctly', () => { +it('TestJavaScript.com 이 올바르게 랜더링 된다.', () => { //Arrange @@ -477,16 +478,18 @@ const receivedPage = renderer //Assert expect(receivedPage).toMatchSnapshot(); -//We now implicitly maintain a 2000 lines long document -//every additional line break or comment - will break this test +// 이제 2,000 라인의 문서를 암묵적으로 유지합니다. +// 모든 줄바꿈 또는 주석이 테스트를 망가뜨립니다. }); ``` +
    -### :clap: Doing It Right Example: Expectations are visible and focused +### :clap: 올바른 예: expectation이 잘 보이고 집중된다. + ```javascript -it('When visiting TestJavaScript.com home page, a menu is displayed', () => { +it('TestJavaScript.com 홈페이지를 방문하면 메뉴가 보인다.', () => { //Arrange //Act @@ -509,7 +512,6 @@ expect(menu).toMatchInlineSnapshot(`
    -

    ## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test From d0616c6f28a29e58466104d6853328f38937535a Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Mon, 9 Sep 2019 09:45:12 +0900 Subject: [PATCH 080/502] Translate into Korean 1.9 --- readme.kr.md | 55 +++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index a79db949..e9de9fbd 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -99,9 +99,9 @@ JavaScript 및 Node.js에 대한 A부터 Z까지의 믿음직한 가이드입니
    -**👇 주의:** 각 글에는 코드 예제가 있으며 때로는 이미지도 있습니다. 클릭하여 확장 +**👇 주의:** 각 글에는 예제 코드가 있으며 때로는 이미지도 있습니다. 클릭하여 확장 -
    코드 예제 +
    예제 코드
    @@ -149,7 +149,7 @@ describe('제품 서비스', function() {
    -
    코드 예제 +
    예제 코드
    @@ -203,7 +203,7 @@ test('프리미엄으로 분류해야 합니다.', () => {
    -
    코드 예제
    +
    예제 코드
    ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg @@ -260,7 +260,7 @@ it("관리자 요청이 들어오면 정렬된 관리자 목록만 결과에 포
    -
    코드 예제 +
    예제 코드
    @@ -309,7 +309,7 @@ it("화이트박스 테스트: 내부 method가 VAT 0을 받으면 0을 반환
    -
    코드 예제 +
    예제 코드
    @@ -356,7 +356,7 @@ it("유효한 제품을 삭제하려고 할 때, 메일을 보낸다", async ()
    -
    코드 예제 +
    예제 코드
    @@ -415,7 +415,7 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",
    -
    코드 예제 +
    예제 코드
    @@ -457,7 +457,7 @@ describe('Product service', () => {
    -
    코드 예제 +
    예제 코드
    @@ -514,51 +514,50 @@ expect(menu).toMatchInlineSnapshot(`

    -## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test - -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) -
    +## ⚪ ️ 1.9 테스트 데이터를 글로벌로 하지말고 테스트별로 따로 추가하라. +:white_check_mark: **이렇게 해라:** 황금률에 따르면(섹션 0), 각 테스트는 커플링을 방지하고 테스트 흐름을 쉽게 추론하기 위해 자체 DB 데이터를 추가하고 실행해야 합니다. 실제로 성능 향상(테스트를 실행하기 전에 DB 데이터를 준비(['테스트 픽스쳐'라고도 합니다](https://en.wikipedia.org/wiki/Test_fixture)))을 위해 이를 위반하는 테스터들이 많습니다. 성능은 실제로 유효한 문제이지만 완화될 수 있습니다(2.2 컴포넌트 테스트 참고). 그러나 테스트 복잡성은 대부분의 다른 고려사항들을 통제해야 하는 고통을 수반합니다. 각 테스트에 필요한 DB 레코드를 명시적으로 추가하고, 해당 데이터에 대해서만 테스트를 수행하십시오. 성능이 중요한 문제가 되는 경우 - 데이터를 변경하지 않는 테스트 모음(예: 쿼리)에 대해서 데이터를 준비하는 형태로 타협할 수 있습니다. -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +
    +❌ **그렇지 않으면:** 테스트 실패, 배포 중단으로 팀원들이 귀중한 시간을 소비할 것입니다. 버그가 있습니까? 조사해보니 '없습니다' - 두 테스트에서 동일한 테스트 데이터를 변겨안 것으로 보입니다.
    -
    Code Examples +
    예제 코드
    -### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :thumbsdown: 올바르지 않은 예: 테스트는 독립적이지 않으며 글로벌 훅에 의한 DB 데이터에 의존 ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") ```javascript before(() => { - //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + // 사이트 및 관리자 데이터를 DB에 추가. 데이터는 어디에 있습니까? 외부에. 외부 JSON 또는 마이그레이션 프레임워크에 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 +it("사이트 이름을 업데이트 할 때, 성공을 확인한다.", async () => { + // 사이트 이름 "portal"이 존재한다는 것을 알고있습니다. 시드파일에서 봤습니다. 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 +it("사이트 이름을 쿼리할 때, 올바른 사이트 이름을 얻는다.", async () => { + // 사이트 이름 "portal"이 존재한다는 것을 알고있습니다. 시드파일에서 봤습니다. const siteToCheck = await SiteService.getSiteByName("Portal"); - expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ + expect(siteToCheck.name).to.be.equal("Portal"); // 실패! 이전 테스트에서 이름이 변경되었습니다. ㅠㅠ }); - ``` +
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: 올바른 예: 우리는 테스트 내부에만 머물 수 있으며, 각 테스트는 자체 데이터 세트에서 동작합니다. ```javascript -it("When updating site name, get successful confirmation", async () => { - //test is adding a fresh new records and acting on the records only +it("사이트 이름을 업데이트 할 때, 성공을 확인한다.", async () => { + // 테스트는 새로운 레코드를 새로 추가하고 해당 레코드에 대해서만 동작합니다. const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); @@ -567,13 +566,11 @@ it("When updating site name, get successful confirmation", async () => { expect(updateNameResult).to.be(true); }); - ```
    - -
    +

    ## ⚪ ️ 1.10 Don’t catch errors, expect them :white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations From ee492a27515188967ed2618c19e1579421efa8e8 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Wed, 11 Sep 2019 00:24:47 +0900 Subject: [PATCH 081/502] Translate into Korean 1.10 to 1.11 --- readme.kr.md | 76 +++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index e9de9fbd..fce412a0 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -572,98 +572,88 @@ it("사이트 이름을 업데이트 할 때, 성공을 확인한다.", async ()

    -## ⚪ ️ 1.10 Don’t catch errors, expect them -:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations +## ⚪ ️ 1.10 오류를 catch 하지말고 expect 하십시오. -A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user -
    +:white_check_mark: **이렇게 해라:** 오류를 발생시키는 입력값을 assert 할 때, try-catch-finally를 사용하고 catch 블럭에서 assert 하는게 맞아 보일수도 있습니다. 아래 예는 테스트 의도와 결과 expectation을 숨기는 어색하고 장황한 테스트 사례입니다. +보다 우아한 대안은 한줄짜리 Chai assertion을 사용하는 것 입니다: expect(method).to.throw (혹은 Jest: expect(method).toThrow()). 오류 유형을 알려주는 속성이 예외에 포함되어야 합니다. 그렇지 않고 일반적인 오류를 발생시키면 어플리케이션은 사용자에게 실망스러운 메시지를 표시하는 것 밖에 할 수 없습니다. -❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +
    +❌ **그렇지 않으면:** 무엇이 잘못되었는지 테스트 보고서(예: CI 보고서)에서 추론하기 어려울 것입니다.
    -
    Code Examples +
    예제 코드
    -### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch +### :thumbsdown: 올바르지 않은 예: try-catch로 오류가 존재한다고 assert 하는 긴 테스트 사례 ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") ```javascript -it("When no product name, it throws error 400", async() => { -let errorWeExceptFor = null; -try { - const result = await addNewProduct({name:'nest'});} -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 +it("제품명이 없으면 400 오류를 던진다.", async() => { + let errorWeExceptFor = null; + try { + const result = await addNewProduct({name:'nest'});} + catch (error) { + expect(error.code).to.equal('InvalidInput'); + errorWeExceptFor = error; + } + expect(errorWeExceptFor).not.to.be.null; + // 이 asserting이 실패하면, 테스트 결과에서 누락된 입력값에 대한 단어는 알 수 없고 + // 입력값이 null 이라는 것만 알 수 있습니다. }); - ``` +
    -### :clap: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM +### :clap: 올바른 예: QA나 PM이라도 쉽게 이해할 수 있고 읽기 쉬운 expectation ```javascript -it.only("When no product name, it throws error 400", async() => { +it.only("제품명이 없으면 400 오류를 던진다.", async() => { expect(addNewProduct)).to.eventually.throw(AppError).with.property('code', "InvalidInput"); }); - ```
    - - -

    -## ⚪ ️ 1.11 Tag your tests - -:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ -
    +## ⚪ ️ 1.11 테스트에 태깅하십시오. +:white_check_mark: **이렇게 해라:** 다른 테스트는 꼭 다른 시나리오에서 실행해야 합니다: 개발자가 파일을 저장하거나 커밋을 할 때 빠르고, IO가 많이 없는 테스트를 실행해야 합니다. 전체 end-to-end 테스트는 일반적으로 새로운 Pull Request가 제출되었을 때 실행됩니다. 등.. 이러한 경우에 #cold #api #sanity와 같은 키워드로 테스트에 태깅하면 테스트를 효율적으로 grep 할 수 있고, 원하는 하위세트를 호출할 수 있습니다. 예) Mocha를 이용해서 sanity 테스트 그룹만 실행하는 방법입니다: mocha - grep 'sanity' -❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests +
    +❌ **그렇지 앟으면:** 개발자가 작은 변경을 할 때마다 수십 개의 DB 쿼리를 수행하는 테스트를 포함한 모든 테스트를 실행한다면, 속도가 매우 느려져 개발자가 테스트를 수행하지 않게 만들 것입니다.
    -
    Code Examples +
    예제 코드
    -### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) +### :clap: 올바른 예: 테스트를 '#cold-test'로 태깅하면 테스트를 수행하는 사람이 빠른 테스트만 실행할 수 있습니다(IO를 수행하지 않고 개발자가 코딩하는 중에도 자주 실행할 수 있는 테스트 cold === quick). ![](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 +// 이 테스트는 빠르고(DB 없음) 현재 사용자/CI가 자주 실행할 수 있는 태그를 지정하고 있습니다. +describe('주문 서비스', function() { + describe('새 주문 추가 #cold-test #sanity', function() { + test('시나리오 - 통화가 제공되지 않음. 예외 - 기본 통화 사용 #sanity', function() { + // code logic here }); }); }); - - ```
    - - -

    ## ⚪ ️1.12 Other generic good testing hygiene From 5c8c6524836cdedbb09a8fdb04f0ef5b862c9399 Mon Sep 17 00:00:00 2001 From: sury Date: Thu, 12 Sep 2019 11:50:06 +0900 Subject: [PATCH 082/502] translate into Korean 2.1 --- readme.kr.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index fce412a0..02b5d02a 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -670,32 +670,30 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr # Section 2️⃣: Backend Testing -## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid +## ⚪ ️2.1 당신의 테스트 포트폴리오를 풍부하게 하십시오: 단위 테스트와 피라미드를 넘어서세요. -:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **이렇게 해라:** 10년이 넘은 모델인 [테스트 피라미드](https://martinfowler.com/bliki/TestPyramid.html)는 세 가지 테스트 유형을 제시하고 대다수 개발자의 테스트 전략에 영향을 주는 훌륭한 모델입니다. 동시에, 몇 가지 반짝이는 새로운 테스트 기술들이 등장하였지만 모두 테스트 피라미드의 그림자 뒤로 사라졌습니다. 우리가 최근 10년간 보아 온 극적인 기술의 변화들(Microservices, cloud, serverless)을 고려할 때, 아주 오래된 모델 하나가 *모든* 어플리케이션 유형에 적합하다는 것이 가능한가요? 테스트 세계는 새로운 기술을 받아들이는 것을 고려하지 않나요? -Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. +오해는 하지 마세요. 2019 테스트 피라미드에서 TDD와 단위 테스트는 여전히 강력한 기술이고 아마도 많은 어플리케이션에 가장 어울리는 기술입니다. 다른 모델과 마찬가지로, 테스트 미라미드는 유용하지만 [그것이 항상 맞는 것은 아닙니다](https://en.wikipedia.org/wiki/All_models_are_wrong). 예를 들어, 어떤 IOT 어플리케이션을 생각해 봅시다. 이 어플리케이션은 다수의 이벤트를 Kafka/RabbitMQ 같은 메세지 버스로 보내고 다시 데이터 웨어하우스로 흘려보냅니다. 그리고 이 데이터들은 어떤 분석 UI에서 조회됩니다. 우리는 정말 우리의 테스트 예산의 50%를 통합 중심적(intergration-centric)이고 로직이 거의 없는 어플리케이션의 단위 테스트를 작성하는데 할애해야 할까요? 어플리케이션 유형들이 다양해질 수록(bots, crypto, Alexa-skills) 테스트 피라미드가 적합하지 않은 시나리오들을 발견할 가능성이 커집니다. -It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks +지금이 당신의 테스트 포트폴리오를 넓히고 더 많은 테스트 유형들에 익숙해질 시간입니다. (다음 총알에서 몇 가지 아이디어들을 제안합니다.) 테스트 피라미드 같은 모델들도 염두에 둘 뿐만 아니라 당신이 직면하고 있는 현실 세계의 문제들에 적합한 테스트 유형들을 찾으세요. ("우리 API 깨졌어. Consumer-driven contract 테스트 작성하자!" 처럼요.) 위험성 분석을 기반으로 포르폴리오를 구축하는 투자자처럼 당신의 테스트를 다양화하세요 - 문제가 발생할 수 있는 부분을 가늠하고 잠재적 위험성을 줄일 수 있는 예방 방법을 찾으세요. -A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] +주의 사항 : 소프트웨어 세계에서의 TDD 논쟁은 전형적인 잘못된 이분법입니다. 어떤 사람들은 TDD를 모든 곳에 적용하라고 주장하지만, 다른 일부는 TDD를 악마라고 생각합니다. 절대적으로 한쪽만 주장하는 사람들은 모두 틀렸습니다 :]
    -❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes - +❌ **그렇지 않으면:** 당신은 굉장한 RIO를 주는 몇 가지 툴들을 놓칠 것입니다. Fuzz, lint, mutation 테스트들은 단 10분만에 당신에게 가치를 제공할 수 있습니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘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’") +### :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) +예제: [YouTube: “Beyond Unit Tests: 5 Shiny Node.JS Test Types (2018)” (Yoni Goldberg)](https://www.youtube.com/watch?v=-2zP494wdUY&feature=youtu.be)
    From 323f6df95c84c5a99f71c3412f475109c00b6615 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Sat, 14 Sep 2019 01:18:42 +0900 Subject: [PATCH 083/502] Translate into Korean 1.12 - Fix typo. --- readme.kr.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 02b5d02a..74855db3 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -656,21 +656,21 @@ describe('주문 서비스', function() {

    -## ⚪ ️1.12 Other generic good testing hygiene -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +## ⚪ ️ 1.12 일반적인 좋은 테스트 기법들 + +:white_check_mark: **이렇게 해라:** 이 글은 Node.js와 관련이 있거나 최소한 Node.js로 예를 들 수 있는 테스트 조언에 중점을두고 있습니다. 그러나 이번에는 Node.js가 아니지만 잘 알려진 팁들을 포함하고 있습니다. -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) -
    +[TDD 원칙](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/)을 배우고 연습하십시오 - 많은 사람들에게 매우 가치가 있지만, 자신의 스타일에 맞지 않을 수 있습니다. [실패-성공-리페토링 스타일](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html)로 코드 작성 전에 테스트를 작성하는 것을 고려하십시오. 버그를 발견하면 각 테스트에서 정확히 한 가지만 확인하도록 하십시오. 수정하기 전에 앞으로 이 버그를 발견 할 테스트를 작성하십시오. 테스트가 성공하기 전에 각 테스트가 한번 이상 실패하도록 하십시오. 테스트를 만족시키는 간단한 코드를 작성하여 빠르게 모듈을 시작하십시오 - 점신적으로 리펙토링하여 프로덕션 등급의 수준으로 가져가십시오. 환경(경로, OS 등)에 대한 종속성을 피하십시오. +
    -❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades +❌ **그렇지 않으면:** 수십 년 동안 수집 된 아주 소중한 조언을 놓치게 될 것입니다.

    +# 섹션 2️⃣: 백엔드 테스트 -# Section 2️⃣: Backend Testing - -## ⚪ ️2.1 당신의 테스트 포트폴리오를 풍부하게 하십시오: 단위 테스트와 피라미드를 넘어서세요. +## ⚪ ️ 2.1 당신의 테스트 포트폴리오를 풍부하게 하십시오: 단위 테스트와 피라미드를 넘어서세요. :white_check_mark: **이렇게 해라:** 10년이 넘은 모델인 [테스트 피라미드](https://martinfowler.com/bliki/TestPyramid.html)는 세 가지 테스트 유형을 제시하고 대다수 개발자의 테스트 전략에 영향을 주는 훌륭한 모델입니다. 동시에, 몇 가지 반짝이는 새로운 테스트 기술들이 등장하였지만 모두 테스트 피라미드의 그림자 뒤로 사라졌습니다. 우리가 최근 10년간 보아 온 극적인 기술의 변화들(Microservices, cloud, serverless)을 고려할 때, 아주 오래된 모델 하나가 *모든* 어플리케이션 유형에 적합하다는 것이 가능한가요? 테스트 세계는 새로운 기술을 받아들이는 것을 고려하지 않나요? @@ -682,8 +682,7 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr
    - -❌ **그렇지 않으면:** 당신은 굉장한 RIO를 주는 몇 가지 툴들을 놓칠 것입니다. Fuzz, lint, mutation 테스트들은 단 10분만에 당신에게 가치를 제공할 수 있습니다. +❌ **그렇지 않으면:** 당신은 굉장한 ROI를 주는 몇 가지 툴들을 놓칠 것입니다. Fuzz, lint, mutation 테스트들은 단 10분만에 당신에게 가치를 제공할 수 있습니다.
    @@ -699,12 +698,8 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr ![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "A test name that constitutes 3 parts") -
    - - -

    ## ⚪ ️2.2 Component testing might be your best affair From d67d24ea5ea22e73965f3144704a87c89fc80192 Mon Sep 17 00:00:00 2001 From: leo lee Date: Sun, 15 Sep 2019 17:00:37 +0900 Subject: [PATCH 084/502] - Translation into Korean(3.1, 3.2) --- readme.kr.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 74855db3..6ea44346 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -917,32 +917,32 @@ it("When updating site name, get successful confirmation", async () => {

    -# Section 3️⃣: Frontend Testing +# 섹션 3️⃣: 프론트엔드 테스트 -## ⚪ ️ 3.1. Separate UI from functionality +## ⚪ ️ 3.1. 기능으로부터 화면을 분리하십시오 -:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI +:white_check_mark: **이렇게 해라:** 컴포넌트 로직을 테스트할때, 화면의 세부사항들은 제외되어야할 노이즈가 됩니다. 그것을 제외함으로써 당신의 테스트들은 순수한 데이터에 집중할 수 있습니다. 실제로, 그래픽 구현에 너무 결합되지 않는 추상적인 방법을 통해 요구되어지는 데이터를 마크업으로부터 추출하십시오. 그리고 느리게 만드는 애니메이션들을 제외한 오직 순수한 데이터를 검증하십시오(vs HTML/CSS 화면 세부사항). 당신은 렌더링하는 것을 피하고 오직 화면의 뒷부분(서비스, 액션, 스토어등과 같은)만을 테스트 하려고 할 수도 있습니다. 하지만, 이것은 실제와 같지도 않으며 심지어 화면에 올바른 데이터가 도달하지 않은 경우를 나타내지도 않는 가짜 테스트에서의 결과가 될 것 입니다.
    -❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation +❌ **그렇지 않으면:** 당신의 테스트의 순수하게 계산된 데이터는 10ms 내에 준비될수도 있지만, 전체 테스트는 화려하고 불필요한 애니메이션 때문에 500ms(100 테스트 = 1분) 동안 지속될 것 입니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Separating out the UI details +### :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', () => { +test('오직 VIP를 보기위해 사용자목록을 표시했을때, 오직 VIP 멤버들만 보여져야 한다', () => { // Arrange const allUsers = [ { id: 1, name: 'Yoni Goldberg', vip: false }, @@ -952,19 +952,19 @@ test('When users-list is flagged to show only VIP, should display only VIP membe // Act const { getAllByTestId } = render(); - // Assert - Extract the data from the UI first + // 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 + expect(allRenderedUsers).toEqual(allRealVIPUsers); // 화면에 아닌 데이터를 비교 }); ```
    -### :thumbsdown: Anti Pattern Example: Assertion mix UI details and data +### :thumbsdown: 잘못된 예: 화면 세부사항들과 데이터를 섞어서 검증 ```javascript -test('When flagging to show only VIP, should display only VIP members', () => { +test('오직 VIP를 보기위해 사용자목록을 표시했을때, 오직 VIP 멤버들만 보여져야 한다', () => { // Arrange const allUsers = [ {id: 1, name: 'Yoni Goldberg', vip: false }, @@ -974,7 +974,7 @@ test('When flagging to show only VIP, should display only VIP members', () => { // Act const { getAllByTestId } = render(); - // Assert - Mix UI & data in assertion + // Assert - 화면과 데이터를 섞어서 검증 expect(getAllByTestId('user')).toEqual('[
  • John Doe
  • ]'); }); @@ -988,21 +988,21 @@ test('When flagging to show only VIP, should display only VIP members', () => {

    -## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change +## ⚪ ️ 3.2 변하지 않은 요소들에 기반해서 HTML 엘리먼트들을 찾으십시오 -:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +:white_check_mark: **이렇게 해라:** CSS 검색자들과 다르게 양식 레이블들과 같이 그래픽 변경에도 살아남을 요소들을 기반으로 HTML 엘리먼트들을 찾으십시오. 만약 설계된 엘리먼트가 이와 같은 요소들을 가지고 있지 않다면, 'test-id-submit-button' 과 같이 테스트에 한정된 요소를 만드십시오. 이 방법은 당신의 기능/로직 테스트들이 룩앤필때문에 절대 망가지지 않을 것을 보장할 뿐만 아니라, 이 엘리먼트와 요소가 테스트에 의해 사용되어지고 제거되어서는 안된다는것을 팀 전체에게 명확하게 합니다.
    -❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **그렇지 않으면:** 당신은 로그인 기능을 테스트하기를 원합니다. 이 기능은 많은 컴포넌트들, 로직 그리고 서비스들에 걸쳐져 있고 모든 것은 완벽하게 준비되어 있습니다 - 스텁, 스파이, Ajax 호출은 격리되어져 있습니다. 모든것은 완벽한 것 처럼 보입니다. 그렇지만, 이 테스트는 디자이너에 의해 div 클래스 이름이 'thick-border' 에서 'thin-border'로 바뀌었기 때문에 실패합니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing +### :clap: 올바른 예: 테스트를 위해 한정된 요소를 사용해서 엘리먼트를 찾으십시오 ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") @@ -1017,8 +1017,8 @@ test('When flagging to show only VIP, should display only VIP members', () => { ``` ```javascript -// this example is using react-testing-library - test('Whenever no data is passed to metric, show 0 as default', () => { +// react-testing-library를 사용한 예제 + test('metric에 데이터가 전달되지 않으면, 0을 기본값으로 보여준다', () => { // Arrange const metricValue = undefined; @@ -1032,15 +1032,15 @@ test('When flagging to show only VIP, should display only VIP members', () => {
    -### :thumbsdown: Anti-Pattern Example: Relying on CSS attributes +### :thumbsdown: 잘못된 예: CSS 요소들에 의존 ```html -{value} +{value} ``` ```javascript -// this exammple is using enzyme -test('Whenever no data is passed, error metric shows zero', () => { +// enzyme을 사용한 예제 +test('데이터가 전달되지 않으면, 0을 보여준다', () => { // ... expect(wrapper.find("[className='d-flex-column']").text()).toBe("0"); From 76f62965c38da95de5fb0006c47290571c51ed7b Mon Sep 17 00:00:00 2001 From: sury Date: Wed, 18 Sep 2019 18:33:16 +0900 Subject: [PATCH 085/502] Translation into Korean (2.2,2.3) --- readme.kr.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 6ea44346..47f0dde0 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -702,24 +702,23 @@ describe('주문 서비스', function() {

    -## ⚪ ️2.2 Component testing might be your best affair +## ⚪ ️2.2 컴포넌트 테스트가 최선의 방법일 수 있다. -:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +:white_check_mark: **이렇게 해라:** 각각의 단위 테스트는 어플리케이션의 매우 작은 부분만을 커버하고 전체를 모두 커버하기에는 비용이 많이 듭니다. 반면에, end-to-end 테스트는 간단하게 많은 부분을 커버할 수 있지만 깊이가 얕고 더 느립니다. 그렇다면 균형 잡힌 접근법을 적용하여 단위 테스트보다는 크지만 end-to-end 테스트보다는 작은 테스트를 작성하는 것은 어떨까요? 컴포넌트 테스트는 테스트 세계에서 잘 알려지지 않은 방법입니다. - 컴포넌트 테스트는 다음의 두 가지 이점을 모두 제공합니다: 합리적인 성능과 TDD 패턴을 적용할 수 있는 가능성 + 현실적이면서 훌륭한 커버리지 -Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +컴포넌트 테스트는 마이크로 서비스 '단위'에 중점을 두고 API에 대하여 동작합니다. 마이크로서비스 그 자체에 속한 것들 (예를들면, 실제 DB 또는 해당 DB의 인-메모리 버전)은 모킹(Mock)하지 않고, 다른 마이크로서비스 호출과 같은 외부적인 것은 스텁(Stub)합니다. 그렇게 함으로써 우리는 우리가 배포하는 것을 테스트하고 어플리케이션의 바깥쪽에서 안쪽으로 접근하며, 적당한 시간 안에서 큰 자신감을 얻을 수 있습니다.
    -❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage - +❌ **그렇지 않으면:** 시스템 커버리지가 20%에 불과하다는 것을 깨닫기까지 단위 테스트를 작성하는 데 오랜 시간이 걸릴 수 있습니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) +### :clap: 올바른 예: Supertest를 통해 프로세스 내 Express API에 접근할 수 있습니다. (빠르고 다양한 계층을 커버함) ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Jest") @@ -730,22 +729,23 @@ Component tests focus on the Microservice ‘unit’, they work against the API,

    -## ⚪ ️2.3 Ensure new releases don’t break the API using +## ⚪ ️2.3 신규 릴리즈가 API 사용을 깨지게 하지 마십시오. -:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration -
    +:white_check_mark: **이렇게 해라:** 당신의 마이크로서비스는 다수의 클라이언트를 가지고 있고 호환성의 이유로 여러 버전의 서비스를 운영합니다 (모든 사람을 만족시키기 위해서). 그런 상황에서 당신이 일부 필드를 변경하면 이 필드를 믿고 사용하던 일부 중요한 클라이언트는 화가 날 것입니다. 이것은 통합(integration) 세계에서 해결하기 어려운 진퇴양난에 놓인 문제입니다: 서버 사이드가 여러 클라이언트들의 모든 기댓값을 고려하는 것은 매우 어려운 일입니다. - 반면에, 서버가 릴리즈 날짜를 결정하기 때문에 클라이언트는 어떠한 테스트도 수행할 수 없습니다. +[소비자 주도 계약 테스트(Consumer-driven contracts)와 PACT 프레임워크](https://docs.pact.io/)는 매우 파괴적인 방법으로 이러한 프로세스를 표준화하기 위해 나타났습니다. - 서버가 서버의 테스트 계획을 결정하지 않고, 클라이언트가 서버의 테스트를 결정합니다! PACT는 클라이언트의 기댓값을 기록하여 "브로커"라는 공유된 위치에 올려둘 수 있습니다. 그러면 서버는 그 기댓값을 당겨 받을 수 있고 빌드할 때마다 PACT 라이브러리를 사용하여 깨진 계약(contract - 충족되지 않은 클라이언트의 기댓값)을 감지할 수 있습니다. 이렇게 함으로써, 모든 서버-클라이언트 API 간 일치하지 않은 것들을 빌드/CI 환경에서 조기에 잡을 수 있고 당신의 큰 절망감을 줄여줄 수 있을 것입니다. +
    -❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear +❌ **그렇지 않으면:** 대안은 수동 배포나 배포에 대한 두려움을 안고 가는 것 뿐입니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: +### :clap: 올바른 예: ![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") From 4ba6c308e26f5543b8617024bf0f5c9cee1dc951 Mon Sep 17 00:00:00 2001 From: sury Date: Thu, 19 Sep 2019 13:25:51 +0900 Subject: [PATCH 086/502] Translation into Korean(2.4,2.5) --- readme.kr.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 47f0dde0..374e163b 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -760,32 +760,32 @@ describe('주문 서비스', function() {

    -## ⚪ ️ 2.4 Test your middlewares in isolation +## ⚪ ️ 2.4 당신의 미들웨어를 독립적으로 테스트 하십시오. -:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +:white_check_mark: **Do:** 많은 사람들은 미들웨어(Middleware) 테스트를 피합니다. 왜냐하면 미들웨어 테스트는 시스템의 작은 부분일 뿐이고 라이브 Express 서버가 필요하기 때문입니다. 하지만 두 가지 이유 모두 틀렸습니다. - 미들웨어는 작지만 모든 요청 또는 대부분의 요청에 영향을 미치고, {req,res} JS 객체를 가지는 순수한 함수로 쉽게 테스트할 수 있기 때문입니다. 미들웨어 함수를 테스트하기 위해서는 단지 함수를 불러오고 함수가 올바르게 동작하는 것을 확인하기 위해 {req, res} 객체에 대한 인터렉션을 스파이(spy)([예를들어 Sinon을 사용](https://www.npmjs.com/package/sinon))하면 됩니다. 라이브러리 [node-mock-http](https://www.npmjs.com/package/node-mocks-http)는 더 나아가서 행위에 대한 스파이와 함께 {req, res} 객체도 테스트할 수 있습니다. 예를 들어, response 객체의 http 상태가 기대했던 값과 일치하는지 여부를 확인(assert)할 수 있습니다. (아래 예제를 보세요)
    -❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests +❌ **Otherwise:** Express 미들웨어에서의 버그 === 모든 요청 또는 대부분의 요청에서의 버그
    -
    Code Examples +
    코드 예제
    -### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :clap:올바른 예: 네트워크 호출 없이 전체 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', () => { +//Jest 문법으로 Mocha의 describe() & it()과 동일 +test('헤더에 인증정보가 없는 요청은, http status 403을 리턴해야한다.', () => { const request = httpMocks.createRequest({ method: 'GET', url: '/user/42', @@ -807,24 +807,24 @@ test('A request without authentication header, should return http status 403', (

    -## ⚪ ️2.5 Measure and refactor using static analysis tools -:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) +## ⚪ ️2.5 정적 분석 도구를 사용하여 측정하고 리팩토링 하십시오. +:white_check_mark: **이렇게 해라:** 정적 분석 도구를 사용하면 코드 품질을 개선하고 코드를 유지 관리할 수 있는 객관적인 방법을 제공할 수 있습니다. 정적 분석 도구를 당신의 CI 빌드에 추가하여 코드 냄새(code smell)가 발견되면 중단되도록 할 수 있습니다. 정적 분석 도구가 일반적인 린트(lint) 도구보다 더 좋은 점은 여러 파일들의 컨텍스트 안에서 품질을 검사하고(예: 중복 탐지), 고급 분석(예: 코드 복잡성)을 할 수 있으며 코드 이슈에 대한 히스토리와 프로세스를 추적할 수 있다는 것입니다. 사용할 수 있는 정적 분석 도구 두 가지는 [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube))와 [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate))입니다. Credit:: [Keith Holliday](https://github.com/TheHollidayInn)
    -❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix +❌ **그렇지 않으면:** 코드 품질이 좋지 않으면 버그와 성능은 빛나는 새 라이브러리나 최신 기능으로 해결할 수 없는 문제가 될 것입니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: +### :clap: 올바른 예: 복잡도가 높은 함수를 찾아내는 상용 도구인 CodeClimate: ![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg "Examples with CodeClimate") From bbfe0d6ead21d5a99a6f215bedc6dc44d1be694a Mon Sep 17 00:00:00 2001 From: sury05 Date: Mon, 23 Sep 2019 19:57:48 +0900 Subject: [PATCH 087/502] Translation into Korean(2.6, 2.7) --- readme.kr.md | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 374e163b..6ad3c9d2 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -676,7 +676,7 @@ describe('주문 서비스', function() { 오해는 하지 마세요. 2019 테스트 피라미드에서 TDD와 단위 테스트는 여전히 강력한 기술이고 아마도 많은 어플리케이션에 가장 어울리는 기술입니다. 다른 모델과 마찬가지로, 테스트 미라미드는 유용하지만 [그것이 항상 맞는 것은 아닙니다](https://en.wikipedia.org/wiki/All_models_are_wrong). 예를 들어, 어떤 IOT 어플리케이션을 생각해 봅시다. 이 어플리케이션은 다수의 이벤트를 Kafka/RabbitMQ 같은 메세지 버스로 보내고 다시 데이터 웨어하우스로 흘려보냅니다. 그리고 이 데이터들은 어떤 분석 UI에서 조회됩니다. 우리는 정말 우리의 테스트 예산의 50%를 통합 중심적(intergration-centric)이고 로직이 거의 없는 어플리케이션의 단위 테스트를 작성하는데 할애해야 할까요? 어플리케이션 유형들이 다양해질 수록(bots, crypto, Alexa-skills) 테스트 피라미드가 적합하지 않은 시나리오들을 발견할 가능성이 커집니다. -지금이 당신의 테스트 포트폴리오를 넓히고 더 많은 테스트 유형들에 익숙해질 시간입니다. (다음 총알에서 몇 가지 아이디어들을 제안합니다.) 테스트 피라미드 같은 모델들도 염두에 둘 뿐만 아니라 당신이 직면하고 있는 현실 세계의 문제들에 적합한 테스트 유형들을 찾으세요. ("우리 API 깨졌어. Consumer-driven contract 테스트 작성하자!" 처럼요.) 위험성 분석을 기반으로 포르폴리오를 구축하는 투자자처럼 당신의 테스트를 다양화하세요 - 문제가 발생할 수 있는 부분을 가늠하고 잠재적 위험성을 줄일 수 있는 예방 방법을 찾으세요. +지금이 당신의 테스트 포트폴리오를 넓히고 더 많은 테스트 유형들에 익숙해질 시간입니다. (다음 항목에서 몇 가지 아이디어들을 제안합니다.) 테스트 피라미드 같은 모델들도 염두에 둘 뿐만 아니라 당신이 직면하고 있는 현실 세계의 문제들에 적합한 테스트 유형들을 찾으세요. ("우리 API 깨졌어. Consumer-driven contract 테스트 작성하자!" 처럼요.) 위험성 분석을 기반으로 포르폴리오를 구축하는 투자자처럼 당신의 테스트를 다양화하세요 - 문제가 발생할 수 있는 부분을 가늠하고 잠재적 위험성을 줄일 수 있는 예방 방법을 찾으세요. 주의 사항 : 소프트웨어 세계에서의 TDD 논쟁은 전형적인 잘못된 이분법입니다. 어떤 사람들은 TDD를 모든 곳에 적용하라고 주장하지만, 다른 일부는 TDD를 악마라고 생각합니다. 절대적으로 한쪽만 주장하는 사람들은 모두 틀렸습니다 :] @@ -838,72 +838,70 @@ Credit::
    -## ⚪ ️2.7 Avoid global test fixtures and seeds, add data per-test - -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +## ⚪ ️2.7 글로벌한 초기 테스트 데이터 집합을 만들지 말고 각 테스트 마다 데이터를 추가하십시오. +:white_check_mark: **이렇게 해라:** 황금률(섹션 0)에 따르면 각 테스트는 커플링을 방지하고 테스트 흐름에 대해서 쉽게 추론하기 위해 자신의 DB 데이터들을 추가하고 해당 데이터로 테스트되어야 합니다. 하지만 현실 세계에선 성능 향상을 위해 테스트를 실행하기 전에 초기 데이터를 DB에 추가하는(‘test fixture’라고 알려져 있음) 테스터들에 의해서 이 규칙은 종종 깨지곤 합니다. 성능은 실제로 중요한 문제입니다. - 이 문제는 완화될 수 있습니다 ('컴포넌트 테스트' 섹션을 보세요). 하지만 테스트 복잡성은 대부분의 다른 고려사항들을 지배해 버리는 더욱 고통스런 문제입니다. 실질적으로 각 테스트 케이스에 필요한 DB 레코드만 명시적으로 추가하고 해당 레코드를 가지고만 테스트하세요. 만약 성능이 중요한 문제라면 - 데이터를 변경하지 않는 테스트들에 대해서만 초기 데이터를 채우는 형태로 타협할 수 있습니다. (예: 쿼리)
    -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data - +❌ **그렇지 않으면:** 테스트가 실패하고 배포는 중단되어 팀원들은 지금 소중한 시간을 할애해야 합니다. 버그가 있습니까? 찾아봅시다, 오 이런 - 두 개의 테스트가 동일한 테스트 데이터(seed data)를 변경한 것으로 보입니다.
    -
    Code Examples +
    코드 예제
    -### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :thumbsdown: 올바르지 않은 예: 테스트는 독립적이지 않고 테스트마다 글로벌 DB 데이터를 사용하도록 훅이 걸려있습니다. ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") ```javascript before(() => { - //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + // DB에 사이트와 어드민 데이터를 추가합니다. 데이터는 어디에 있나요? 외부에 있습니다. 외부 json 파일이나 마이그레이션 프레임워크에 있습니다. 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 +it("사이트 이름을 변경하면, 성공 결과값을 받아온다", async () => { + //"portal"이라는 이름의 사이트가 있다는 것을 알고 있습니다. - 씨드 파일에서 봤습니다. 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 +it("사이트 이름으로 조회했을때, 해당 사이트를 가져온다", async () => { + //"portal"이라는 이름의 사이트가 있다는 것을 알고 있습니다. - 씨드 파일에서 봤습니다. const siteToCheck = await SiteService.getSiteByName("Portal"); - expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ + expect(siteToCheck.name).to.be.equal("Portal"); //실패! 이전 테스트에서 이름이 변경되었습니다 :[ }); ```
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: 올바른 예: 테스트 안에서만 머물며 각 테스트는 자신의 데이터 세트 안에서만 동작합니다. ```javascript -it("When updating site name, get successful confirmation", async () => { - //test is adding a fresh new records and acting on the records only +it("사이트 이름을 변경하면, 성공 결과값을 받아온다", async () => { + //테스트는 새로운 신규 레코드를 추가하고 그 레코드를 가지고 동작합니다. const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); From 6509c23243fce1fb05bf3710797b887bbc6de87c Mon Sep 17 00:00:00 2001 From: sury05 Date: Tue, 24 Sep 2019 14:55:41 +0900 Subject: [PATCH 088/502] Translation into Korean (4.1) --- readme.kr.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 6ad3c9d2..826d20c0 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1537,34 +1537,32 @@ cy.eyesCheckWindow('mark as completed');

    -# Section 4️⃣: Measuring Test Effectiveness +# Section 4️⃣: 테스트 효과 측정

    -## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number +## ⚪ ️ 4.1 자신감을 갖기에 충분한 커버리지를 확보하십시오. ~80%가 이상적인 것 같습니다. -:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. - -Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets +:white_check_mark: **이렇게 해라:** 테스트의 목적은 빠른 변경에 대한 충분한 자신감을 갖기 위한 것입니다. 분명히 더 많은 코드가 테스트 될수록 팀은 더 자신감을 가질 수 있습니다. 커버리지는 얼마나 많은 라인(브랜치, 구문(statements) 등)이 테스트에 의해 커버되었는지에 대한 지표입니다. 그렇다면 어느 정도가 충분할까요? 10–30%는 빌드 정확성에 대해 판단하기에는 분명히 너무 낮습니다. 반면에 100%는 비용이 많이 들고 정작 당신의 관심을 중요한 부분이 아닌 테스트 코드로 옮겨버릴지도 모릅니다. 이것에 대한 답은 수치는 어플리케이션 유형과 같은 다양한 요소들에 따라 달라진다는 것입니다. - 만약 당신이 Airbus A380의 차세대 버전을 만들면 100%로 맞춰야 하지만 웹툰 사이트라면 50%면 충분합니다. 비록 테스트에 열성인 대부분의 사람들은 적절한 커버리지 임계값이 상황에 따라 달라져야 한다고 하지만, 그들 중 대부분은 대다수의 어플리케이션을 만족하기 위해서 경험상으로 80%([마틴 파울러: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html))가 적절하다고 얘기합니다. +구현 팁: 당신의 CI 환경에서 커버리지 임계치를 설정하여 그 기준에 미치지 못하면 빌드를 멈추도록 하고 싶을 것입니다. (컴포넌트 당 임계치를 설정하는 것도 가능합니다. 아래 예제 코드를 보세요). 이 위에, 빌드 커버리지 감소에 대한 감지도 고려해 보세요. (새로 커밋 된 코드가 커버리지에 못 미칠 때) - 이렇게 함으로써 개발자들이 커버리지를 올리거나 적어도 유지하도록 압박할 수 있습니다. 말한대로 커버리지는 오직 하나의 양적 지표일 뿐 테스트의 견고성을 나타내기에는 충분하지 않습니다. 그리고 다음 항목에 나와있는 것처럼 당신을 속일 수 있습니다.
    -❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down - +❌ **그렇지 않으면:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down
    -
    Code Examples +
    코드 예제
    -### :clap: Example: A typical coverage report +### :clap: 예제: 일반적인 커버리지 보고서 ![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report")
    -### :clap: Doing It Right Example: Setting up coverage per component (using Jest) +### :clap: 올바른 예: 컴포넌트 당 커버리지를 설정하십시오. (Jest를 사용하여) ![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Cypress to illustrate the idea") From 052aebf3c906b7aa2e7de4a4bc5f0e6c6aace157 Mon Sep 17 00:00:00 2001 From: sury05 Date: Wed, 25 Sep 2019 11:25:58 +0900 Subject: [PATCH 089/502] Translation into Korean (4.2) --- readme.kr.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 826d20c0..3062f4c4 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1575,22 +1575,22 @@ cy.eyesCheckWindow('mark as completed');

    -## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities +## ⚪ ️ 4.2 커버리지 리포트를 확인하여 테스트 되지 않은 부분과 기타 이상한 점들을 감지하십시오. -:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +:white_check_mark: **이렇게 해라:** 일부 문제들은 레이더망 아래로 숨어버려 기존의 툴들을 사용하여 찾기 매우 어렵습니다. 이것들은 실제로 버그는 아니지만 심각한 영향을 줄 수 있는 생각지 못 한 어플리케이션 동작들입니다. 예를 들어, 일부 코드 영역은 절대 또는 거의 호출되지 않습니다. - ‘PricingCalculator’라는 상품 가격을 설정하는 클래스가 있다고 생각해 보세요. DB에 100000개의 상품이 있고 판매도 많지만 이 클래스는 실제로 절대 호출되지 않는 것으로 밝혀졌습니다... 코드 커버리지 리포트를 통해 어플리케이션이 당신이 원하는 대로 동작하는지 확인할 수 있습니다. 그 외에도 리포트는 어떤 코드들이 테스트되지 않았는지를 강조해서 보여줄 수도 있습니다. - 코드의 80%가 테스트 되었다는 알림이 중요한 부분이 커버되었는지에 대한 여부를 나타내진 않습니다. 리포트를 만드는 것은 쉽습니다. - 운영 또는 테스트를 할 때 커버리지 트래킹을 하면서 어플리케이션을 실행하세요. 그러고 나서 각 코드 영역이 얼마나 자주 호출됐는지를 나타내는 형형색색의 리포트를 보세요. 잠깐 시간을 내서 이 데이터들을 보면 몇 가지 문제점들을 발견하게 될 수도 있습니다.
    -❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from - +❌ **그렇지 않으면:** 어떤 코드가 테스트되지 않았는지 알 수 없으면 문제의 원인도 알 수 없습니다.
    -
    Code Examples +
    코드 예제
    -### :thumbsdown: Anti-Pattern Example: 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) +### :thumbsdown: 올바르지 않은 예: 이 커버리지 리포트에는 어떤 문제가 있나요? 현실 세계 시나리오로 QA에서 어플리케이션 사용을 추적했고 흥미로운 로그인 패턴을 찾았습니다. (힌트: 로그인 실패 횟수가 비례하지 않습니다. 분명히 무언가 잘못되었습니다.) 마침내 일부 프론트엔드 버그가 백엔드 로그인 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) From 1a6af40b199a4484a9bc9b65fcbc6b3d5db39fac Mon Sep 17 00:00:00 2001 From: NoriSte Date: Tue, 1 Oct 2019 09:59:16 +0200 Subject: [PATCH 090/502] Optimize the image --- assets/headspace.png | Bin 426823 -> 333277 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/headspace.png b/assets/headspace.png index 324d820417b82f9ce22b5ed31b7fb1f303e0c9a4..83ed85d7e1d523a8188e9c3366ff500c4dda77ec 100644 GIT binary patch literal 333277 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

    !{C8G3;}j(Qzrp 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 (4NT&as9E*C=>rqs-3Y<- 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

    FWl>GssY64A^a4A(-+Z(x#L zHaxXK;{!3`ZWFOj_OuGqthgt1BJYxayNq2jePw_zki0?^Nj+K+%GND}pSM8zoqTg& zveC9VEL6s+mz7oc#mt!>FXKI5=%U&OjJM2 z<18|Rd00jf#%i6#XV4-@7v_k4kwlXGc#~&1+K4Yl@OlGLn(l;Mtk*$}7fn zc$us&$Ju9h&&)JA2`FP8`+}!o7*;Etuc7xy1m0TWPhvW0D*BP4>aBrxWxhVN9Q2ho zk|pwNTcJuq*Dp_oD7i8q%T*Rz9V$OMIP~2Au$&-T?_erO{E+r1UgMZ?yVQB(F0T3( z$J=!xj8n2#09jnMGA$ALO&O3(>r%THU}6edKTR| zJ7|M?uQ$?*$T#P)kQ?fMVtB~npH=dM-OnO!)s^bxhxFJH6>`AZ{ zTuvga=zP2LwJlwkcO{`lLVfa6SEQR=V(dJTeoDB{zE)15O0HbXA;-wzCj5nYgmEcot4 z@tm1pyO`1d;2ja&DKg z2GK?b24am%#)r{$;v!b5S^0n=VO{l`jAq(*vZ3RhU(gT$snY|RLGPN!kO z(0b_c$$uG}nPJ-us=>}6iEP*r_0v^RQut8;z|006u3xp2f|W8NVoxsm2s8=mIX-N9 z1@J4vd`e(jIifSe#jhc1(Ou2%E*pbNQSd`C6Ps0s+x@H~=OiWH1CeOs7Rd<5nDa1mAI&(Ms=tk@ zvHl?uifw73Nm;QU_v{H9;LA7U3HD#k#BPKJ8%vwMWs^9vd6Bv76ht1kZ)iz(9{8?6 zQ1D)pTWU78EaAV{mL1;;Tn5mzyv!U0#g>w4h;mPf|(Rtl7p4)>lTYL&^O$nZV5N>5oqlnf# zB(rE?oqBn|&M@T{t?vXf2R+v;Mc!<00xPH%|C3(e^QIJ>WGSp1MH4bP%xUr|sOjfy zSTCPKpM`69?r*m0uS(N3R4#jBl~ARJ!9F5SsZsYniJvA6%(+fxKl*ws;pxCZsxYn}X($u7&J&p(E;HoMRud6ZDJNE=maQ`-6@)G_;Uy4~+1 zwq`jToO%*K!Z(F2-k3Wfdt!w^oQJvbg=*-2CC%px#npe5`1?3LBLts*v>Zy;(-C^t zIOssOXQdmPS@B~%9Vz@X>o7MJA=TnpDkT&PSN~2XIIUN_5-HX&fAo4kf9d71xNl!m zL^)3NMwGq=Fs<>=qmicr!Aj+Gy>HO7beOaSAWr4bIW3In3IGVt%W%2xgUP*vHjQUl ztdfw{N+KJUki{A)c!w?IbazOwSM1dVT*=WUc0%q zPM=3Qk3vQsH5Soin{-^)A^#R^c`({2YZ&E zZI+Eocv9{1LkGKYDHp{SPNImJjr>Jse>qeGu;`yl^poE=P`MNG$Anln-9coH9x-*j zvdldED3}(blcv9|8+Q6{wDJ7V^Ug7DAwZxqe&Ai{OKrF^%=(LilXTiEk2bTWmf~Fd z2T;IuFQi2^4ErAQOc`)PwQyZ_thnjMa{$bKrAq<0a&9n1LQKAc#YX0>gAsngZ9N~` zQh=fG{$ao-FIgoUeIPt)(J(kA^C$yS(>t>O4A~q zmB;K{XL!4YmwSJQhFeF(`sMnDx=_e}SU6eKQUtuz^Zxg>>>eqly8I7WeuI?>&z|h8V>avn^RPp&-c!ealyJ%u*Gzbu2b54wo{#xz2#UIm=#@U=$9)SxS~aQkFebBlppd`AIFcZ2;;5AgG0+K+jRx3cnes-^k)Qa zQ>VDBaf#`TZM1GI_2&PY-%ED5T0Eeb%om1J>R~dmVjh8zSdcDzZM2D!(rVPHJaDvefzUg zRNbZ~-JLj#W@ElZWjLDh*jsP)3R^{X)v{p&cM{e?;wSgHllt_4FlPC@LZY~I)y`kg zjEBo1qYrnlDsUcu{JWO8bR1@^f{aZ@(Fji5ZMsO{AMC2 z$N!O^?lCvB{D;pMFV|+~6^%vdNmill+g-e#R<=(xb_IXPo_wKt!^P=Ur$AXHpLPzP z+gsNp%XRwVR4x4@tu{c@)h3st|&?dA=%_*&?~K&v&w!*9q!LS0&2n$RCq? z%T2hEPJ6!DkfzBuqFA-fuJxmnwk0^iShvhttoD$Qd=JN7D%_WNwY~UOvrF!WoH&kh zbBi8bXJjPD!BSt#;FSZM(@;ne1}#`pvI>gNjNwY1lDMbTtocLCc;HHzeo$|@_@MA@ zR*agI>*em|&a3@Vi)f4VPnN*Tc{b`v7-5tysE@d*@qj-^sk)7N||_bHCCc;aEP7D)6mYHE>a!Qa~kR(HYX?0j6Eo7u8P>H4<)gemH3mT^&(h*eT$RL{>)GaXb@7e{MBw7!kI5?Iw=> zVm{O#W?pQRWS4d{qs-rDC?cQ|+mh_IGRA%k*LsZuD09dy!t#nPTp^^@t~Gxh1-oa4?267QpV?aqxp z(=O1dj{o1x=cB%cW9YR80@8-Dfp#{{HWg$|ZB7sby!8rhcq9F?-?D%-Yt&)~uEyPf zJ5r!U@Uceh=7idPc15#2|OZlzD zYOfi0T6ehF4)nXpMSTv-dTQI^P%4ab8vo2N>*tcET5g$8VX3wHXS0jSnW{3Aq0O^3 z@u$D^A6oe!1KE*^S@%Pp38P&_f@I|CmGFA<3g1=?CmW@+zgRi|XXoP|kAv zU|>Q^v<-jrcfXD1Pae)rUB{@mb?vQ3R{dMfR>dE01*>TWjx=~5RKO*OOVry0;8X8# z!`!mCyL&!0NT_H%g51akfVz)CUYyjb>xb9QD&*Jsz|4qAJIvh$BkQ-5kQpjcn$vj9 zsE|kzZRf%u0NjDlnJ4*K6~bv@l~=kKodZ(*y{DVd6pO!+d?$%V|6yIQ54L$hu z+9aT%DvX8_|9P+*q(%~1{LIi3b}tmnOz!@w%W?ofM=TufzAS%lPf}3&`6cKx?o>_C zcmQ%10*a^iZmPHq+o^$m%`hyU3-t(T0@)ucI5d5M1xMiT816MR z1iY;3$lV+}uYUI|97 z3J*$=YvtP^Z8LF>{%0Qp|9{X-G(+;|(BQKd7PLdZ)>$MOlFVH30G4)&Kt@bvFOVVM zN!kW#^(_afd@DE)dST84bSJpI&z;7jgf`OybSW${9tHNxL^MlN!i;tG~_I( zJs^)x`k79tW6E}E_XH-R5a}0@S7^&fzy`s-Ar1rOGrV;eN&Jkb1$`Of23963EThJokymg?hDwI zR`$^m{0XzQKm-XbxWHtV?|&p2%~HSU?en-WTd?46_c{9F6s(&{5{J!Tkwj}+R z%^zTcweYsVV+JJ4p)mTd&ZqK2e)?pic?7_BipnAIYHAH5fl{O?{XetUv|;rd5u zLPk=Yx!H2-^95$<`aG~2hDDN;Wzsoi^D&j1ng|M*fk&II0UvQe0QwH!1GgU!;te=m?^ewr(gqSGK0kVy!9`HN0m{F z<40@lPa-pp@kD|QbF+()Dt~_H3`Zt@oD+LvApcrHaNDgsHLzt8H@D!YCD?TF!T*q9 zot)%VepvxI^R0Qv0Y3$v|MQyi=JB+gz4@~iU{7*5?D_{If^el*2^{tG95MuMZ!2fpu#kwKk5g9B zgR+8-T{DZpQ&_1fu|2`aboDw+$koYp$WF9rFI$U9*LaT;nTbDmyXYR&T}*3~iJ7#l zjYa2c9&qc~^=Qwd(60tQw+|hSZl@eubgs<~;CFCXSCeh??Ht1I*hSs|p9XGG3Gbs* z={nF7xptS>{vQqNgPHEm5fPfv2bY^cMj~kTYVW^QpPP~L zRE9j(;37qK!Z8d+bT6DrYA)SO96Q3C6Z^%_r)`#QG|Jm3O?lGzp50cITI}@SW?QAY z!yM9CtaENrcAoZ&EX<5*k8hg&=xgu?tqIfvNZCNlD^hB=0Oq^s^Y7s*X8EjiRmogL z(B)Z|lYZIhCl4xjhzb%xz+7zLc^DKI$Q^PFA)M&ctBMbwk!@ZB1sq^SVnMa3>p2NN z&L^Adr@;PaSqD&p=oPYFEB;4T`xaRs>rc8WTtVpwP-RK&)7qU1Y|D1d)rWMzhMr{9 z8Qq&^wmhBr&%uh!g=g2*Q)~-I4u?_UK9&V)r!x<;V(mZcH^oZj91tN7)pZ2ef=Hhq zaI>GO1vC4p9lXqA&j?XBN(eTROCVn#&a7$MIT*3nb^srVGxvTLwyccuPK3}OTr*3M zPQ&vFnOb`&TXAkQim0f2+9^s&$iy1ob}|cK#=}r`Rn&VXrltwhb}UuDU#o;b8wyu*}znQCf26 zNmf7SQ~ESBz1+Fuu^i?M+-@-{moo{(t|3w4MrWEbX|^UGAgE{p)+3~Ok2n=nFlRaP z>dO||xnAO>ZDme-uumi|rdMJYdrOO2wQonD-gk-HQv0!JOvBBFASKl7u=wa*Y8M-P zR5H7~(EBy*tw}d*@K_cr`W)xg73xFghM{(zB_HxU+6E&U&P-?JJZ)JuP>j!S-_SpW zJ6WD{j@UM|Y^;RJF&j88ys3Sp!!G)CMK5vCPQvIlzT9x zetW{-0Mta4hOA2U>IJ$eMYH4_J{#R3a-C6RKp9p3#huz}y@cd_V6OB_R_RskatZqA zncXWcs8~l!Nno)?#^}$@Ua=t2*Ko5wSP8X-W1Rs-O4smP$3d-0ziPGAWG8;vBIDw4 z*z=YSKPj6Ip7XAu4#F0h+eRF8GN$ud9CkSd8~WXxR5;H5o9eWn_@FptBas~>0j>Qv zK#ghyq5o)YS4wmGK)k0-Z?v(xkMD4jNqPdoi-xgSc9bh&u%B9xlv>TMz1-9r6o3yEKVkY6WHo3|RD|nAnhS=B?Tbk+6(;z|WxpIl< z-l4Qj+aaJ<)B+VT0H?|PtJb5(v&#`?ZY^cN&a8KkWkPfg>>9oBnT78f^~mkH;}-Xt zUBZ=w{MJYHj<8`t>Sk_*NyShcd)d(|(LhhJuE%3Ni}g1l&D&21_a*5wzIRiBN(*Mk ztnbOWz2;?iUOafU#izb}aQ26oNnq{$)Hr)i8bd2@t-_kgV(x+{t~WcAhi9J{)@(8p z-HYO9#eJme%&pEC=nc8Ll6ymBGC_X(&K969fBc^4Vy2h&!WsP$lwbY~JF5`KX3YKe zkMBUKL#I}+!*pixERLbp>{yOGZXriUdHqhpBhN0;t|NCBoX_9LvUfD+&cv_evvX^A zxU}hg5GwVtJk3mUAS<|?`SyXO1uSEqd7mt8bqtBYo@!IdAu65Li7+OjrJl|IsTAZIOK zEZJkuTVy?}=wzObEhvy+ZQQ;J(=lK*9Phf~5RM+%Tp#<9 zVl2QDg*OM)Tj^)Ir=?!>G0u8zMDdkao<=4)O{Fw&sZ$Y=HukkYWbQUUW99kVt+cAk zDx;d^1YuapbglpJ%Cv^p^@%az#qX-BdOSAUB$`yDyX-e$7PNZ^(8_?VzC}1>!ji=* zhBJR{7sE$y5EPFC)hClti%c_-c8sG@N3vKCvU9mlNG%Sh#=F!Bil^7?c(>(E%<^-2 z3U!54_heR>Td>R6cXc~) z^lugizu!}?wQrv^C@-bS2vzrEVHha5TjO-bFTQ-SXEDkyW?y_c(5DPX`9p8NgIQI)Sg)FG;Fe)XDvqm&ru^}w#hu^SkkkB0gg|(xfRwb##8*A z!|2-1Awy2zU&@;W#ft&s6vPTtl~A>cy60R&>^zl{mhv~>9u_C zJ-_gKXxq~wCq>o9>&eO`_TFv7=^!r;{5M+URgNF-86OHKgmys7CcGs*A^yr>>;XGp zmNbc9w-BMX558~szK7*@*1 zjdUxzusGnti46|=z5hgjxT6Fb?Zz)SW3@3o{;PRbgOIyOLT^iuyAsMp6Xh~XRA(C< z7JW>}Gs&`3#RmHw(o8~R`lgfOCAlG$U-tkfQv~LM9bm&o8LM>KDeC~IyuI|V@s?s5 zbZOM!T1I>6r`fufW5Xs-?YP_0!XLRcg>4E2cX2I?{Rp@DN0Z#iVg|QMU1r(c}f915H!#!BC zCX@aqNsCF7JVs>gJLY6*-}ie%*cCRMP;9Psa%vlk4e1ZZQ zN1kk7q*{$wF(yc{jgFq~Gzs%?(?JPBCCcDf>w7QIp76+4e+Vn-|D+%KS$(JeO}B7{ zD%HD4V{f(FM;=J{Hioq*7sih|D;SBCX-ufJqR}1c=<*v=UOTyj`5%jXy5c&qTk}Wy z&j+1m*B$s!Sdg?SFC6ia&(XMKmNYh|{42_q9hOv4cZUU3R~hbZxE8STqKd;1efcGm z?lmI^H}nA?nr6DiRg_kLq<0Sg$QiMHZa#cFotP=jQ!+ssb+D7M9PyGhe2f`x_!UVXpsSaz?}Gr#(oA-pqS+LyapO`;O{haW5@$eF2BZx|T#yFxi9HtNoOx<&R& zO3r!i=6NZ7mt7?{^lh$v(0$XP?mk2MYQ>W1U$*dko4N>0H0Bxl=3O2aa9)R z#`3aR55ww5m%vw%^jheSkxSx{C&^1u|1C-OH2HlSfb;`o#5WT->p42Z|*9=u6TAO@MVyAlvDHw4!1Jp zh3z}C(rZCf*68cYQ2Z&vT3WL56nwg`{4YvY$k=l!MX0iMTZVbbu^r7DOAWSFM0Yq8 zpSdL}Z+oAKb5yJ_YBVP`ncHn-{bKjPjg|(bdKU9_`Qmh3{jOA1FC<%jC3<|AnUf0D;e>N2pSAF0#;--qu zE3J8>*La`QryPM`a@6LZ_JE~qE8O?;4+zV6xvSm%kB$Z5a9`!xM$d&Mny?X2*VwyU zEtoi`#lEW~<~N@=5Q){%zVpLrcER~o!fdv50ZTYJ;}nNayOi{RG(XZ0%o=QmqF0h& zo7zKj#X)rE<7uv!IhP0wEiQ(nQNivX&u^1Lu>|F)Nfz@c{6}%p*VXTU&g*#i4oW!F z9#T}?jdBv%&VMEg;8V=#U9fGm;y*gH0Vz1w2P7Joi~yzWKTXV8)J(;hIcZV@GXQBd z$NJjx$4Gk86J{FsYuqAi`{742K zOAeBiOk2iFg76~EMwnkQm?$sAC~kVcZSrh@-&qc_WX#7RBcjkcdy3} zs99i|^grHy4Vz}&Zv&wh`v;Dijq@y+-DdaT+ z2P|4|J*32n`~-=Jr1t4_wr2M)gAq(E(>szS@t)ruIp6A!X_=^ZzrT~S;=#sfYi-}J z#l>|#ZMy>0BAhfghbpVv6U2_*n?;*_5N&P-De7*;326F{bv#=T2T*$VaZch0Gv)9N zPQ9j1@UO|h6a_mRw(1MqQq^4{7&(!cX8+(dKccUmqi$lM`7>usQSb-m~na_j2Zins>a}kGFQVQx!c^0(cxs{ERcm?1?_$>h-KMk{EnZn2 z_rAakpAYJX z7N#=I)ANpng%yr0?F`$fX~Q*0t*cm{#?^_q(LTI6(@c@|T~ z>--g$W9J-Tv{pkNJOQqjRg7hQO&Z&r$8VMRsj(|Iq;dCiZl4?Aao_G9K4!_+4^0{pw{ zY`6GIj6@U^)*7(I$<@lKXrrdYJ~r@C%9r1J>&7>Ml}*5M%T*;FVQfidY8k%Vr?hI6 z#`bm9)jq7485gF#$lAJmPkyqiz9>S8aU#aT;AvMe z(;k;B?;IIl79vR>eN^ga5N3PZ)!N$eW6g^Z-@n%)**U4G1UZ_XnW?s~qBA0@=D>w{NF%%T zmJHxjLnPZ7jF*4(zy{j~d#hi(=7;Z42Y;D5^7L?H1;lBDX>Ow4R8-d1%go)(_e<{N zwX`XNd4bkj<7`Eq5rzZG$K-(0hw_v^rwfCh4Ujzn4+?>=4A?y{ptM8wOJXNTNbcR< z@;#CrH4SDQb*%@VAz5Hsz2vj^dj)KqLlUqecED$oZOci2Ph3{m?}dLIGj*WfOLlx* zmscx&Xl!fkj{%PG3+6?ftsQbw3*LBVN&VhANER6HfGT2KEj&`r_X{zGTCgT5mBdww z%OhX{KCz|gnVICsT^D}5WyPlDNcLr*uNcGq?SCYA zp_`I>7u&~o!5U>KAG(0CLa?=Uzsi{ZCx1xT>Rpe|B6*xQS3?9A!L4&V@bZD;1MHQG zAId2cr6Nizt!+TRVXKs#*R`wX&2f%>Vp^I4%C9BM_iV#Xoed>clEtqFj=ldr#9|OH z^^pHZW5H0JS6BJx0D#{UPbBY)H&GH`j1dX-j7_fDs*W6CEydUq5pl)DK zJQp0md+-R`El2@-M^f0Rj*{X4!k54-2s4*wlsKGoomU3NlI#_z8{mo&W;lB#d6gf$ zc}9%4Cv7iJ*BCsMJ|>?60Aw7HnR$ZWX^qwB7Ec7G#jI7IlUE1&;Ni8h0YbRYd81(f zX%$ckUR*AxYw&JBPG)8R&q#Fb(XsKx@taNjoHz#OHIP~yu*`G|C-B@D*$|%#(%AeU zREs3&`89%!nOt(Lmc2On@KO^ysyYm?E1#`Ibjc*M1_&9^CELAuee-cRyi#}!sBSgF z-nq>Rz9jTLBKURQ)#@&Pg^rZ(PUGj8^o4!(p}dtfR-gm1uxCZX7=;fHa!uA$%i zUFd{|+~P?Jg(A)cV0OSrWQ`4`3UPU{Gp1uNsFKD6;4O!r^!r1x2Bs2OsoOGjX~>nIaoddK_-L?AAtxV%l^o@Sl0V11O3B6ccd|g|xly1tH$l`8<%Q#yTJFzEgONU$Li;juYpd@`(s=g78zYnVqr71^baWKjB#=hUcXB&Y8(X z1QA`uXGdm^#pbQI4B1gUt_smt_9xGEt`=Q_aJ9O57D^U?xwwZtwNEO~hg!&h#e+u0bEw$F~H^VxZ2d0X6sTi2%Ye9n4 z_;gmaRshis-QDF+=7x44U1x=xp@|D{Gwnmb98YHkviNQb(~y?3cKXod3|_(zcr9~@ z{d9IXp9fBUG34ZXX_|s-DbAohCXb9`iCAnOKlo^Hat#ElM(E2}a1T@+m{B#6#}q{D zzL0V7-YWkZ65rl~@|h{T*NXHta}D=O_Tt=wm~;_RR|Hk-s0&r@gc8Vk$^f!DcffU@TD)P3DJNpavAP#(VUD{Gb5H>hRmNS0pdwSyrrgd$I;1E9a-s zXbaT5_)>|-DpdnB$sVF1>tA~jB+7v@Hap++YrK)u2iFeI4&beLMDKE1IDQvg4^Rrs zO4>jiCxC0)P4F&q&f#tW*#W;jk_COm3eFH3g8gLA0WpXph@`Rpe*k5dd=?t7niS-d zY!C1bzmi27ZLlh)0z`1m-_krJ(C}P5ODYm32(3ky%mRwd0}3;s{y^W&X7Da-=c_iu z&sekE=|Q&x@NL7@d^zE0hCY#jmMh-FD@-0j4XO-gCj%45V=$Wu=ZfeL(JfH+8EQCs zFM}CQJRIi;fUjVvHN(#YvJTFwk_V0vE2mL1Kswp9kgq-*o5d~6IV>Ffq34|$y| zku5=>;po7=H)h(YZFmRG&}2_hFn$XNpP z>1p?xpa0-a?9B(B3g^Po{eo)Oe(cUf+W*!_pFBB#f5pM z;h#zeK|J6m4snsxc)TREz&8erO@T%SZC(EMWF@rT0P2=cA$DgA5Wf^KMMN5_yo*u1 zK4?AyoIb0~F@O}@-*&Y}6YfH|P#W-Pa0R6dJbVHjy99JB(_oqoH=LhsNAl)VtyrBs z(M>bW#=t`+e&;GUUzd7P$9xEK9MIu5tZH2@!6IWJbQXy)qd-Jk*}viAT9NORYC7*K zGtJlS8Eu0jzN?VZSz=A1v$RFz7$)yvq0Wcare9&?y3Pt<9iAY6D+d`9jEJlsn1vB5 zDn{JFMpg2$AJX`z2etwl?dDyhI>dxnoD&>&5kqUFx9g5H@Q@tkdFS;0sJaC4FM?(e zqw%UKcPLyo?g<1?6%xS3G+y(_03In-QbKwpl*STwlSkLhtOY*q9Iskt60_(Ce!4Jg%zs&U4m1r=wu!Qxh0kt|momFCy6C65`1wvx zyP38PtQRJ7hc7faLF`F|{~15{;g&6q*f?|WAYs1|#XD<6HgIn~&mO%F_a{Hkvc>fM zDwhLfgB4zigg8hmk|wS{X2!XA>B6!T4xuH*T#1NnK{f% zGQ1KegIdY>`h2svx$<=mG#V4D)X_rl`MLr}SDEOYA$Tnp1bkh2a`nqoP$wA`QGzTS z1|j3Us6mN0;TSiQiM}H5U-tX{K}X2!A}q_a!cq``crpO&AmhlY-BAAss9WZ+wx<hxFleKA<@?8xgt4=`-X;g3#;STlGnevC*Q9cxC!_c$IGiDMKw=B{rt!njpOl0T+Fohakq7OziRQXNbA4btL%WQAO_5%8FMdz7d)Pqa zw=7&iU0ws}GAcklVxHTu1Ev6h2>xC(Ui8;;%K7vfeYLWtSC4(2KoH{e$h?6y0dI09 zi=k|jA?2hMx`Bit2@c5>@58b@7%Q*HYa~vgGN^1X&i|W5WVWNWza+ZyCQm}0$h|>} znG__Y|NE5fT13_$ihs5Mj{Zx22VwjL3g*H9BzORGmL%qco;(K;`)8x@;Wp~72!6mGPX-o6tud@EJo7E2rNBe7+D2x z>?Jj)M1ZvmF`YFcxCk&ud5&>Ku=|p#G%ViTh^rLj%RoqwSPz7yrEmbXFwfLDOBR3?#0LeXfaR zPY>CUuz&hDDZ-n#u|g@Hvfd%XE7^5XhdYE7{JR{lq&|f{($ynFzi(B$4P3Vq z#LykOFhh&q#ylUuJg3K~LR~v%v&w$?(k6%xm)2mjos_&ULsNjIfjp3p1BJ5qz~n+% zKlIyHwf$gJ4u@cvV&Z(tD4qtj&7*6^a00E5fQK{1{NJydOLV00C|$FoD9nYf-FKwa z1aNc)31o$TW@`qQBJxo?@NsHV=Sh->SlYlwup1%Q!Kk3hJUYh)x9TcWiGa=-tpYHG ztUb$p}q)Cnh5LReJUwaw6h zk`6{3g-5m0aUqxt`f?YMB`lW+W+_u24nQ2OM#?|oF^(MGOcbcErP+EP76qh6H}Q#! z4u5hakNEttRRvEQ*w|E2h)W!dk9}r_GUBt2?lPweXl_cZ4N>VBPbUb} Date: Fri, 4 Oct 2019 11:17:22 +0200 Subject: [PATCH 091/502] fix bad link in 4.1 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c03fdfa2..0005d756 100644 --- a/readme.md +++ b/readme.md @@ -1616,7 +1616,7 @@ Implementation tips: You may want to configure your continuous integration (CI) ![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Cypress to illustrate the idea") -![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest) +![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest)")

    AeJm+-PoGfnKQ8voq(pNp(WEI&| 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 LQ4`Vu%(edqegksr literal 426823 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

    From 3bd399f7b83c04577b0b9069261695ec5fe44440 Mon Sep 17 00:00:00 2001 From: Kyle Martin Date: Sun, 6 Oct 2019 10:31:06 +1300 Subject: [PATCH 092/502] init allcontributors bot --- .all-contributorsrc | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .all-contributorsrc diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 00000000..12b02e18 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,8 @@ +{ + "files": ["readme.md"], + "imageSize": 100, + "contributorsPerLine": 7, + "badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square)](#contributors)", + "skipCi": "true", + "contributors": [] +} \ No newline at end of file From 75e2f737777bc3ab896088ef3a934aab9a1afbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=95=E5=A4=AB?= Date: Wed, 9 Oct 2019 10:32:19 +0800 Subject: [PATCH 093/502] feat: finish translation work -- Chinese Simplified --- .vscode/settings.json | 17 + readme-zh-CN.md | 2054 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2071 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 readme-zh-CN.md diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..ba08ab99 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "workbench.colorCustomizations": { + "activityBarBadge.background": "#2979FF", + "list.activeSelectionForeground": "#2979FF", + "list.inactiveSelectionForeground": "#2979FF", + "list.highlightForeground": "#2979FF", + "scrollbarSlider.activeBackground": "#2979FF50", + "editorSuggestWidget.highlightForeground": "#2979FF", + "textLink.foreground": "#2979FF", + "progressBar.background": "#2979FF", + "pickerGroup.foreground": "#2979FF", + "tab.activeBorder": "#2979FF", + "activityBar.background": "#09332D", + "titleBar.activeBackground": "#0C483F", + "titleBar.activeForeground": "#F2FDFB" + } +} \ No newline at end of file diff --git a/readme-zh-CN.md b/readme-zh-CN.md new file mode 100644 index 00000000..e28121e4 --- /dev/null +++ b/readme-zh-CN.md @@ -0,0 +1,2054 @@ + + +
    + +# 👇 为什么本指南可以助你将测试能力提升到下一层级 + +
    + +## 📗 45+ 最佳实践:非常全面彻底 +这篇文章从 A 到 Z 给出了 JavaScript & Node.js 的稳定性指南。它为你整理总结了市面上大量的最佳博客文章、书籍以及工具。 + + +## 🚢 进阶:在基础上前进 10000 公里 +从基础领域跨上前往进阶话题的旅程,包括:在生产环境测试、编译测试、基于属性的测试以及很多策略 & 专业工具。如果你仔细阅读了本指南中的每个字,则你的测试能力将有可能大大超过平均水平。 + + +## 🌐 全栈:前端、后端、CI、任何岗位 +先了解通用的测试实践为其他应用层的打下基础。然后,在你自己的领域深入探索:前端/UI、后端、CI 甚至是他们所有的层面。 + +
    + +### 作者 Yoni Goldberg +- 一位 JavaScript & Node.js 顾问 +- 👨‍🏫 [我的测试网站](https://www.testjavascript.com/) - 在欧洲 & 美国了解 [我的测试网站](https://www.testjavascript.com/) +- [在 Twitter 关注我](https://twitter.com/goldbergyoni/) +- 来 [LA](https://js.la/), [Verona](https://2019.nodejsday.it/), [Kharkiv](https://kharkivjs.org/), [free webinar](https://zoom.us/webinar/register/1015657064375/WN_Lzvnuv4oQJOYey2jXNqX6A)听我的演讲。后续工作待定。 +- [我的 JavaScript 质量新闻](https://testjavascript.com/newsletter/) - 战略层面的视野和信息 + + +

    + +## `内容列表` + +#### [`第 0 章:黄金法则`](#section-0️⃣-the-golden-rule) + +一条启发所有人的建议(特殊的 1 条) + +#### [`第一章:测试剖析`](#section-1-the-test-anatomy-1) + +基础 - 搭建干净的测试(12 条) + +#### [`第二章:后端`](#section-2️⃣-backend-testing) + +高效地编写后端和微服务的测试(8 条) + +#### [`第三章:前端`](#section-3️⃣-frontend-testing) + +为 UI(包括组件和 E2E 测试)编写测试(11 条) + +#### [`第四章:度量测试效果`](#section-4️⃣-measuring-test-effectiveness) + +度量测试质量(4 条) + +#### [`第五章:持续集成(CI)`](#section-5️⃣-ci-and-other-quality-measures) + +JS 领域的 CI 指南(9 条) + +

    + + +# 第 0 章:黄金法则 + +
    + +## ⚪️ 0. 黄金法则:设计瘦测试 + +:white_check_mark: **建议:** 测试代码与生产代码不同,要使它变得极其简单、短小、没有抽象、扁平化、使人愉悦、瘦。一段测试代码需要做到让人一眼就能看出其目的。 + +我们的思维空间被主体生产代码充满,因此无法腾出额外的“大脑空间”存放复杂的东西。如果向可怜的大脑中塞进其他复杂代码,将会使得整个部分变慢,而这个部分正是用来解决我们需要测试的问题的。这也是大部分团队放弃测试的原因。 + +另一方面,测试是一个友好的助手,一个你乐于与之合作、投资小汇报大的助手。科学证明我们有两套大脑系统:系统 1 用于无需努力的活动如在一个空旷的路上开车;系统 2 用于复杂和繁琐的工作如算一道数学表达式。将你的测试为系统 1 设计,当你看一段测试代码时,需要像改 HTML 文档一样简单而不是像计算 2 × (17 × 24)。 + +为了达到这个目的,我们可以通过选择性价比高、投入产出比(ROI)高的技术、工具以及测试对象。仅测试需要的内容,努力保持其灵活性,某些时候甚至值得去舍弃一些测试来换取灵活性和简洁性。 + +![alt text](/assets/headspace.png "We have no head room for additional complexity") + +下面的大部分建议衍生自这一原则。 + +### 准备好开始了吗? + + +

    + +# 第一章: 测试剖析 + +
    + +## ⚪ ️ 1.1 每个测试用例的名称必须包含三个部分 + +:white_check_mark: **建议:** 一个测试报告需要让不熟悉代码的人(测试、运维)明确知道新的变更是符合需求。因此测试名称需要从**需求层面**描述,并且包含三个部分: + +(1) 被测的是什么?(比如 ProductsService.addNewProduct 方法) + +(2) 在什么条件和场景下?(比如没有 向该方法传入 price 参数) + +(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") + +
    + +

    + +## ⚪ ️ 1.2 使用 AAA 模式构造测试内容 + +:white_check_mark: **建议:** 将你的测试内容划分为三个部分:布置,执行,断言 —— Arrange, Act & Assert (AAA)。这样读者就无需动用脑细胞理解你的测试内容了: + +1st A - 准备(Arrange):一些用于提供上下文的代码。可能包含:构造数据、添加 DB 记录、mocking/stubbing 对象,以及其他的准备代码; + +2nd A - 执行(Act):执行测试单元。通常一行代码。 + +3rd 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 Jest") + +```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}); + + const 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 坚持黑盒测试:只测 public 方法 + +: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 + calculateVAT(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; + } +} + + +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 Mocha & Chai") +```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 关注于测试需求本身,而作为副作用不得不接触内部 + +```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) +}); +``` + +
    + + + +

    + +## ⚪ ️1.6 不要“foo”,使用真实数据 + +:white_check_mark: **建议:** 生产环境中的 bug 通常是在一些特殊或者意外的输入下出现的——所以测试的输入数据越真实,越容易在早期抓住问题。使用现有的一些库(比如 [Faker](https://www.npmjs.com/package/faker))去造“假”真数据来模拟生产环境数据的多样性和形式。比如,这些库可以生成真实的电话号码、用户名、信用卡、公司名等等。你还可以创建一些测试(在单元测试之上,而不是替代)生产随机 fakers 数据来扩展你的测试单元,甚至从生产环境中导入真实的数据。想要进阶的话,请看下一条:基于属性的测试。 +
    + + +❌ **否则:** 你所有的用例都在 “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 allowd + + 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 基于属性的测试:测试输入的多种组合 + +:white_check_mark: **建议:** 通常我们只会选择部分的数据样例去测试,即使是使用了上一节讲到的工具去模拟真实数据,我们也只覆盖到了一部分输入的组合(`method(‘’, true, 1), method(“string” , false” , 0)`)。然而在生产环境中,一个拥有 5 个参数的 API,可能会遇到上千种排列组合,而其中的某一种可能会把你的进程搞挂([见 Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing))。如何自动生成这上千种组合并在它们出问题后 catch 到?基于属性的测试适用于这种需求:向你的测试单元传入所有可能的输入组合,以增加发现 bug 的可能。例如,给定一个方法 —— `addNewProduct(id, name, isDiscount)`,支持属性测试的库将使用一批`(number, string, boolean)`组合调用此方法,比如`(1,“iPhone”,false)`,`(2,“Galaxy”,true)`。您可以使用您最喜欢的测试运行器(Mocha、Jest等),
    通常,我们为每个测试选择一些输入样本。即使输入格式类似于现实世界的数据(见子弹“别foo”),我们只涉及几个输入组合(方法(“,真的,1),方法(“字符串”,假”,0)),然而,在生产中,一个API调用与成千上万的5个参数可以调用不同的排列,其中一个可能使我们的流程(见模糊测试)。如果您可以编写一个测试,自动发送1000个不同输入的排列组合,并捕获我们的代码未能返回正确响应的输入,那该怎么办?基于属性的测试就是这样一种技术:通过发送所有可能的输入组合到你的测试单元中,它增加了发现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) 它提供了更多的能力,似乎更易维护。 +
    + + +❌ **否则:** 你无意中选择的输入数据只覆盖了没问题的代码路径。不幸的是,它没有真正发现了 bug。 + + +
    + +
    代码示例 + +
    + +### :clap: 正例: 使用“mocha-testcheck”测试输入的组合 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with Jest") + +```javascript +require('mocha-testcheck').install(); +const {expect} = require('chai'); + +describe('Product service', () => { + describe('Adding new', () => { + //this will run 100 times with different random properties + check.it('Add new product with random yet valid properties, always successful', + gen.int, gen.string, (id, name) => { + expect(addNewProduct(id, name).status).to.equal('approved'); + }); + }) +}); + +``` + +
    + + + + +

    + +## ⚪ ️ 1.8 snapshot:如果需要,仅使用短的行内快照 + +:white_check_mark: **建议:** 如果你需要 [快照测试](https://jestjs.io/docs/en/snapshot-testing),仅使用端快照(比如 3-7 行),并且把它们作为测试的一部分([内联快照](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots))而不是存放到外部文件中。遵循这条指导原则将确保您的测试保持自解释并且不那么脆弱。 + +另一方面,“经典”快照教程和工具鼓励我们在一些外部介质上存储大文件(如组件的渲染结果,API 的 JSON 结果),并确保每次运行测试时将新结果与保存的版本进行比较。打个比方,这么做有可能隐式地将我们的测试与包含 3000 个数据值的 1000 行内容关联起来,而这些数据值是测试编写者从来没有读过和考虑过的。这么做将会使得你的用例有 1000 个失败的理由 —— 常常改一行代码就会导致快照失效。这个频率有多高?对于每个空格,注释或少量的 CSS/HTML 更改。不仅如此,失败结果不会给出关于失败的任何提示,因为它只是检查 1000 行内容有没有改动,而且测试编写人员不得不将这一大堆他无法自己验证的长文档作为期望的 true。所有这些都是测试目标不明确、测试目标过多的症状。 + +这将会使得我们的测试带上一大堆我们以后可能不会再看的数据。这样做有什么问题?你的测试将有无数种理由失败,因为你放入了太多自己不需要关心的结果数据进去,而你又无法抽出足够的精力从结果的 diff 中判断当前表现是否符合期望。 + +仅在很少的场景下,长外部快照是可以接受的——当测试断言 schema 而不是数据时(提取值并关注其中的字段),或者当快照的内容很少被更改时。 + +
    + +❌ **否则:** 一个 UI 测试挂掉了。代码看起来 ok,屏幕上正确渲染了每个像素,发生了什么?- 你的测试发现跟之前的快照相比,新的快照 markdown 中多了一个空格…… + +
    + +
    代码示例 + +
    + +### :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 < /DisplayPage>) +.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 +receivedPage tree = renderer +.create( Test JavaScript < /DisplayPage>) +.toJSON(); + +//Assert + +const menu = receivedPage.content.menu; +expect(menu).toMatchInlineSnapshot(` +
      +
    • Home
    • +
    • About
    • +
    • Contact
    • +
    +`); +}); +``` + +
    + + +

    + +## ⚪ ️1.9 不要写全局的 fixtures 和 seeds,而是放在每个测试中 + +:white_check_mark: **建议:** 参照黄金法则,每条测试需要在它自己的 DB 行中运行避免互相污染。现实中,这条规则经常被打破:为了性能提升而在执行测试前全局初始化数据库([也被称为‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture))。尽管性能很重要,但是它可以通过后面讲的「分组件测试」缓和。为了减轻复杂度,我们可以在每个测试中只初始化自己需要的数据。除非性能问题真的非常显著,那么可以做一定的妥协——仅在全局放不会改变的数据(比如 query)。 +
    + + +❌ **否则:** 一部分测试挂了,我们的团队花费大量宝贵时间后发现,是由于两个测试同时改变了同一个 seed 数据导致的。 + + +
    + +
    代码示例 + +
    + +### :thumbsdown: 反例: 用例之间不独立,而是依赖同一个全局钩子来生成全局 DB 数据 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with Jest") + +```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 :[ +}); + +``` +
    + +### :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 测试看起来似乎没问题。但结果会比较奇葩(会隐藏测试的意图和期望结果),并且把 tc 复杂化(比如下面的例子)。 + +一个更优雅的替代方法是使用 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 Jest") + +```javascript +it("When no product name, it throws error 400", async() => { +let errorWeExceptFor = null; +try { + const result = await addNewProduct({name:'nest'});} +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: 正例: 一个人类可读的期望,它很容易被理解,甚至可被 QA 或技术 PM 理解 + +```javascript +it.only("When no product name, it throws error 400", async() => { + expect(addNewProduct)).to.eventually.throw(AppError).with.property('code', "InvalidInput"); +}); + +``` + +
    + + + + +

    + +## ⚪ ️ 1.11 为你的测试用例打标签 + +:white_check_mark: **建议:** 不同的测试需要在不同的场景中执行:快速冒烟、IO 测试、开发者保存或者提交文件后的测试、当一个新的 PR 提交后需要全量执行的端到端测试 等等。你可以用一些 #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: **建议:** 本文主要讨论与 Node JS 相关的测试建议,或者至少可以用 Node JS 作为例子。然而,本小节整理了一些众所周知的与 Node 无关的技巧。 + +学习并实践 [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 时,在修复前新增一个测试在未来检测到它,让每一个测试在变绿之前至少失败一次,快速编写一个简单的代码模块以满足这个测试,然后逐渐将其重构至生产水平,避免任何依赖环境(路径、操作系统等)。 + +❌ **否则:** 你会错过数十年来智慧的结晶。 + +

    + + +# 第二章: 后端测试 + +## ⚪ ️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 这样的消息总线中,然后这些事件流入一些数据仓库并被分析 UI 查询。我们真的需要花费 50% 的测试预算去为这个几乎没有逻辑的集成中心化的应用写单测吗?随着应用类型(机器人、密码、Alexa-skills)的多样性增长,测试金字塔可能将不再是某些场景的最佳选择了。 + +是时候丰富你的测试组合并了解更多的测试类型了(下一节会给你一些小建议),这些类似于测试金字塔的思维模型与你所面临的现实问题更匹配('嘿,我们的API 挂了,试试消费者驱动的合同测试!'),让您的测试多样化,比如建立基于风险分析的检查模型 —— 评估可能出现问题的位置,并提供一些预防措施以减轻这些潜在风险。 + +需要注意的是:软件世界中的 TDD 模型面临两个极端的态度,一些人鼓吹到处使用它,另一些人则认为它是魔鬼。 每个说绝对的人都是错的 :] + +
    + + +❌ **否则:** 你将错过一些超高投入产出比的工具,比如 Fuzz、lint、mutation 这些工具只需 10 分钟配置就能贡献价值。 + + +
    + +
    代码示例 + +
    + +### :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’") + +☺️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 的内存版本)但是 stub 所有外部的东西比如调用其他微服务。这么做,我们测试我们部署的部分,由外而内地覆盖应用,节省大量时间。 + +
    + + +❌ **否则:** 你可能花了好几天写单测,却发现仅得到了 20% 的系统覆盖率。 + + +
    + +
    代码示例 + +
    + +### :clap: 正例: 使用 Supertest 测试 Express API (快速、覆盖很多层) +![](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)") + +
    + +

    + +## ⚪ ️2.3 保证新的 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 将会在 构建/CI 阶段被 catch 到,从而减少你大量的烦恼。 + +
    + + +❌ **否则:** 所有的变更将带来繁琐的手动测试,导致开发者惧怕发布。 + + +
    + +
    代码示例 + +
    + +### :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 单独测试你的中间件 + +:white_check_mark: **建议:** 许多人拒绝测试中间件,是因为它们仅占据系统的一小部分而且依赖真实的 Express server。这两个原因都不正确——中间件虽然小,但是影响全部或者大部分请求,而且可以被简单地作为纯函数测试(参数为 {req,res} JS 对象)。测试中间件函数,你仅需调用它,并且 spy ([比如使用 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 中间件上的一个 bug === 所有或者大部分请求的 bug + + +
    + +
    代码示例 + +
    + +### :clap:正例: 隔离地测试中间件,不发出网络调用或唤醒整个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/) (2,600+ stars) and [Code Climate](https://codeclimate.com/) (1,500+ stars) + +贡献:: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + + +❌ **否则:** 由于代码质量差,再新的库和 feature 也无法拯救你的 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 " CodeClimat, a commercial tool that can identify complex methods:") + +
    + + + + +

    + +## ⚪ ️ 2.6 你是否准备好迎接 Node 相关的噪声 + +:white_check_mark: **建议:** 怪异的是,大部分软件测试仅关注逻辑和数据,但是最糟糕(而且很难减轻)的往往是基础设施问题。例如,你测试过当你的进程存储过载、服务器/进程挂掉时的表现吗?或者你的监控系统会检测到 API 减慢 50% 了吗?为了测试及减轻类似问题,Netflix 设立了 [噪声工程](https://principlesofchaos.org/)。它的目的是为我们的系统在故障问题下的健壮性提供意识、框架及工具。比如,著名的工具之一 [噪声猴子](https://github.com/Netflix/chaosmonkey),随机地杀掉服务以保证我们的服务仍服务于用户,而不是仅依赖一个单独的服务器(Kubernetes 也有一个版本 [kube-monkey](https://github.com/asobti/kube-monkey) 用于杀掉 pods)。这些工具都是作用于服务器/平台层面,但如果你想测试及生产纯粹的 Node 噪声比如检查你的 Node 进程如何处理未知错误、未知的 promise rejection、v8 内存超过 1.7GB 的限制以及当事件循环经常卡住后你的 UX 是否仍正常运行?为了解决上面提到的这些问题, [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 不要写全局的 fixtures 和 seeds,而是放在每个测试中 + +:white_check_mark: **建议:** 参照黄金法则,每条测试需要在它自己的 DB 行中运行避免互相污染。现实中,这条规则经常被打破:为了性能提升而在执行测试前全局初始化数据库([也被称为‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture))。尽管性能很重要,但是它可以通过后面讲的「分组件测试」缓和。为了减轻复杂度,我们可以在每个测试中只初始化自己需要的数据。除非性能问题真的非常显著,那么可以做一定的妥协——仅在全局放不会改变的数据(比如 query)。 +
    + + +❌ **否则:** 一部分测试挂了,我们的团队花费大量宝贵时间后发现,是由于两个测试同时改变了同一个 seed 数据导致的。 + + +
    + +
    代码示例 + +
    + +### :thumbsdown: 反例: 用例之间不独立,而是依赖同一个全局钩子来生成全局 DB 数据 + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg + "Examples with Jest") + +```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 :[ +}); + +``` +
    + +### :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); +}); + +``` + +
    + +

    + +# 第三章: 前端测试 + +## ⚪ ️ 3.1. 将 UI 与功能分离 + +:white_check_mark: **建议:** 当专注于测试组件逻辑时,UI 细节就变成了应该剔除的噪音,这样您的测试就可以集中在纯数据上。实际上,通过抽象从代码中提取所需的数据将降低与图形实现的耦合,仅对纯数据 (vs 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 CSS 类从 'thick-border' 更改为 'thin-border'。 + +
    + +
    代码示例 + +
    + +### :clap: 正例: 使用专用的 attrbiute 查询元素进行测试 + +![](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 attribute +```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、部分和 shallow render——这么做可能会由于缺乏细节导致未捕获的 bug,并且由于测试与内部的混在一起将增加维护成本(参见小结“多用黑盒测试”)。如果其中一个子组件明显拖慢测试(如动画)或使很难配置,可以考虑主动用伪组件替换它。 + +综上所述,需要注意的是: 这种技术适用于封装一定数量子组件的中小型组件。如果一个组件包含太多的子组件,那么将很难对失败测试进行定位(分析根本原因),并且可能会变得过于缓慢。在这种情况下,只需针对胖父组件编写少量测试,而针对其子组件编写更多测试。 + +
    + +❌ **否则:** 之前通过调用组件的私有方法来测试组件的内部状态。后续重构组件时你必须重构所有测试。你真的有能力进行这种程度的维护吗? + + +
    + +
    代码示例 + +
    + +### :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 render 测试伪组件 +```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,使用框架内置的对 async 事件的支持。并且尝试提效。 + +:white_check_mark: **建议:** 在许多情况下,被测试单元的完成时间是未知的 (例如,animation 挂起了元素表现 )——在这种情况下,不要 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 仅在异步完成后 resolve (Cypress) + +![](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 +// 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/)。最重要的是,你还可以关注技术原因,比如确保内容被压缩、第一个字节的时间、优化图像、确保合理的 DOM 大小、SSL 和许多其他方面。建议在开发期间使用这些丰富的监视器,作为 CI 的一部分,最重要的是在生产服务器/CDN上 24x7 使用它们。 + +
    + +❌ **否则:** 在精心设计了一个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 double](https://www.npmjs.com/package/testdouble) 等) 来 stub API 响应,而不是真正的对API的网络调用。最大的好处是防止出现故障——测试或预发环境下 api 的定义不是很稳定,尽管组件的表现正确(生产环境不适合测试,它通常会限制请求),但有时会请求失败。通过 stub 允许模拟各种 API 行为,比如当没有找到数据或 API 抛出错误时测试组件行为。最后但并非最不重要的原因是,网络调用将大大降低测试速度。 + +
    + +❌ **否则:** 测试的平均时长不在是几毫秒,一个经典的 API 调用花费 100ms+,这使得每个用例变慢 ~20x。 + + +
    + +
    代码示例 + +
    + +### :clap: 正例: stub 或拦截 API 调用 +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg + "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-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 写几个跨越整个系统的端到端测试 + +:white_check_mark: **建议:** 虽然 E2E (端到端) 通常表示在真实浏览器中进行 UI 测试(见 3.6),但某些情况下,它们表示覆盖整个系统的测试,包括真正的后端。后一种测试非常有价值,因为它们涵盖了前端和后端之间的集成 bug,这些 bug 可能是由于沟通 schema 时产生误会导致的。它们也是一种有效的方法来发现 backend-to-backend 集成问题 (例如微服务 A 将错误的信息发送给微服务 B) 甚至检测部署失败,目前后端没有像 [Cypress](https://www.cypress.io/) 和 [Pupeteer](https://github.com/GoogleChrome/puppeteer) 友好的 UI 框架一样友好且成熟的 E2E 框架。这种测试的缺点是,配置一个包含如此多组件的环境的成本很高,而且大多数组件都很脆弱——假设有 50 个微服务,即使其中一个失败,整个 E2E 也会失败。出于这个原因,我们应该少用这种技术,大概1-10个就够了。也就是说,即使是少量的 E2E 测试也很有可能捕获它们所针对的问题——部署和集成故障。建议在与生产环境相似的预发运行它们。 + +
    + +❌ **否则:** UI 可能在测试它的功能上投入了大量的精力,但最后才意识到后端返回的有效负载 (UI 必须使用的数据模式) 与预期有很大的不同。 + +
    + +## ⚪ ️ 3.8 通过复用登录凭证提速 E2E 测试 + +:white_check_mark: **建议:** 在涉及真实的后端并依赖有效的用户 token 进行 API 调用的 E2E 测试中,我们没有必要将测试按照「创建用户并在每个请求中登录」的级别隔离。相反,在测试执行开始之前只登录一次 (即 before-all hook),将 token 保存在一些本地存储中,并在请求之间复用它。这似乎违反了核心测试原则之一——保持测试的自治,不要耦合资源。虽然这是一个合理的担忧,但在 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: **建议:** 为了监控生产环境以及开发时的完整性检查,运行一个 E2E 测试,该测试访问所有或大部分站点页面并确保没有被中断。这种测试投资回报率极高,因为它非常容易编写和维护,但可以检测任何类型的故障,包括功能、网络和部署问题。其他类型的冒烟和完备性检查并没有那么可靠和详尽——一些 ops 团队只是 ping 主页 (生产),或者开发人员运行一些集成测试无法发现打包和浏览器问题。毫无疑问,烟雾测试不会取代功能测试,而只是作为一个快速的烟雾探测器。 + +
    + +❌ **否则:** 一切似乎都很完美,所有的测试都通过了,生产环境健康检查也是 OK 的,但是支付组件有一些打包问题,只有 `/Payment` 路径没有渲染。 + + +
    + +
    代码示例 + +
    + +### :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.contains('https://mysite.com/Login'); + cy.contains('Login'); + cy.contains('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),它具有 JavaScript 风格,参见下面的示例。另一个相似但不同的场景是 [StoryBook](https://storybook.js.org/),它可以将 UI 组件公开为一个图形化的目录,用户可以浏览每个组件的各种状态(如一个栅格组件的 w/o filter,使其渲染多行或者 0 行,等等),查看它的展示形式,以及如何触发状态——这也可以提供给产品人原,但主要是作为实时文档提供给消费这些组件的开发人员。 + +❌ **否则:** 你在测试上耗费了大量的资源,如果不利用这项投资来获取更大的价值,是很可惜的。 + + +
    + +
    代码示例 + +
    + +### :clap: 正例: 使用 cucumber-js 以人类语言描述测试 + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cocumber-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") + + +
    + + + + +## ⚪ ️ 3.11 使用自动化工具检测可视化问题 + + +:white_check_mark: **建议:** 设置自动化工具来抓取 UI 截屏,并在变更后检测内容重叠或中断等可视化问题。这样不仅可以确保数据的正确性,而且用户可以方便地看到它。这种技术没有被广泛采用,我们的测试思维更倾向于功能测试,但它代表了真实的用户体验,而且可以轻易地发现跨多设备类型的 UI bug。目前部分免费工具可以提供一些基础功能——生成和保存屏幕截图以供肉眼检查。虽然这种方法对于小应用来说可能已经足够了,但是它的缺陷与任何其他手动测试一样
    :任何变更后都需要耗费人力来处理。另一方面,由于缺乏清晰的定义,自动检测 UI 问题非常具有挑战性——这就是“视觉回归”领域解决这个难题的切入点:对比旧 UI 与最新的更改并检测差异。一些开源/免费的工具可以提供这个能力 (例如: [wraith](https://github.com/BBC-News/wraith)、PhantomCSS) 但可能安装耗时比较久。一些商业工具 (如  [Applitools](https://applitools.com/)、[Percy.io](https://percy.io/)) 则更进一步,它们简化了安装过程,并封装了高级特性,如管理 UI、告警、通过去除“视觉噪音”(如广告、动画) 进行智能捕获,甚至可以分析引发问题的 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 Cypress to illustrate the idea") + +``` +​# 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 Cypress to illustrate the idea") ![](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.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 中设置覆盖率门槛,并阻止不满足要求的构建(也可以为每一个组件设置门槛,见下面的例子)。另外,我们可以监测构建的覆盖率下降(当新提交的代码的覆盖率较低时)——这将推动开发者提升或者至少保持被测试的代码数。说了这么多,覆盖率仅仅是一个可量化的度量值,它并不能确切地证明你的测试的健壮性,你也可能被它骗到(见下一节内容)。 + +
    + + +❌ **否则:** 信心和数字是相辅相成的,如果无法确保你的测试已经覆盖了了大部分的系统,那你将会害怕,害怕会让你慢下来。 + + +
    + +
    代码示例 + +
    + +### :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 Cypress to illustrate the idea") + +![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest)") + +
    + + + +

    + +## ⚪ ️ 4.2 检查覆盖率报告,以发现未覆盖的区域和其他奇怪的地方 + +:white_check_mark: **建议:** 有些问题隐藏在雷达之下,而使用传统工具很难发现它们。它们通常不是真正的 bug,大多数情况下是应用的怪异表现,而这种表现可能造成严重影响。例如,一些代码区域几乎不会或很少被调用——你以为“PricingCalculator”类只会设置产品价格,结果他几乎不会被调用,即使我们的数据库中有 10000 件商品以及很多交易……代码覆盖率报告可以帮助你发现应用是否按照你的期望执行。初次之外,它高亮了那些类型的代码没有被测试到——80% 的代码被测试并不能说明你的关键部分被覆盖到。生成报告很简单——只需在构造或测试覆盖率时跑你的应用,然后看看花花绿绿的报告来告诉你每一片代码区域被多频繁地调到。如果你花一点时间看看这些数据——你可能会发现一些问题。 + +
    + + +❌ **否则:** 如果你不知道你的代码中有哪些部分没有被测试到,则你没法准确定位问题的来源。 + + +
    + +
    代码示例 + +
    + +### :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) + +
    + + +

    + +## ⚪ ️ 4.3 使用「变异测试」度量逻辑覆盖率 + +:white_check_mark: **建议:** 传统覆盖率通常是骗人的:它可能显示了 100% 的代码覆盖率,但是你的所有的函数都没有返回正确的结果。怎么回事?它只是简单地度量你的测试代码访问过哪些代码行,而不会检查 tc 是否真正地测试了什么——断言了正确的返回。
    基于变更的测试适用于这个需求。它度量了真正被**测试过**的代码而不是仅仅被**访问过**的。[Stryker](https://stryker-mutator.io/) 是一个用于变异测试的 JavaScript 库,而它的实现很巧妙: + +(1) 它有意地改变代码并「植入 bug」。例如代码 newOrder.price===0 会被改成 newOrder.price!=0,这个 “bug”即成为变异。 + +(2) 它跑一遍用例,如果所有都成功了则说明有问题——这些用例没有真正实现他们发现 bug 的目的,这些变异即所谓的“存活”了。如果用例失败了,那么很棒,变异被杀掉了。 + +相对于传统覆盖率,得知所有或者大部分变异被杀掉会给予你更高的信心,而两者花费的时间差不多。 +
    + + +❌ **否则:** 你会误以为 85% 的覆盖率代表你的测试会发现你代码中的 85% 的 bug. + +
    + +
    代码示例 + +
    + +### :thumbsdown: 反例: 100% 覆盖率, 0% 被测试到 + +![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg + "Using Cypress to illustrate the idea") +```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({asignee: "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 +}); +``` + +
    + +

    + + +# 第五章:持续集成(CI) + +

    + +## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues + +:white_check_mark: **建议:** 只需五分钟配置,即可免费获取自动保护代码的工具来捕获代码中的显著问题。Lint 不再只是样式工具,现在的 linter 可以捕获很多严重的问题比如 error 没有被正确抛出以及信息丢失。在基础 rule(如 [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 而日志没有显示错误堆栈信息。到底发生了什么?你的代码错误地抛了一个非 error 的对象,而堆栈 trace 丢失了,真让人头秃……只需要五分钟配置一个 linter 来发现这个书写错误即可节省你大量的时间。 + +
    + +
    代码示例 + +
    + +### :thumbsdown: 反例: 出错的对象被错误地抛出,没有显示这个错误的堆栈信息。好在 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: **建议:** 在本地使用一个包含测试、Lint、稳定性检查等功能的 CI 可以帮助开发者迅速得到反馈并缩短[反馈循环](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/)。因为一个有效的测试流程包含很多迭代循环 (1) 尝试 -> (2) 反馈 -> (3) 重构。所以反馈越快,开发者可以在每个模块中可以执行的迭代就越多,并且可以得到更好的结果。反过来,如果反馈来得很慢,则一天只能执行很少的迭代,则团队可能会因急需执行下一个主题/任务/模块 而不再提炼当前模块。 + +目前已有一些 CI 供应商 (如: [CircleCI load CLI](https://circleci.com/docs/2.0/local-cli/)) 支持在本地执行 CI。一些商业工具如 [wallaby](https://wallabyjs.com/) 为开发原型提供了非常有用的测试能力。或者你可以仅仅在 package.json 中添加 npm 脚本来跑一些质量命令——使用工具如 [concurrently](https://www.npmjs.com/package/concurrently) 来并行执行,并在任何工具失败后抛出非 0 exit code。则开发者只需执行一个命令(如 `npm run quality` )来快速获取反馈。可以用 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 在真实的生产环境镜像中执行端到端测试 + +:white_check_mark: **建议:** 端到端测试是每个 CI 的主要挑战——实时创建一个生产环境镜像并带上所有相关的云服务是很费时费力的。你需要找到最佳的折中:[Docker-compose](https://serverless.com/) 通过一个文本文件将独立的 docker 环境放置到相同的容器中,但是背后的技术(如网络、构建模型)与真实世界有所差别。你可以将其与[‘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 管道变得很容易,其他的则允许针对远程 Kubernetes 进行自定义脚本。 + +
    + + +❌ **否则:** 生产和测试环境使用不同的技术,需要维护两个部署模型,并将开发人员和 ops 团队分隔开来。 + + +
    + +
    代码示例 + +
    + +### :clap: 正例: 动态生成 Kubernetes 集群的 CI 管道 (贡献: 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: **建议:** 只要操作合理,测试是你 7x24 小时的朋友,为你提供非常及时的反馈。实际上,在单个线程上执行 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 上 - 并行化需要保证测试用例的独立性,因为每个用例可能在不同的进程上运行。 + + +❌ **否则:** 在推送新代码 1 小时后获得测试结果,而你已经在写下一个 feature 了。 + + +
    + +
    代码示例 + +
    + +### :clap: 正例: Mocha parallel & Jest 轻松地加速了传统的 Mocha,感谢并行测试([贡献:JavaScript测试运行基准](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 包,如 [license check](https://www.npmjs.com/package/license-checker) 和 [plagiarism check](https://www.npmjs.com/package/plagiarism-checker)(商业的,但是有免费选项)可以很容易地加入您的 CI 管道,并检查一些坑:依赖限制性许可证或从Stackoverflow复制粘贴的代码,或者很明显地侵犯了某些版权。 + +❌ **否则:** 无意中,开发人员可能会使用包含不适当许可证的软件包或复制粘贴商业代码并遇到法务问题。 + + +
    + +
    代码示例 + +
    + +### :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 调用他俩。 + +❌ **否则:** 在没有专用工具的帮助下保持代码远离漏洞,将需要不断关注有关新威胁的发布信息。 这相当乏味。 + + +
    + +
    代码示例 + +
    + +### :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)使用商业工具,他们可以扫描代码并自动发送更新依赖的 PR。剩下的一个有趣的问题是依赖更新策略—— 每个补丁的更新都会产生太多的开销,而大版本发布时更新可能会指向一个不稳定的版本(许多软件包在发布后的几天内被爆出漏洞,请[参阅](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: 正例: 可以手动或在 CI 管道中使用 [ncu](https://www.npmjs.com/package/npm-checkupdates) 来检测代码在最新版本之后的滞后程度 + +![alt text](assets/bp-27-yoni-goldberg-npm.png "Nncu 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。 +1. 选择具有本地 Docker 支持的工具。 +1. 尽快失败,先运行最快的测试。设立一个“冒烟测试” 阶段/里程碑,对多个快速检查工具(如 linting,单元测试)进行分组,为代码提交者提供快速反馈。 +1. 设法方便地浏览构建的所有产出,包括测试报告,覆盖率报告,变异报告,日志等。 +1. 为每个事件创建多个管道/作业,提取他们的相同工作。例如,为功能分支的提交配置一个作业,为 master PR配置另一个。(大多数工具提供了一些代码重用的机制) +1. 永远不要在工作声明中加入机密信息,从机密库或工作的配置中获取它。 +1. 在发布构建中明确目标版本号 +1. 仅构建一次并对整个构建执行所有检查(例如Docker镜像) +1. 在一个临时的环境中进行测试,这个环境不会在不同构建之间产生状态漂移。缓存 node_modules 可能是惟一的例外。 + +
    + + +❌ **否则:** 你会错过多年来智慧的结晶 + +

    + +## ⚪ ️ 5.9 构建模型(Matrix):使用多个 Node 版本执行同一个 CI 流程 + +:white_check_mark: **建议:** 质量检查是用于发现意外,你覆盖的部分越多,你就越可能尽早地发现问题。 在开发包或运行具有各种配置和 Node 版本的多客户生产环境时,CI 必须在所有配置的组合上运行测试管道。 例如,假设我们的某些客户使用 MySQL,另一批客户使用 Postgres。一些 CI 工具支持一种称为“Matrix”的功能,该功能可以针对 MySQL、Postgres 和多个 Node 版本(如8、9、10)的所有组合执行测试。 只要配置即可完成而无需任何额外工作。 其他不支持 Matrix 的 CI 可能可以通过扩展或一定调整来实现这个功能。 +
    + + +❌ **否则:** 在辛辛苦苦写完所有用例编写之后,怎么可以因为配置问题而让漏洞溜进来? + + +
    + +
    代码示例 + +
    + +### :clap: 正例: 使用 Travis (CI 提供商) 构建配置,在多个 Node 版本上运行相同的测试 + +```yaml +language: node_js +node_js: + - "7" + - "6" + - "5" + - "4" +install: + - npm install +script: + - npm run test +``` + +
    + +

    + +# Team + + + +## Yoni Goldberg + +
    + +
    + +**Role:** 作者 + +**About:** 我是一名独立顾问,与 500 强企业和创业公司合作,完善他们的 JS 和 Node.js 应用。与其他任何话题相比,我更感兴趣的是掌握测试的艺术。我也是[Node.js 最佳实践](https://github.com/goldbergyoni/nodebestpractices)的作者。 + +
    + +**Workshop:** 👨‍🏫 是否想在您自己的办公室中(欧洲和美国)学习所有这些实践和技术? [在此处注册我的测试工作室](https://testjavascript.com/) +
    + +**关注:** + +* [🐦 Twitter](https://twitter.com/goldbergyoni/) +* [📞 Contact](https://testjavascript.com/contact-2/) +* [✉️ Newsletter](https://testjavascript.com/newsletter//) + +
    +
    +
    + + +## [Bruno Scheufler](https://github.com/BrunoScheufler) + +**角色:** 技术评审人和顾问 + +致力于修改、完善、备注及优化所有文字。 + +**关于我:** 全栈 Web 工程师,Node.js 和 GraphQL 爱好者 +
    +
    + +## [Ido Richter](https://github.com/idori) + +**Role:** 概念,设计以及提供好的建议 + +**About:** 优秀的前端开发者,CSS 专家,emoji 怪 + +## [Kyle Martin](https://github.com/js-kyle) + +**Role:** 帮助保持本项目的运行,并审查与安全性有关的实践 + +**About:** 喜欢从事 Node.js 项目和 Web 应用安全性的工作。 From fc70faf9eca7daba6a5c2cbe09e2b5d10eb980e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=95=E5=A4=AB?= Date: Wed, 9 Oct 2019 10:38:39 +0800 Subject: [PATCH 094/502] refactor: remove useless vscode config folder --- .vscode/settings.json | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ba08ab99..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "workbench.colorCustomizations": { - "activityBarBadge.background": "#2979FF", - "list.activeSelectionForeground": "#2979FF", - "list.inactiveSelectionForeground": "#2979FF", - "list.highlightForeground": "#2979FF", - "scrollbarSlider.activeBackground": "#2979FF50", - "editorSuggestWidget.highlightForeground": "#2979FF", - "textLink.foreground": "#2979FF", - "progressBar.background": "#2979FF", - "pickerGroup.foreground": "#2979FF", - "tab.activeBorder": "#2979FF", - "activityBar.background": "#09332D", - "titleBar.activeBackground": "#0C483F", - "titleBar.activeForeground": "#F2FDFB" - } -} \ No newline at end of file From 081926ff35a965afa62d745436cdb69d3c71d29c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=95=E5=A4=AB?= Date: Wed, 9 Oct 2019 10:50:03 +0800 Subject: [PATCH 095/502] fix: fix links in content table & fix some missing translations --- readme-zh-CN.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme-zh-CN.md b/readme-zh-CN.md index e28121e4..cc39e6fc 100644 --- a/readme-zh-CN.md +++ b/readme-zh-CN.md @@ -31,27 +31,27 @@ ## `内容列表` -#### [`第 0 章:黄金法则`](#section-0️⃣-the-golden-rule) +#### [`第 0 章:黄金法则`](#第-0-章黄金法则-1) 一条启发所有人的建议(特殊的 1 条) -#### [`第一章:测试剖析`](#section-1-the-test-anatomy-1) +#### [`第一章:测试剖析`](#第一章-测试剖析) 基础 - 搭建干净的测试(12 条) -#### [`第二章:后端`](#section-2️⃣-backend-testing) +#### [`第二章:后端测试`](#第二章-后端测试) 高效地编写后端和微服务的测试(8 条) -#### [`第三章:前端`](#section-3️⃣-frontend-testing) +#### [`第三章:前端测试`](#第三章-前端测试) 为 UI(包括组件和 E2E 测试)编写测试(11 条) -#### [`第四章:度量测试效果`](#section-4️⃣-measuring-test-effectiveness) +#### [`第四章:度量测试效果`](#第四章-度量测试效果) 度量测试质量(4 条) -#### [`第五章:持续集成(CI)`](#section-5️⃣-ci-and-other-quality-measures) +#### [`第五章:持续集成(CI)`](#第五章持续集成ci-1) JS 领域的 CI 指南(9 条) @@ -1718,11 +1718,11 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# 第五章:持续集成(CI) +# 第五章:持续集成(CI)以及其他质量度量手段

    -## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues +## ⚪ ️ 5.1 丰富你的 linter 并丢弃有 lint 问题的构建 :white_check_mark: **建议:** 只需五分钟配置,即可免费获取自动保护代码的工具来捕获代码中的显著问题。Lint 不再只是样式工具,现在的 linter 可以捕获很多严重的问题比如 error 没有被正确抛出以及信息丢失。在基础 rule(如 [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(…)。 From 55beddcfec76e45db5c58cb525124c00409d77cb Mon Sep 17 00:00:00 2001 From: devori Date: Wed, 9 Oct 2019 17:46:09 +0900 Subject: [PATCH 096/502] [Translation - Korean] 3.3 ~ 3.4 --- readme.kr.md | 55 ++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 3062f4c4..2d812a29 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1053,24 +1053,23 @@ test('데이터가 전달되지 않으면, 0을 보여준다', () => {
    -## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component +## ⚪ ️ 3.3 가능한한, 실제와 같고 완전히 렌더링된 컴포넌트를 테스트하십시오 -:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +:white_check_mark: **이렇게 해라:** 적당한 크기가 때마다 사용자가 하는 것처럼 외부로부터 컴포넌트를 테스트하고, 화면를 완전히 렌더링하고, 그에 따라 조치를 취하고 렌더링 된 화면이 예상대로 작동하는지 확인하십시오. 모든 종류의 목킹, 부분 및 얕은 렌더링을 피하십시오. 이 접근은 세부정보의 부족으로 인해 걸리지 않는 버그가 발생할 수 있으며, 내부요소들과 함께 지저분해진 테스트들과 같이 유지보수를 하기 어렵게 만들 수도 있습니다. (see bullet 'Favour blackbox testing'). 만약 자식 컴포넌트들중 하나가 심각하게 느려지게 하거나(예: 애니메이션)) 설정을 복잡하게 하는 경우에는, 해당요소를 가상으로 처리하는 것이 좋습니다. -With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children +주의할 점: 이 기술은 자식 컴포넌트들의 크기가 적당하게 묶여있는, 소형 혹은 중형 컴포넌트들에게 적합합니다. 너무 많은 자식들과 함께 렌더링된 컴포넌트는, 테스트가 실패한 원인(근본원인 분석)을 추론하기도 어렵고 매우 느려질 수도 있습니다. 이러한 경우들에서는, 부모에 대해서는 몇가지 테스트만을 작성하고, 자식들에 대해서 더 많은 테스트를 작성하십시오.
    -❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? - +❌ **그렇지 않으면:** 내부 메소드를 호출하여 컴포넌트의 내부에 영향을 주고 그리고 내부의 상태를 확인한다면 - 당신이 컴포넌트의 구현을 리팩토링할때, 모든 테스트도 함께 변경해야 합니다. 당신은 유지보수를 위한 그런 여유가 있습니까?
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Working realstically with a fully rendered component +### :clap: 올바른 예: 완전하게 렌더링된 컴포넌트와 함께 실제와 같은 동작 ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Enzyme-blue.svg @@ -1091,7 +1090,7 @@ class Calendar extends React.Component { } //Examples use React & Enzyme -test('Realistic approach: When clicked to show filters, filters are displayed', () => { +test('실제적인 접근: 필터들을 클릭하면, 필터들이 화면에 표시된다', () => { // Arrange const wrapper = mount() @@ -1100,26 +1099,26 @@ test('Realistic approach: When clicked to show filters, filters are displayed', // Assert expect(wrapper.text().includes('Choose Filter')); - // This is how the user will approach this element: by text + // 사용자가 요소에 접근하는 방법: 텍스트를 이용 }) ``` -### :thumbsdown: Anti-Pattern Example: Mocking the reality with shallow rendering +### :thumbsdown: 잘못된 예: 얕은 렌더링과 함께 실제를 목킹 ```javascript -test('Shallow/mocked approach: When clicked to show filters, filters are displayed', () => { +test('얕은/목킹 접근: 필터들을 클릭하면, 필터들이 화면에 표시된다', () => { // 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? + // name을 변경하거나, 관련된 다른 것들을 전달하지 않는다면 어떻게 될까? }) ``` @@ -1129,21 +1128,21 @@ test('Shallow/mocked approach: When clicked to show filters, filters are display
    -## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up +## ⚪ ️ 3.4 슬립을 사용하지 마십시오. 프레임워크에서 비동기 이벤트들을 위해 지원하는 내장 기능을 사용하십시오. 그리고 속도를 높이려 노력하십시오 -:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +:white_check_mark: **이렇게 해라:** 대부분의 경우에 테스트 완료시간은 알 수 없습니다. (예: 애니메이션은 요소의 출현을 지연시킴) - 이런 경우에는 슬립(예: setTimeout)을 피하고, 대부분의 플랫폼들이 제공하는 더 결정적인 메소드들을 사용하십시오. 몇몇 라이브러리들은 awaiting 기능을 허용합니다. (예: [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)가 유용할 수 있습니다. 슬립은 당신의 테스트를 느리고 위험하게 만들기 때문에 피해야할 패턴입니다(너무 짧은 시간 기다려야할 경우). 만약 슬립과 폴링이 필연적이고 테스트 프레임워크의 지원이 없다면, [wait-for-expect](https://www.npmjs.com/package/wait-for-expect)와 같은 라이브러리들이 준결정 솔루션으로서 도움을 줄 수도 있습니다.
    -❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance +❌ **그렇지 않으면:** 오랜 시간동안 슬립하는 경우, 테스트는 더 느려질 것 입니다. 슬립할때, 테스트중인 유닛이 제 시간에 반응하지 않으면 테스트는 실패할 것 입니다. 그래서 그것은 테스트가 실패하는 약점과 나쁜 성능간의 트레이드 오프를 가지게 됩니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) +### :clap: 올바른 예: 비동기 실행이 완료될때 처리되는 E2E API (Cypress) ![](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 @@ -1153,35 +1152,35 @@ test('Shallow/mocked approach: When clicked to show filters, filters are display // 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: Doing It Right Example: Testing library that waits for DOM elements +### :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: Anti-Pattern Example: custom sleep code +### :thumbsdown: 잘못된 예: 사용자 정의 슬립 코드 ```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){ @@ -1191,7 +1190,7 @@ test('movie title appears', async () => { }, 100); - // wait for appearance and return the element + // 출현을 기다린 후 요소를 리턴 const movie = await waitForElement(() => getByText('the lion king')) }) @@ -1575,7 +1574,7 @@ cy.eyesCheckWindow('mark as completed');

    -## ⚪ ️ 4.2 커버리지 리포트를 확인하여 테스트 되지 않은 부분과 기타 이상한 점들을 감지하십시오. +## ⚪ ️ 4.2 커버리지 리포트를 확인하여 테스트 되지 않은 부분과 기타 이상한 점들을 감지하십시오. :white_check_mark: **이렇게 해라:** 일부 문제들은 레이더망 아래로 숨어버려 기존의 툴들을 사용하여 찾기 매우 어렵습니다. 이것들은 실제로 버그는 아니지만 심각한 영향을 줄 수 있는 생각지 못 한 어플리케이션 동작들입니다. 예를 들어, 일부 코드 영역은 절대 또는 거의 호출되지 않습니다. - ‘PricingCalculator’라는 상품 가격을 설정하는 클래스가 있다고 생각해 보세요. DB에 100000개의 상품이 있고 판매도 많지만 이 클래스는 실제로 절대 호출되지 않는 것으로 밝혀졌습니다... 코드 커버리지 리포트를 통해 어플리케이션이 당신이 원하는 대로 동작하는지 확인할 수 있습니다. 그 외에도 리포트는 어떤 코드들이 테스트되지 않았는지를 강조해서 보여줄 수도 있습니다. - 코드의 80%가 테스트 되었다는 알림이 중요한 부분이 커버되었는지에 대한 여부를 나타내진 않습니다. 리포트를 만드는 것은 쉽습니다. - 운영 또는 테스트를 할 때 커버리지 트래킹을 하면서 어플리케이션을 실행하세요. 그러고 나서 각 코드 영역이 얼마나 자주 호출됐는지를 나타내는 형형색색의 리포트를 보세요. 잠깐 시간을 내서 이 데이터들을 보면 몇 가지 문제점들을 발견하게 될 수도 있습니다.
    From 5b2382c225f58289014fd073acd9b2b9eb0a0566 Mon Sep 17 00:00:00 2001 From: icepeng Date: Tue, 15 Oct 2019 10:37:33 +0900 Subject: [PATCH 097/502] [Translation - Korean] 5.1 --- readme.kr.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 2d812a29..1a8ec159 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1686,27 +1686,27 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# Section 5️⃣: CI and Other Quality Measures +# Section 5️⃣: 지속적인 통합

    -## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues +## ⚪ ️ 5.1 린터를 풍성하게 구성하고 린트 문제가 있는 빌드를 중단하십시오 -:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash._map(…) +:white_check_mark: **이렇게 해라:** 린터는 공짜 점심이며, 5분의 설정만으로 코드를 지켜주고 입력과 동시에 중요한 문제를 포착하는 자동 조종 장치를 거저 얻을 수 있습니다. 린터가 장식(세미콜론)에 지나지 않던 시대는 지나갔습니다. 요즘의 린터는 올바르게 throw되지 않고 정보가 손실되는 오류와 같은 심각한 문제를 포착 할 수 있습니다. 기본 규칙 세트 ([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) 는 resolve되지 않는 Promise를 발견해줍니다 (이런 코드는 계속해서 실행되는것이 불가능합니다), [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)는 코드가 Lodash._map(...)과 같은 V8 코어 메소드의 일부인 유틸리티 라이브러리 메소드를 사용할 때 경고해줍니다.
    -❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day +❌ **그렇지 않으면:** 프로덕션이 계속 깨지는데 로그에 에러의 stack trace가 표시되지 않는 우울한 날을 상상해봅시다. 어떻게 된 걸까요? 실수로 코드가 에러가 아닌 객체를 던지고 있어서 stack trace가 손실되었다면, 벽에 머리를 들이박기 딱 좋을것입니다. 5분의 린터 설정으로 이런 오타를 감지하고 하루를 지켜낼 수 있습니다.
    -
    Code Examples +
    코드 예제
    -### :thumbsdown: Anti Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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") +### :thumbsdown: 올바르지 않은 예: 잘못된 Error 객체가 실수로 throw되어 이 오류에 대한 stack trace가 나타나지 않습니다. 운 좋게도 ESLint는 다음과 같은 프로덕션 버그를 잡아냅니다. +![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "잘못된 Error 객체가 실수로 throw되어 이 오류에 대한 stack trace가 나타나지 않습니다. 운 좋게도 ESLint는 다음과 같은 프로덕션 버그를 잡아냅니다.")
    From c1c0aa387de2ba39e4664bc6c8429cbc172f7d85 Mon Sep 17 00:00:00 2001 From: ha Date: Fri, 18 Oct 2019 01:21:30 +0900 Subject: [PATCH 098/502] [Translation - Korean] 5.2 --- readme.kr.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 1a8ec159..7099263c 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1715,15 +1715,15 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI +# ⚪ ️ 5.2 로컬 개발자 CI로 피드백 주기 단축 -:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +:white_check_mark: **이렇게 해라:** 테스트, 린트, 취약점 확인 등과 같은 근사한 품질 검사가 포함된 CI를 사용합니까? 개발자가 이 파이프라인을 로컬에서도 실행할 수 있도록 해서 [피드백 주기](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/)를 단축하십시오. 왜? 효율적인 테스트 프로세스는 많은 반복 루프를 구성합니다. (1) 시도 -> (2) 피드백 -> (3) 리팩터링. 피드백이 빠를수록 개발자가 모듈 각각을 개선하며 결과를 완벽하게 할 수 있습니다. 한편, 피드백이 늦어지면서 하루에 개선이 반복되는 빈도가 적어진다면, 팀은 이미 다른 주제 / 작업 / 모듈로 넘어갈 수 있으며 해당 모듈의 수정이 이루어지지 않을 수 있습니다. -Practically, some CI vendors (Example: [CircleCI load CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +실제로 몇몇 CI 공급 업체 (예: [CircleCI load CLI](https://circleci.com/docs/2.0/local-cli/)) 는 파이프라인의 로컬 실행을 허용합니다. [wallaby](https://wallabyjs.com/)와 같은 몇몇 상용 도구는 개발자 프로토타입으로 높은 가치의 테스트 통찰력을 제공합니다(협찬 아님). 또는 모든 품질 관련 명령어(예 : test, lint, vulnerabilities)를 실행하는 npm 스크립트를 package.json에 추가 할 수 있습니다. 병렬화를 위해 [concurrently](https://www.npmjs.com/package/concurrently)와 같은 도구를 사용하고 명령어 중 하나가 실패할 경우에는 0이 아닌 종료 코드를 사용하십시오. 이제 개발자는 하나의 명령을 호출해야 합니다. ‘npm run quality’— 즉각적인 피드백을 받습니다. githook을 사용하여 품질 검사에 실패한 경우 커밋을 중단하는 것도 고려하십시오 ([husky가 도움될 수 있음](https://github.com/typicode/husky))
    -❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact +❌ **그렇지 않으면:** 코드를 작성한 다음 날에 품질 결과가 도착한다면 테스트는 개발 과정에 자연스럽게 포함될 수 없습니다
    @@ -1732,7 +1732,7 @@ Practically, some CI vendors (Example: [CircleCI load CLI](https://circleci.com/
    -### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: 올바른 예: 코드 품질 검사를 수행하는 npm 스크립트는 요청 시 또는 개발자가 새 코드를 푸시하려고 할 때 모두 병렬로 실행됩니다. ```javascript "scripts": { "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", From 19fc94aea042a989a74e126dd635818833df0bda Mon Sep 17 00:00:00 2001 From: Tom Nagle Date: Wed, 23 Oct 2019 10:47:18 +1100 Subject: [PATCH 099/502] Update readme.md Minor language changes to make it easier to read. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0005d756..9c513293 100644 --- a/readme.md +++ b/readme.md @@ -70,7 +70,7 @@ Testing code is not like production-code - design it to be dead-simple, short, a Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. -The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells we have two brain systems: system 1 which is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should *feel* as easy as modifying an HTML document and not like solving 2X(17 × 24). +The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should *feel* as easy as modifying an HTML document and not like solving 2X(17 × 24). This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. From 80d86b38f1f7ed616829344f4d55eed955640209 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sat, 26 Oct 2019 21:40:01 +0300 Subject: [PATCH 100/502] Update readme.md --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 9c513293..a81e980c 100644 --- a/readme.md +++ b/readme.md @@ -1466,6 +1466,8 @@ Feature: Twitter new tweet ### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook ![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") +![alt text](assets/story-book.jpg "Storybook") +
    From 8dd1c0bbdcb5df4b1624c7c50df2f899e8f0e304 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sat, 26 Oct 2019 21:41:36 +0300 Subject: [PATCH 101/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index a81e980c..b63dd2c1 100644 --- a/readme.md +++ b/readme.md @@ -1471,7 +1471,7 @@ Feature: Twitter new tweet
    - +

    ## ⚪ ️ 3.11 Detect visual issues with automated tools From dfd8fa78b23faf8e0f5ea213a384a09a5f9a08bf Mon Sep 17 00:00:00 2001 From: Userbit <34487074+Userbit@users.noreply.github.com> Date: Tue, 29 Oct 2019 14:40:12 +0300 Subject: [PATCH 102/502] Correct method name & add return value in *1.4 ... Test only public methods* --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b63dd2c1..125c39a4 100644 --- a/readme.md +++ b/readme.md @@ -290,7 +290,7 @@ it("When asking for an admin, ensure only ordered admins in results" , () => { class ProductService{ //this method is only used internally //Change this name will make the tests fail - calculateVAT(priceWithoutVAT){ + calculateVATAdd(priceWithoutVAT){ return {finalPrice: priceWithoutVAT * 1.2}; //Change the result format or key name above will make the tests fail } @@ -298,6 +298,7 @@ class ProductService{ getPrice(productId){ const desiredProduct= DB.getProduct(productId); finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice; + return finalPrice; } } From 3563f9975da273b366c7bd4ce4d2536bde2645eb Mon Sep 17 00:00:00 2001 From: Userbit <34487074+Userbit@users.noreply.github.com> Date: Tue, 29 Oct 2019 15:13:07 +0300 Subject: [PATCH 103/502] Add missing expect() in 1.5 Section --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index b63dd2c1..907351c0 100644 --- a/readme.md +++ b/readme.md @@ -357,6 +357,7 @@ 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); //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; }); ``` From 9ae73cc29d92f1c90a3489fb58596e40ebf4ab25 Mon Sep 17 00:00:00 2001 From: Userbit <34487074+Userbit@users.noreply.github.com> Date: Tue, 29 Oct 2019 15:32:36 +0300 Subject: [PATCH 104/502] Correct typo in 1.6 Section --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b63dd2c1..cd82f330 100644 --- a/readme.md +++ b/readme.md @@ -389,7 +389,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ```javascript const addProduct = (name, price) =>{ - const productNameRegexNoSpace = /^\S*$/;//no white-space allowd + const productNameRegexNoSpace = /^\S*$/; //no white-space allowed if(!productNameRegexNoSpace.test(name)) return false;//this path never reached due to dull input From 5292ce3163dc25e86f87e92479f68b4edb3045f3 Mon Sep 17 00:00:00 2001 From: Userbit <34487074+Userbit@users.noreply.github.com> Date: Tue, 29 Oct 2019 17:45:39 +0300 Subject: [PATCH 105/502] Fix in 1.8 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b63dd2c1..6c2790a0 100644 --- a/readme.md +++ b/readme.md @@ -517,7 +517,7 @@ it('When visiting TestJavaScript.com home page, a menu is displayed', () => { //Arrange //Act -receivedPage tree = renderer +const receivedPage = renderer .create( Test JavaScript < /DisplayPage>) .toJSON(); From edba996804401e16825353b977e04aafec99cde4 Mon Sep 17 00:00:00 2001 From: Userbit <34487074+Userbit@users.noreply.github.com> Date: Tue, 29 Oct 2019 18:42:09 +0300 Subject: [PATCH 106/502] Fix in 1.10 --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index b63dd2c1..401f870a 100644 --- a/readme.md +++ b/readme.md @@ -625,8 +625,8 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e it("When no product name, it throws error 400", async() => { let errorWeExceptFor = null; try { - const result = await addNewProduct({name:'nest'});} -catch (error) { + const result = await addNewProduct({}); +} catch (error) { expect(error.code).to.equal('InvalidInput'); errorWeExceptFor = error; } @@ -642,7 +642,7 @@ expect(errorWeExceptFor).not.to.be.null; ```javascript it.only("When no product name, it throws error 400", async() => { - expect(addNewProduct)).to.eventually.throw(AppError).with.property('code', "InvalidInput"); + await expect(addNewProduct({})).to.eventually.throw(AppError).with.property('code', "InvalidInput"); }); ``` From 8899cd3f3d38c0b873fd9d80f9b7f9d4e29a16a5 Mon Sep 17 00:00:00 2001 From: Userbit <34487074+Userbit@users.noreply.github.com> Date: Wed, 30 Oct 2019 16:51:15 +0300 Subject: [PATCH 107/502] Fix typo in 3.2 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b63dd2c1..770c0872 100644 --- a/readme.md +++ b/readme.md @@ -1070,7 +1070,7 @@ test('When flagging to show only VIP, should display only VIP members', () => { // Act const { getByTestId } = render(); - expect(getByTestId('errorsLabel')).text()).toBe("0"); + expect(getByTestId('errorsLabel').text()).toBe("0"); }); ``` From ad0a688d949b914949ed23793412068d7ab4322e Mon Sep 17 00:00:00 2001 From: Userbit <34487074+Userbit@users.noreply.github.com> Date: Wed, 30 Oct 2019 18:23:31 +0300 Subject: [PATCH 108/502] Add correct code indentation in 3.11 --- readme.md | 47 ++++++++++++----------------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/readme.md b/readme.md index b63dd2c1..be408a22 100644 --- a/readme.md +++ b/readme.md @@ -1538,41 +1538,18 @@ paths: 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'); - -}); - + 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'); + }); }); ``` From 615f58ee0750c79e724a15245f93108ad27d6810 Mon Sep 17 00:00:00 2001 From: Userbit <34487074+Userbit@users.noreply.github.com> Date: Wed, 30 Oct 2019 19:02:29 +0300 Subject: [PATCH 109/502] Fix mess in 4.2 Code Examples --- readme.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index b63dd2c1..e2fd3869 100644 --- a/readme.md +++ b/readme.md @@ -1641,9 +1641,11 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :thumbsdown: Anti-Pattern Example: 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) +### :thumbsdown: Anti-Pattern Example: What’s wrong with this coverage report? -![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) +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?")
    From d8e93064f646b1d960f8604372f7041d8d998146 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Sun, 3 Nov 2019 01:55:12 +0900 Subject: [PATCH 110/502] Translate into Korean 5.8 to 5.9 --- readme.kr.md | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 7099263c..1e31a418 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1728,7 +1728,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    -
    Code Examples +
    예제 코드
    @@ -1897,32 +1897,41 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    -## ⚪ ️ 5.8 Other, non-Node related, CI tips -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +## ⚪ ️ 5.8 기타, 노드와 관련없는 CI 팁 -
    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
    -
    +:white_check_mark: **이렇게 해라:** 이 글은 Node.js와 관련이 있거나 최소한 Node.js로 예를 들 수 있는 테스트 조언에 중점을 두고 있습니다. 그러나 이번에는 Node.js와 관련없지만 잘 알려진 팁 몇개를 그룹화 하였습니다. +1. 선언적 구문을 사용하십시오. 대부분의 벤더에서는 선택할 수 없지만, 이전 버전의 Jenkins에서 코드 또는 UI를 사용할 수 있습니다. +2. 고유 Docker를 지원하는 벤더를 선택하십시오. +3. 일찍 실패하고 가장 빠른 테스트를 먼저 실행하십시오. 다양한 빠른 Inspection(예: 린트, 단위테스트)를 그룹화 하고 코드 커미터에 대한 신속한 피드백을 제공할 수 잇는 [스모크 테스트](https://terms.naver.com/entry.nhn?docId=864587&cid=42346&categoryId=42346) 단계/마일스톤을 만드십시오. +4. 테스트 보고서, 커버리지, 변화, 로그 등의 모든 결과물을 훑어보기 쉽게 하십시오. +5. 각 이벤트에 대해 여러 파이프라인/작업을 작성하고, 그 사이 단계를 재사용 하십시오. 예를 들면, feature 브랜치 커밋이나 마스터 PR에 대한 작업 구성. 각 재사용 로직이 공유 단계를 사용하게 하십시오.(대부분의 벤더는 코드 재사용을 위한 메커니즘을 제공합니다) +6. 작업 선언에 어떤한것도 숨겨놓지 마십시오. +7. 릴리스 빌드에서 명시적으로 버전을 충돌 시켜보거나 최소한 개발자가 그렇게했는지 확인하십시오. +8. 한번만 빌드하고 단일 빌드 결과물(예: Docker 이미지)에 대해 모든 검사를 수행하십시오. +9. 빌드간에 상태가 변하지 않는 임시 환경에서 테스트하십시오. node_modules 캐싱은 유일한 예외 일 수 있습니다. -❌ **Otherwise:** You‘ll miss years of wisdom +❌ **그렇지 않으면:** 수년간의 노하우를 놓치는 것과 같습니다.

    -## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions -:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that -
    +## ⚪ ️ 5.9 빌드 매트릭스: 여러 노드 버전을 사용해서 동일한 CI 단계를 실행 하십시오. + +:white_check_mark: **이렇게 해라:** 품질 검사는 [세런디피티](https://ko.wikipedia.org/wiki/%EC%84%B8%EB%9F%B0%EB%94%94%ED%94%BC%ED%8B%B0)에 관한 것으로, 문제를 조기에 발견하는데 도움이 되는 더 많은 기회를 제공합니다. 재사용 가능한 패키지를 개발하거나 다양한 구성 및 노드 버전으로 여러 고객의 제품을 실행하는 경우, CI는 모든 구성의 순열에 대해 테스트 파이프 라인을 실행해야합니다. 예를 들어, 일부 고객은 MySQL을 사용하고 다른 고객은 PostgreSQL을 사용한다고 가정합시다. 일부 CI 벤더는 '매트릭스'라는 기능을 제공하여 MySQL, PostgreSQL 및 8, 9, 10과 같은 여러 노드 버전의 모든 순열에 대해 테스트를 실행할 수 있습니다. 이 경우에는 어떠한 추가 노력없이 구성(설정)만을 사용하여 가능합니다(테스트 또는 기타 품질 검사가 있다고 가정). 매트릭스를 지원하지 않는 다른 CI는 확장이나 조정이 필요할 수 있습니다. +
    -❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? +❌ **그렇지 않으면:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues?
    -
    Code Examples +
    예제 코드
    -### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +### :clap: 올바른 예: Travis(CI 벤더) 빌드 정의를 사용하여 여러 노드 버전에 대한 동일한 테스트를 실행하십시오. +
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    @@ -1930,8 +1939,6 @@ An efficient update policy may allow some ‘vesting period’ — let the c # Team - - ## Yoni Goldberg
    From f84b2dd485d7579ebc1db2b578779e5849d61296 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Mon, 4 Nov 2019 00:00:03 +0900 Subject: [PATCH 111/502] Translate into Korean 5.7 --- readme.kr.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 1e31a418..29f48368 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1868,33 +1868,31 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.7 Automate dependency updates -:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: +## ⚪ ️ 5.7 종속성 업데이트 자동화 -(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. +:white_check_mark: **이렇게 해라:** yarn과 npm이 최근 소개한 package-lock.json는 중대한 도전입니다(지옥으로 가는 길은 좋은 의도로 포장되어 있습니다). 기본적으로 이제 패키지는 더이상 업데이트되지 않습니다. 'npm install'과 'npm update'을 통해 새로운 배포를 많이 수행하는 팀도 새로운 업데이트를 받지 않습니다. 이로 인해 하위 종속 패키지 버전이 최상이거나, 최악의 경우에는 취약한 코드가 됩니다. 팀은 이제 패키지를 수동으로 업데이트하기 위해 개발자의 의지와 기억에 의존하거나 [ncu](https://www.npmjs.com/package/npm-check-updates)같은 도구를 수동으로 사용하게 됩니다. 보다 안정적인 방법은 가장 안정적인 종속성 버전을 얻는 프로세스를 자동화하는 것입니다. 묘책은 없지만 가능한 두가지 자동화 방법이 있습니다: -(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). +(1) CI는 ['npm outdated'](https://docs.npmjs.com/cli/outdated) 또는 'npm-check-updates(ncu)'같은 툴을 사용하여 오래된 종속성을 가진 빌드를 실패하게 할 수 있습니다. 그렇게하면 개발자가 종속성 업데이트를 해야합니다. -An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) -
    +(2) 코드를 스캔하고 업데이트된 종속성으로 PR을 자동으로 보내주는 상용 도구를 사용하십시오. 남아있는 흥미로운 질문 하나는 종속성 업데이트 정책입니다. 모든 패치에서 업데이트를 하면 너무 많은 오버헤드가 발생합니다. 메이저 버전이 공개될 때 바로 업데이트 하면 불안정한 버전을 가리킬 수 있습니다.(많은 패키지가 출시된 직후 첫 날에 취약한 것으로 밝혀 짐 [esline-scope](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/)사건 참조) +효율적인 업데이트 정책은 일부 '투자 기간'을 허용할 수 있습니다. 로컬 사본을 버리기 전에 종속성의 시간과 버전을 @latest보다 약간 뒤쳐지게 하십시오. (예: 로컬 버전은 1.3.1 레파지토리 버전은 1.3.8) -❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky +
    +❌ **그렇지 않으면:** 당신의 제품은 작성자가 위험하다고 명시적으로 태그한 패키지를 실행할 것입니다.
    -
    Code Examples +
    예제 코드
    -### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions -![alt text](assets/bp-27-yoni-goldberg-npm.png "Nncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions") - +### :clap: 올바른 예: 코드가 최신 버전보다 어느정도 뒤쳐지는지 감지하기 위하여 [ncu](https://www.npmjs.com/package/npm-check-updates)를 수동으로 또는 CI 파이프라인 내에서 사용할 수 있습니다. +![alt text](assets/bp-27-yoni-goldberg-npm.png "코드가 최신 버전보다 어느정도 뒤쳐지는지 감지하기 위하여 ncu를 수동으로 또는 CI 파이프라인 내에서 사용할 수 있습니다.")
    -

    ## ⚪ ️ 5.8 기타, 노드와 관련없는 CI 팁 From 0a5ecf80d4e4991c50133b9b6686a7cbe1f35fb7 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Mon, 4 Nov 2019 00:34:46 +0900 Subject: [PATCH 112/502] Translate into Korean 5.6 --- readme.kr.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 29f48368..e542d15c 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1844,27 +1844,25 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.6 Constantly inspect for vulnerable dependencies -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like license check and plagiarism check (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights -
    - - -❌ **Otherwise:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +## ⚪ ️ 5.6 취약한 종속성을 지속적으로 검사 +:white_check_mark: **이렇게 해라:** 라이센싱과 표절 문제는 당장 당신의 주요 관심사가 아닐 수 있지만, 10분 안에 이 내용을 확인하지 않으시겠습니까? 많은 npm 패키지의 라이센스 및 표절 확인(상용 도구의 무료 플랜)을 CI 파이프라인에 쉽게 포함시킬 수 있습니다. 그리고 제한적인 라이센스의 종속성이나 Stack Overflow에서 복사하여 붙여넣은 일부 저작권을 위반한 것으로 보이는 코드를 점검하십시오.
    -
    Code Examples +❌ **그렇지 않으면:** Express와 같이 상당히 평판이 좋은 종속성 조차도 취약점이 있습니다. [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit)과 같은 커뮤니티 도구 또는 [snyk](https://snyk.io/)같은 상용 도구를 사용하여 쉽게 알 수 있습니다(무료 커뮤니티 버전도 제공). 둘다 당신의 CI의 모든 빌드에서 사용할 수 있습니다.
    -### :clap: Example: NPM Audit result -![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") +
    예제 코드 -
    +
    +### :clap: 올바른 예: NPM Audit 결과 +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit 결과") +


    @@ -1889,6 +1887,7 @@ license-checker --summary --failOn BSD
    ### :clap: 올바른 예: 코드가 최신 버전보다 어느정도 뒤쳐지는지 감지하기 위하여 [ncu](https://www.npmjs.com/package/npm-check-updates)를 수동으로 또는 CI 파이프라인 내에서 사용할 수 있습니다. + ![alt text](assets/bp-27-yoni-goldberg-npm.png "코드가 최신 버전보다 어느정도 뒤쳐지는지 감지하기 위하여 ncu를 수동으로 또는 CI 파이프라인 내에서 사용할 수 있습니다.")
    @@ -1909,6 +1908,8 @@ license-checker --summary --failOn BSD 8. 한번만 빌드하고 단일 빌드 결과물(예: Docker 이미지)에 대해 모든 검사를 수행하십시오. 9. 빌드간에 상태가 변하지 않는 임시 환경에서 테스트하십시오. node_modules 캐싱은 유일한 예외 일 수 있습니다. +
    + ❌ **그렇지 않으면:** 수년간의 노하우를 놓치는 것과 같습니다.

    From 5db9bb80756a10ee5288063a617d3c74fa9de424 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Tue, 5 Nov 2019 01:05:16 +0900 Subject: [PATCH 113/502] Translate into Korean 5.3 to 5.5 --- readme.kr.md | 73 ++++++++++++++++++++-------------------------------- 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index e542d15c..a89292d8 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1693,12 +1693,11 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 5.1 린터를 풍성하게 구성하고 린트 문제가 있는 빌드를 중단하십시오 :white_check_mark: **이렇게 해라:** 린터는 공짜 점심이며, 5분의 설정만으로 코드를 지켜주고 입력과 동시에 중요한 문제를 포착하는 자동 조종 장치를 거저 얻을 수 있습니다. 린터가 장식(세미콜론)에 지나지 않던 시대는 지나갔습니다. 요즘의 린터는 올바르게 throw되지 않고 정보가 손실되는 오류와 같은 심각한 문제를 포착 할 수 있습니다. 기본 규칙 세트 ([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) 는 resolve되지 않는 Promise를 발견해줍니다 (이런 코드는 계속해서 실행되는것이 불가능합니다), [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)는 코드가 Lodash._map(...)과 같은 V8 코어 메소드의 일부인 유틸리티 라이브러리 메소드를 사용할 때 경고해줍니다. -
    +
    ❌ **그렇지 않으면:** 프로덕션이 계속 깨지는데 로그에 에러의 stack trace가 표시되지 않는 우울한 날을 상상해봅시다. 어떻게 된 걸까요? 실수로 코드가 에러가 아닌 객체를 던지고 있어서 stack trace가 손실되었다면, 벽에 머리를 들이박기 딱 좋을것입니다. 5분의 린터 설정으로 이런 오타를 감지하고 하루를 지켜낼 수 있습니다. -
    코드 예제 @@ -1706,33 +1705,31 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    ### :thumbsdown: 올바르지 않은 예: 잘못된 Error 객체가 실수로 throw되어 이 오류에 대한 stack trace가 나타나지 않습니다. 운 좋게도 ESLint는 다음과 같은 프로덕션 버그를 잡아냅니다. + ![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "잘못된 Error 객체가 실수로 throw되어 이 오류에 대한 stack trace가 나타나지 않습니다. 운 좋게도 ESLint는 다음과 같은 프로덕션 버그를 잡아냅니다.")
    - - -

    -# ⚪ ️ 5.2 로컬 개발자 CI로 피드백 주기 단축 +## ⚪ ️ 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) 리팩터링. 피드백이 빠를수록 개발자가 모듈 각각을 개선하며 결과를 완벽하게 할 수 있습니다. 한편, 피드백이 늦어지면서 하루에 개선이 반복되는 빈도가 적어진다면, 팀은 이미 다른 주제 / 작업 / 모듈로 넘어갈 수 있으며 해당 모듈의 수정이 이루어지지 않을 수 있습니다. +: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 load CLI](https://circleci.com/docs/2.0/local-cli/)) 는 파이프라인의 로컬 실행을 허용합니다. [wallaby](https://wallabyjs.com/)와 같은 몇몇 상용 도구는 개발자 프로토타입으로 높은 가치의 테스트 통찰력을 제공합니다(협찬 아님). 또는 모든 품질 관련 명령어(예 : test, lint, vulnerabilities)를 실행하는 npm 스크립트를 package.json에 추가 할 수 있습니다. 병렬화를 위해 [concurrently](https://www.npmjs.com/package/concurrently)와 같은 도구를 사용하고 명령어 중 하나가 실패할 경우에는 0이 아닌 종료 코드를 사용하십시오. 이제 개발자는 하나의 명령을 호출해야 합니다. ‘npm run quality’— 즉각적인 피드백을 받습니다. githook을 사용하여 품질 검사에 실패한 경우 커밋을 중단하는 것도 고려하십시오 ([husky가 도움될 수 있음](https://github.com/typicode/husky)) -
    +
    ❌ **그렇지 않으면:** 코드를 작성한 다음 날에 품질 결과가 도착한다면 테스트는 개발 과정에 자연스럽게 포함될 수 없습니다 -
    예제 코드
    -### :clap: 올바른 예: 코드 품질 검사를 수행하는 npm 스크립트는 요청 시 또는 개발자가 새 코드를 푸시하려고 할 때 모두 병렬로 실행됩니다. +### :clap: 올바른 예: 코드 품질 검사를 수행하는 npm 스크립트는 요청 시 또는 개발자가 새 코드를 푸시하려고 할 때 모두 병렬로 실행됩니다. + ```javascript "scripts": { "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", @@ -1754,103 +1751,89 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    - - -

    -# ⚪ ️5.3 Perform e2e testing over a true production-mirror +## ⚪ ️ 5.3 실제 프로덕션 미러를 통해 e2e 테스트 수행 -:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. +:white_check_mark: **이렇게 해라:** e2e 테스트는 모든 CI 파이프라인의 주요 과제입니다. 모든 관련 클라우드 서비스가 동일한 임시 프로덕션 미러를 즉석에서 생성하는 것은 지루하고 비용이 많이 들 수 있습니다. 최고의 타협점을 찾는것이 게임입니다. [Docker-compose](https://serverless.com)를 사용하면 단일 텍스트 파일로 동일한 컨테이너로 격리된 도커 환경을 만들 수 있지만 백업 기술(예: 네트워킹, 배포 모델)은 실제 프로덕션과 다릅니다. 실제 AWS 서비스 기능과 함께 동작하게 하기 위해 이를 ['AWS Local'](https://github.com/localstack/localstack)과 결합할 수 있습니다. [Serverless](https://serverless.com)와 Serverless같은 여러 프레임워크를 사용하는 경우 [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html)을 이용해 로컬에서 FaaS 코드를 호출할 수 있습니다. -The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. -
    +거대한 쿠버네티스 생태계는 많은 새로운 도구가 자주 나오지만, 아직 로컬 및 CI 미러링을 위한 편리한 도구의 표준을 공식화하지 않았습니다. 한가지 방법은 실제와 비슷하지만 오버헤드가 적은 [Minikube](https://kubernetes.io/docs/setup/minikube) 및 [MicroK8s](https://microk8s.io)과 같은 도구를 사용하여 '최소화된 쿠버네티스'를 실행하는 것입니다. 또 다른 방법은 원격 '실제 쿠버네티스'를 테스트하는 것입니다. 일부 CI 벤더(예: [Codefresh](https://codefresh.io)은 쿠버네티스 환경과 통합되어 있어서 실제 상황에서 CI 파이프라인을 쉽게 실행할 수 있으며, 또 원격 쿠버네티스에 대한 사용자 지정 스크립팅을 허용합니다. +
    -❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated - +❌ **그렇지 않으면:** 프로덕션 및 테스트에 서로 다른 기술을 사용하면 두 가지 배포 모델의 관리가 필요하고 개발자와 운영 팀이 분리됩니다.
    -
    Code Examples +
    예제 코드
    -### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([Credit: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) +### :clap: 올바른 예: 쿠버네티스 클러스터를 즉시 생성하는 CI 파이프라인 ([동적 환경 쿠버네티스](https://blog.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 Parallelize test execution -:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes - +## ⚪ ️ 5.4 테스트 실행의 병렬화 -❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant +:white_check_mark: **이렇게 해라:** 제대로만 하면 테스트는 거의 즉각적인 피드백을 주는 24/7 친구입니다. 실제로 단일 스레드에서 500개의 CPU 제한 단위 테스트를 실행하는데는 시간이 오래 걸릴 수 있습니다. 운좋게도 최신 테스트 러너와 CI 플랫폼(예: [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava), [Mocha extension](https://github.com/yandex/mocha-parallel-tests))은 테스트를 여러 프로세스로 병렬화하여 피드백 시간을 크게 개선할 수 있습니다. 일부 CI 벤더는 테스트를 컨테이너 간 병렬화하여(!) 피드백 시간을 더욱 단축시킵니다. 각각 다른 프로세스에서 테스트를 실행(로컬에서 여러 프로세스로 혹은 여러 머신을 사용하는 일부 클라우드 CLI를 통해)하여 테스트를 자동화 하십시오. +❌ **그렇지 않으면:** 새 코드를 푸쉬하고 이미 다음 기능을 코딩 할 때, (한 시간 후에) 테스트 결과를 얻는 것은 테스트의 관련성을 떨어뜨리기 위한 훌륭한 방법입니다.
    -
    Code Examples +
    예제 코드
    -### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :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 Stay away from legal issues using license and plagiarism check -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights +## ⚪ ️ 5.5 라이센스 및 표절을 검사하여 법적 문제를 피하십시오. -❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues +:white_check_mark: **이렇게 해라:** 라이센싱과 표절 문제는 당장 당신의 주요 관심사가 아닐 수 있지만, 10분 안에 이 내용을 확인하지 않으시겠습니까? 많은 npm 패키지의 [라이센스 체크](https://www.npmjs.com/package/license-checker) 및 [표절 확인](https://www.npmjs.com/package/plagiarism-checker)(상용 도구의 무료 플랜)을 CI 파이프라인에 쉽게 포함시킬 수 있습니다. 그리고 제한적인 라이센스의 종속성이나 Stack Overflow에서 복사하여 붙여넣은 일부 저작권을 위반한 것으로 보이는 코드를 점검하십시오. +❌ **그렇지 않으면:** 개발자가 의도치 않게 부적절한 라이센스가 포함된 패키지를 사용하거나 상용 코드를 복사하여 법적 문제가 발생할 수 있습니다.
    -
    Code Examples +
    예제 코드
    -### :clap: Doing It Right Example: +### :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: **이렇게 해라:** 라이센싱과 표절 문제는 당장 당신의 주요 관심사가 아닐 수 있지만, 10분 안에 이 내용을 확인하지 않으시겠습니까? 많은 npm 패키지의 라이센스 및 표절 확인(상용 도구의 무료 플랜)을 CI 파이프라인에 쉽게 포함시킬 수 있습니다. 그리고 제한적인 라이센스의 종속성이나 Stack Overflow에서 복사하여 붙여넣은 일부 저작권을 위반한 것으로 보이는 코드를 점검하십시오. +:white_check_mark: **이렇게 해라:** Express와 같이 상당히 평판이 좋은 종속성 조차도 취약점이 있습니다. [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit)과 같은 커뮤니티 도구 또는 [snyk](https://snyk.io/)같은 상용 도구를 사용하여 쉽게 알 수 있습니다(무료 커뮤니티 버전도 제공). 둘다 당신의 CI의 모든 빌드에서 사용할 수 있습니다.
    -❌ **그렇지 않으면:** Express와 같이 상당히 평판이 좋은 종속성 조차도 취약점이 있습니다. [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit)과 같은 커뮤니티 도구 또는 [snyk](https://snyk.io/)같은 상용 도구를 사용하여 쉽게 알 수 있습니다(무료 커뮤니티 버전도 제공). 둘다 당신의 CI의 모든 빌드에서 사용할 수 있습니다. +❌ **그렇지 않으면:** 전용 도구 없이 코드를 취약점으로부터 안전하게 유지하려면 새로운 위협에 대한 최신 소식을 지속적으로 쫓아야 해서 매우 지루합니다.
    From 1b0c7e9c0fd7b924820adc2e1cdae38c023f14e6 Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Tue, 5 Nov 2019 01:11:07 +0900 Subject: [PATCH 114/502] Translate into Korean about team --- readme.kr.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index a89292d8..e80bca2f 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1769,7 +1769,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    -### :clap: 올바른 예: 쿠버네티스 클러스터를 즉시 생성하는 CI 파이프라인 ([동적 환경 쿠버네티스](https://blog.container-solutions.com/dynamic-environments-kubernetes) +### :clap: 올바른 예: 쿠버네티스 클러스터를 즉시 생성하는 CI 파이프라인 ([동적 환경 쿠버네티스](https://blog.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
    @@ -1919,21 +1919,24 @@ license-checker --summary --failOn BSD

    -# Team +# 팀 ## Yoni Goldberg
    + +
    -**Role:** Writer +**Role:** 저자 -**About:** I'm an independent consultant who works with 500 fortune corporates 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) +**About:** 저는 포춘 500대 기업 및 스타트업과 함께 JS 및 Node.js 어플리케이션을 개발하는 독립 컨설턴트입니다. 다른 어떤 주제보다 더 흥미를 끄는 테스트 기술을 습득하는 것을 목표로 합니다. 또한 [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices)의 저자이기도 합니다.
    -**Workshop:** 👨‍🏫 Want to learn all these practices and techniques at your offices (Europe & USA)? [Register here for my testing workshop](https://testjavascript.com/) +**Workshop:** 👨‍🏫 이러한 모든 프랙티스와 기술을 배우고 싶습니까?(유럽 & 미국) [테스트 워크샵에 등록하십시오](https://testjavascript.com) +
    **Follow:** @@ -1943,22 +1946,25 @@ license-checker --summary --failOn BSD * [✉️ Newsletter](https://testjavascript.com/newsletter//)
    +
    -
    +
    ## [Bruno Scheufler](https://github.com/BrunoScheufler) -**Role:** Tech reviewer and advisor +**Role:** 기술 검토 및 고문 -Took care to revise, improve, lint and polish all the texts +모든 텍스트를 수정, 개선, lint 및 다듬었습니다. + +**About:** 풀 스택 웹 엔지니어, Node.js 및 GraphQL의 열렬한 지지자 -**About:** full-stack web engineer, Node.js & GraphQL enthusiast
    +
    ## [Ido Richter](https://github.com/idori) -**Role:** Concept, design and great advice +**Role:** 컨셉, 디자인 및 훌륭한 조언 -**About:** A savvy frontend developer, CSS expert and emojis freak +**About:** 정통한 프론트 엔드 개발자, CSS 전문가 및 이모티콘에 관심이 많은 사람 From d3ec55d8400e2042e11a65640051b38b9fd1fa14 Mon Sep 17 00:00:00 2001 From: sury05 <37101565+sury05@users.noreply.github.com> Date: Tue, 5 Nov 2019 16:13:31 +0900 Subject: [PATCH 115/502] Translate 3.11 --- readme.kr.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index e80bca2f..8d6759a8 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1424,10 +1424,10 @@ Feature: Twitter new tweet -## ⚪ ️ 3.11 Detect visual issues with automated tools +## ⚪ ️ 3.11 자동화된 툴을 사용하여 시각적 문제(Visual Issues)를 감지해라. -:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS]([https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue +:white_check_mark: **이렇게 해라:** 컨테츠가 겹치거나 깨지는 등의 시각적 문제들이 감지될 때, UI 스크린 샷을 캡처하기 위해 자동화 도구를 셋업하세요. 이를 통해, 올바른 데이터가 준비 될 뿐만 아니라 사용자가 편리하게 변경을 확인할 수 있습니다. 이러한 기술은 현재 널리 채택되지는 않았습니다. 우리의 테스트 사고 방식은 여전히 기능 테스트에 의존하지만 사용자가 실제로 경험하는 것은 시각적 요소이며 다양한 디바이스 유형때문에 일부 UI 버그들은 간과되기 쉽습니다. 일부 무료 툴들은 육안 검사를 위한 스크린 샷을 생성하거나 저장하는 기능과 같은 기본적인 기능들을 제공합니다. 이 방법은 작은 크기의 App에는 충분하지만, 변경이 발생할 때마다 사람의 손길이 필요한 다른 수동 테스트를 수행하기에는 제약이 있습니다. 반면에, 명확한 정의가 없기 때문에 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의 근본 원인을 분석하는 고급 기능들을 제공합니다.
    @@ -1436,7 +1436,7 @@ Feature: Twitter new tweet
    -
    Code Examples +
    코드 예제
    From 15e57efb2abfc413d474257119534d5eace81c1e Mon Sep 17 00:00:00 2001 From: Rain Byun Date: Tue, 5 Nov 2019 23:35:59 +0900 Subject: [PATCH 116/502] Translate into Korean 3.7 to 3.8 and 4.3 to 4.4 --- readme.kr.md | 79 ++++++++++++++++++++++------------------------------ 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 8d6759a8..3a216e03 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1284,31 +1284,31 @@ test('When no products exist, show the appropriate message', () => {
    -## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system +## ⚪ ️ 3.7 전체 시스템에 걸친 엔드-투-엔드 테스트가 거의 없습니다. -:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +:white_check_mark: **이렇게 해라:** E2E(end-to-end)는 일반적으로 실제 브라우저를 사용한 UI를 위한 테스트를 의미하지만(3.6 참고), 다른 의미로 실제 백엔드를 포함하여 전체 시스템을 확장하는 테스트를 의미합니다. 후자의 테스트 유형은 교환 스키마에 대한 잘못된 이해로 인해 발생할 수 있는 프론트엔드와 백엔드간의 통합 버그를 커버하기 때문에 상당히 유용합니다. 또한 백엔드간 통합 문제(예 : 마이크로서비스 A가 마이크로서비스 B에 잘못된 메시지를 보낸다)를 발견하고 배포 실패를 감지하는 효과적인 방법입니다. [Cypress](https://www.cypress.io)와 [Pupeteer](https://github.com/GoogleChrome/puppeteer)같은 UI 프레임워크만큼 친숙하고 성숙한 E2E 테스트 백엔드 프레임워크는 없습니다. 이러한 테스트의 단점은 구성 요소가 많은 환경을 구성하는 데 드는 높은 비용과 주로 불안정성 입니다 - 50개의 마이크로서비스가 제공되는데, 하나가 실패하더라도 전체 E2E가 실패. 따라서 이 기법을 적절히 사용해야 하며, 그 중 1~10개 정도만 사용해야 합니다. 즉, 소수의 E2E 테스트 일지라도 배포 및 통합 오류와 같은 유형의 문제를 잡을 수 있습니다. 프로덕션과 같은 스테이징 환경에서 실행하는 것이 좋습니다.
    -❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very differnt than expected +❌ **그렇지 않으면:** UI는 백엔드에서 리턴하는 페이로드가 예상과 매우 다르다는 것을 알아차리기 위하여 기능 테스트에 많은 투자를 할 수 있습니다.
    -## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials +## ⚪ ️ 3.8 로그인 자격 증명을 재사용하여 E2E 테스트 속도 향상 -:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). +:white_check_mark: **이렇게 해라:** 실제 백엔드를 포함하고 API 호출이 가능한 사용자 토큰을 사용하는 E2E 테스트에서, 모든 요청에서 사용자가 생성되고 로그인이 되는 수준으로 테스트를 격리하는 것은 아닙니다. 대신 테스트가 시작되기 전에 한 번만 로그인하고(즉, before-all), 토큰을 로컬 저장소에 저장해서 여러 요청에 재사용하십시오. 이것은 핵심 테스트 원칙 중 하나를 위반하는 것 같습니다 - 리소스 커플링 없이 테스트를 자율적으로 유지하십시오. 이것은 우려할 만 하지만 E2E 테스트에서 성능은 핵심 관심사이며, 개별 테스트를 시작하기 전에 매번 1~3개의 API 요청을 보내게 되면 실행 시간이 끔직해질 수 있습니다. 자격 증명을 재사용한다고해서 테스트가 동일한 사용자 레코드에 대해 수행되어야 한다는 의미는 아닙니다. 사용자 레코드(예: 테스트 유저 결제 내역)에 의존하는 경우 해당 레코드를 테스트의 일부로 생성하고 다른 테스트와의 공유를 피하십시오. 또한 백엔드가 위조될 수 있음을 기억하십시오 - 테스트가 프론트엔드에 중점을 둔 경우, 테스트를 분리하고 백엔드 API를 스텁하는 것이 좋습니다.(3.6 참고)
    -❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again +❌ **그렇지 않으면:** 200개의 테스트 케이스가 주어졌고 로그인에 100ms가 소요된다고 가정하면 매번 로그인에만 20초가 소요된다.
    -
    Code Examples +
    예제 코드
    -### :clap: Doing It Right Example: Logging-in before-all and not before-each +### :clap: 올바른 예: before-each가 아닌 before-all에 로그인 ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") @@ -1316,7 +1316,7 @@ test('When no products exist, show the appropriate message', () => { ```javascript let authenticationToken; -// happens before ALL tests run +// 모든 테스트가 실행되기 전에 발생 before(() => { cy.request('POST', 'http://localhost:3000/login', { username: Cypress.env('username'), @@ -1328,7 +1328,7 @@ before(() => { }) }) -// happens before EACH test +// 각 테스트 전에 발생 beforeEach(setUser => () { cy.visit('/home', { onBeforeLoad (win) { @@ -1336,14 +1336,10 @@ beforeEach(setUser => () { }, }) }) - ```
    - - -
    ## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map @@ -1557,6 +1553,7 @@ cy.eyesCheckWindow('mark as completed');
    ### :clap: 예제: 일반적인 커버리지 보고서 + ![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report")
    @@ -1570,8 +1567,6 @@ cy.eyesCheckWindow('mark as completed');
    - -

    ## ⚪ ️ 4.2 커버리지 리포트를 확인하여 테스트 되지 않은 부분과 기타 이상한 점들을 감지하십시오. @@ -1579,7 +1574,6 @@ cy.eyesCheckWindow('mark as completed'); :white_check_mark: **이렇게 해라:** 일부 문제들은 레이더망 아래로 숨어버려 기존의 툴들을 사용하여 찾기 매우 어렵습니다. 이것들은 실제로 버그는 아니지만 심각한 영향을 줄 수 있는 생각지 못 한 어플리케이션 동작들입니다. 예를 들어, 일부 코드 영역은 절대 또는 거의 호출되지 않습니다. - ‘PricingCalculator’라는 상품 가격을 설정하는 클래스가 있다고 생각해 보세요. DB에 100000개의 상품이 있고 판매도 많지만 이 클래스는 실제로 절대 호출되지 않는 것으로 밝혀졌습니다... 코드 커버리지 리포트를 통해 어플리케이션이 당신이 원하는 대로 동작하는지 확인할 수 있습니다. 그 외에도 리포트는 어떤 코드들이 테스트되지 않았는지를 강조해서 보여줄 수도 있습니다. - 코드의 80%가 테스트 되었다는 알림이 중요한 부분이 커버되었는지에 대한 여부를 나타내진 않습니다. 리포트를 만드는 것은 쉽습니다. - 운영 또는 테스트를 할 때 커버리지 트래킹을 하면서 어플리케이션을 실행하세요. 그러고 나서 각 코드 영역이 얼마나 자주 호출됐는지를 나타내는 형형색색의 리포트를 보세요. 잠깐 시간을 내서 이 데이터들을 보면 몇 가지 문제점들을 발견하게 될 수도 있습니다.
    - ❌ **그렇지 않으면:** 어떤 코드가 테스트되지 않았는지 알 수 없으면 문제의 원인도 알 수 없습니다.
    @@ -1590,40 +1584,39 @@ cy.eyesCheckWindow('mark as completed'); ### :thumbsdown: 올바르지 않은 예: 이 커버리지 리포트에는 어떤 문제가 있나요? 현실 세계 시나리오로 QA에서 어플리케이션 사용을 추적했고 흥미로운 로그인 패턴을 찾았습니다. (힌트: 로그인 실패 횟수가 비례하지 않습니다. 분명히 무언가 잘못되었습니다.) 마침내 일부 프론트엔드 버그가 백엔드 로그인 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)
    -

    -## ⚪ ️ 4.3 Measure logical coverage using mutation testing +## ⚪ ️ 4.3 mutation 테스트를 사용하여 논리적인 범위를 측정 -:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +:white_check_mark: **이렇게 해라:** 전통적인 커버리지 측정은 거짓말쟁이: 100%의 코드 커버리지를 표시할 수 있지만, 함수 중 올바른 응답을 반환하는 기능은 없습니다. 심지어 하나도. 어찌하여? 테스트가 방문한 코드 라인을 단순하게 측정하지만, 테스트에서 실제로 테스트(올바른 응답을 assertion) 한 것이 있는지 확인하지는 않습니다. 출장을 위해 여행하고 여권 스템프를 보여주는 사람처럼 - 이것은 단지 공항과 호텔을 방문했을 뿐, 일을 했는지 어떤 것도 증명하지 못한다. -Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: +mutation 기반의 테스트는 단순한 '방문'이 아닌 실제로 테스트 '된' 코드의 양을 측정하는데 도움이 됩니다. [Stryker](https://stryker-mutator.io)는 mutation 테스트를 위한 JavaScript 라이브러리이며 구현이 정말 깔끔합니다: -(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations +(1) 의도적으로 코드를 변경하고 "버그를 심습니다". 예를 들면, newOrder.price === 0 는 newOrder.price != 0이 됩니다. 이 "버그"를 mutation이라고 합니다. -(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. +(2) 모든 테스트가 성공하면 우리는 문제가 있다 - 테스트는 버그를 발견하는 목적을 달성하지 못했고, mutation은 살아남았다. 테스트가 실패하면 엄청난 mutation이 죽었다. -Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar -
    +모든 혹은 대부분의 mutation이 죽었다는 것을 알면 전통적인 커버리지보다 훨씬 더 높은 신뢰를 얻을 수 있으며 구성 시간은 비슷합니다. +
    -❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code +❌ **그렇지 않으면:** 85%의 커버리지는 테스트에서 코드의 85%에서 버그를 감지한다는 의미입니다.
    -
    Code Examples +
    예제 코드
    -### :thumbsdown: Anti Pattern Example: 100% coverage, 0% testing +### :thumbsdown: 올바르지 않은 예: 100% 커버리지, 0% 테스트 ![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg - "Using Cypress to illustrate the idea") +"Using Cypress to illustrate the idea") + ```javascript function addNewOrder(newOrder) { logger.log(`Adding new order ${newOrder}`); @@ -1635,37 +1628,34 @@ function addNewOrder(newOrder) { it("Test addNewOrder, don't use such test names", () => { addNewOrder({asignee: "John@mailer.com",price: 120}); -});//Triggers 100% code coverage, but it doesn't check anything - +}); // 100% 커버리지가 나오지만 아무것도 확인하지 않습니다. ``` +
    -### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :clap: 올바른 예: mutation 테스트 도구인 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/bp-20-yoni-goldberg-mutation-testing.jpeg "mutation 테스트 도구인 Stryker 보고서는 테스트 되지 않은 코드의 양을 감지하고 계산합니다.")
    - -

    -## ⚪ ️4.4 Preventing test code issues with Test linters +## ⚪ ️ 4.4 테스트 린터로 테스트 코드 문제 방지 -:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +:white_check_mark: **이렇게 해라:** ESLint 플러그인 세트는 테스트 코드 패턴을 검사하고 문제를 발견하기 위해 특별히 제작되었습니다. 예를 들어 [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha)는 테스트가 글로벌 수준에서 작성될 때(describe() 문 아래에 있지 않음) 또는 테스트를 건너 뛰고 모든 테스트가 통과되었다는 잘못된 믿음을 가질 때 경고합니다. 유사하게, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest)는 예를 들어 테스트에 아무런 assertion이 없을 때 경고합니다.(아무것도 확인하지 않음)
    - -❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation - +❌ **그렇지 않으면:** 90%의 코드 커버리지와 100%의 녹색 테스트를 보며 미소짓는 것은 많은 테스트가 아무것도 assertion하지 않고 많은 테스트 스위트가 건너 뛰어진다는 것을 알 때 까지만입니다. 이 잘못된 결과를 바탕으로 어떤 것도 배포하지 않았기를 바랍니다.
    -
    Code Examples + +
    예제 코드
    -### :thumbsdown: Anti Pattern Example: A test case full of errors, luckily all are caught by Linters +### :thumbsdown: 올바르지 않은 예: 오류로 가득 찬 테스트 케이스, 운 좋게도 린터가 잡았습니다. ```javascript describe("Too short description", () => { @@ -1684,7 +1674,6 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test


    - # Section 5️⃣: 지속적인 통합 @@ -1883,7 +1872,7 @@ license-checker --summary --failOn BSD 1. 선언적 구문을 사용하십시오. 대부분의 벤더에서는 선택할 수 없지만, 이전 버전의 Jenkins에서 코드 또는 UI를 사용할 수 있습니다. 2. 고유 Docker를 지원하는 벤더를 선택하십시오. -3. 일찍 실패하고 가장 빠른 테스트를 먼저 실행하십시오. 다양한 빠른 Inspection(예: 린트, 단위테스트)를 그룹화 하고 코드 커미터에 대한 신속한 피드백을 제공할 수 잇는 [스모크 테스트](https://terms.naver.com/entry.nhn?docId=864587&cid=42346&categoryId=42346) 단계/마일스톤을 만드십시오. +3. 일찍 실패하고 가장 빠른 테스트를 먼저 실행하십시오. 다양한 빠른 Inspection(예: 린트, 단위테스트)를 그룹화 하고 코드 커미터에 대한 신속한 피드백을 제공할 수 있는 [스모크 테스트](https://terms.naver.com/entry.nhn?docId=864587&cid=42346&categoryId=42346) 단계/마일스톤을 만드십시오. 4. 테스트 보고서, 커버리지, 변화, 로그 등의 모든 결과물을 훑어보기 쉽게 하십시오. 5. 각 이벤트에 대해 여러 파이프라인/작업을 작성하고, 그 사이 단계를 재사용 하십시오. 예를 들면, feature 브랜치 커밋이나 마스터 PR에 대한 작업 구성. 각 재사용 로직이 공유 단계를 사용하게 하십시오.(대부분의 벤더는 코드 재사용을 위한 메커니즘을 제공합니다) 6. 작업 선언에 어떤한것도 숨겨놓지 마십시오. From ca9758fd5ad9b10c011bc4164459b3ba80996622 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 6 Nov 2019 12:23:16 +0900 Subject: [PATCH 117/502] 3.5 eng to kor --- readme.kr.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 3a216e03..e63f565c 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1201,22 +1201,22 @@ test('movie title appears', async () => {
    -## ⚪ ️ 3.5. Watch how the content is served over the network +## ⚪ ️ 3.5. 화면의 내용이 네트워크를 통해 어떻게 제공될지 확인하십시오 ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") -✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 +✅ **이렇게 해라:** 네트워크를 점검하고 페이지로드 상태를 확인 할 수 있는 네트워크 액티브 모니터를 적용하십시오. [pingdom](https://www.pingdom.com/), AWS CloudWatch, [gcp StackDriver](https://cloud.google.com/monitoring/uptime-checks/) 를 사용하면 쉽게 서버의 상태와 response 내용을 모니터링 하도록 설정 할 수 있고, 화면에서의 오류는 최소화 하면서 테스트를 진행 할 수 있습니다. 이들은 프론트엔드에 최적화 된 다른 도구([lighthouse](https://developers.google.com/speed/pagespeed/insights/), [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), [화면이 인터렉티브 하게 되는 시간](https://calibreapp.com/blog/time-to-interactive/)과 같이 화면에 직접적으로 영향을 주는 항목이나 상태에 중점을 둬야 합니다. 무엇보다 contents가 압축 되었는지, 첫 byte 응답까지의 시간, image 최적화, 적절한 DOM 의 크기 파악, SSL 등의 기술적 이슈에 중점을 둬야 합니다. 이런 항목에 대한 모니터링은 개발시점이나, CI, 24x7 운영 중 수행할 것을 권장 합니다.
    -❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration +❌ **그렇지 않으면:** 완벽한 UI 를 개발해 두고, 100% 기능 테스트까지 완료 했지만, 최악의 UX를 제공하게 되거나 CDN의 설정 오류 등의 이유 때문에 너무 느린 서비스를 제공하게 될 수 있습니다.
    -
    Code Examples +
    코드 예제 -### :clap: Doing It Right Example: Lighthouse page load inspection report +### :clap: 올바른 예: Lighthouse 페이지 로딩 검사 보고서 ![](/assets/lighthouse2.png "Lighthouse page load inspection report") From 2d3776b79590682fbc5f77a35b50b423e20ed26b Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 6 Nov 2019 12:28:53 +0900 Subject: [PATCH 118/502] 3.6 eng to kor --- readme.kr.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 3a216e03..8fbde2de 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1226,22 +1226,22 @@ test('movie title appears', async () => {
    -## ⚪ ️ 3.6 Stub flakky and slow resources like backend APIs +## ⚪ ️ 3.6 백엔드 API와 같이 자주 멈출 수 있거나 느린 리소스는 stub 하십시오. -:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +:white_check_mark: **이렇게 해라:** 주요 기능에 대한 테스트 코드(E2E테스트 아님) 작성 시, backend API 처럼 그 기능의 주 역할에서 벗어나는 항목은 제외할 수 있도록 stub(예: test double) 하십시오. 실제 네트워크를 호출하지 말고, test double 라이브러리([Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble))를 사용하십시오. 이 경우 가장 큰 장점은 예상치 못한 테스트 실패를 예방 할 수 있습니다. 코드단에서 API 정의에 따라 test를 적용하는 작업은 안정적이지 않고 당신의 component는 문제가 없지만 수시로 test가 실패 할 수 있습니다.(운영 상태의 env 설정은 testing에서는 사용하지 마세요. API 요청에 병목이 발생 할 수 있습니다.) 이런식으로 해서 API 응답 데이터가 없는 경우, 에러가 응답되는 경우 등의 다양한 API 상태에 따른 테스트를 진행 할 수 있습니다. 다시한번 강조하지만 실제 네트워크 호출은 test를 매우 느리게 만들것입니다.
    -❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower +❌ **그렇지 않으면:** 평균 테스트 실행 시간이 몇 ms 인 경우, API 호출로 개당 최소 100ms 시간이 걸리게 되고 테스트는 약 20배 느려지게 됩니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Stubbing or intercepting API calls +### :clap: 올바른 예: Stubbing 하거나 API 응답값 변조 ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with react-testing-library") @@ -1265,7 +1265,7 @@ export default function ProductsList() { } // test -test('When no products exist, show the appropriate message', () => { +test('products가 없는 경우, 적절한 메시지 표시하기', () => { // Arrange nock("api") .get(`/products`) From a013477436e45c846097ea6f862191d103e4b5ef Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 6 Nov 2019 16:51:20 +0900 Subject: [PATCH 119/502] Index link fix --- readme.kr.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index ef735216..562120ae 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -30,28 +30,28 @@ JavaScript 및 Node.js에 대한 A부터 Z까지의 믿음직한 가이드입니 ## `목차` -#### [`섹션 0: 황금률`](#section-0️⃣-the-golden-rule) +#### [`섹션 0: 황금률`](#섹션-0️⃣-황금률) 모든 모든 사람들에게 영감을 주는 하나의 조언(하나의 특수한 항목) -#### [`섹션 1: 테스트 해부`](#section-1-the-test-anatomy-1) +#### [`섹션 1: 테스트 해부`](#섹션-1-테스트-해부-1) 기초 - 깔끔한 테스트 구성하기(12개) -#### [`섹션 2: 백엔드`](#section-2️⃣-backend-testing) +#### [`섹션 2: 백엔드`](#섹션-2️⃣-백엔드-테스트) 백엔드 및 마이크로서비스 테스트 효율적으로 작성하기(8개) -#### [`섹션 3: 프론트엔드`](#section-3️⃣-frontend-testing) +#### [`섹션 3: 프론트엔드`](#섹션-3️⃣-프론트엔드-테스트) 컴포넌트 및 E2E 테스트를 포함한 웹 UI에 대한 테스트 작성하기(11개) -#### [`섹션 4: 테스트 효과 측정`](#section-4️⃣-measuring-test-effectiveness) +#### [`섹션 4: 테스트 효과 측정`](#섹션-4️⃣-테스트-효과-측정) 감시자를 감시하기 - 테스트 품질 측정(4개) -#### [`섹션 5: 지속적인 통합`](#section-5️⃣-ci-and-other-quality-measures) +#### [`섹션 5: 지속적인 통합`](#섹션-5️⃣-지속적인-통합) 자바스크립트 세계에서 CI에 대한 지침(9개) From 90637ce10541598585a4b6f65ddff8d7746f8054 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 6 Nov 2019 16:57:31 +0900 Subject: [PATCH 120/502] 3.9 eng to kor --- readme.kr.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 562120ae..0def9277 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1342,28 +1342,26 @@ beforeEach(setUser => () {
    -## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map - -:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector +## ⚪ ️ 3.9 사이트 전체 페이지를 테스트 할 수 있는 E2E smoke 테스트를 만드십시오 +:white_check_mark: **이렇게 해라:** 운영중인 서비스를 모니터링 하거나 개발중에도 전체 페이지를 점검할 수 있는 E2E 테스트를 실행하십시오. 이런 유형의 테스트는 간단히 작성하고 유지보수 할 수 있지만 그에 따른 효과는 거대합니다. 기능적이거나 네트워크, 개발 관련 이슈를 발견할 수 있습니다. 다른 종류의 테스트들은 E2E 만큼 신뢰할 수는 없고 완벽하지 않습니다.(일부 운영팀에서는 단순히 운영 서버로 ping을 하고있고, 개발자들이 단순 기능 테스트만을 실행하여 패키징이나 브라우져 관련 이슈는 확인 할 수 없습니다.) smoke 테스트는 기능 테스트를 대체하기 보다는 smoke 발견을 위한 도구로 볼수 있습니다.
    -❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering - +❌ **그렇지 않으면:** 모든 테스트도 패스하고 서비스 핼스체크도 정상이라 모든것이 완벽해 보일수 있지만, Payment component가 패키징 이슈가 있어서 페이지 이동 시 화면 랜더링이 안될 수 있습니다.
    -
    Code Examples +
    코드 예제
    -### :clap: Doing It Right Example: Smoke travelling across all pages +### :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', () => { - // exemplified using Cypress but can be implemented easily - // using any E2E suite +it('모든 페이지를 smoke 테스트 할때, 페이지들이 정상적으로 로드 되어야 한다', () => { + // Cypress를 이용한 예제 입니다 + // 다른 E2E 도구로도 쉽게 구현이 가능합니다 cy.visit('https://mysite.com/home'); cy.contains('Home'); cy.contains('https://mysite.com/Login'); From 8db2ecda59066d52959b3b9df1f39b6d029b116b Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 6 Nov 2019 18:56:03 +0900 Subject: [PATCH 121/502] 3.10 eng to kor --- readme.kr.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 0def9277..dbcd820d 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1376,24 +1376,23 @@ it('모든 페이지를 smoke 테스트 할때, 페이지들이 정상적으로
    -## ⚪ ️ 3.10 Expose the tests as a live collaborative document +## ⚪ ️ 3.10 다같이 협업가능한 문서로 테스트 내용을 내보내기 하십시오 -:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. - -❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value +:white_check_mark: **이렇게 해라:** App의 신뢰도를 높일 수 있고, 테스트를 통해 또 다른 개선의 기회가 될 수 있습니다. 테스트 내용은 덜 기술적이면서 제품/UX와 관련된 표현으로 되어 있어서, 다양한 협업자들(개발자, 고객 등)간에 의사소통 수단으로 사용될 수 있습니다. 예를 들어, 어떤 프레임워크들은 비즈니스 흐름과 예상되는 결과들을 누구나(스테이크홀더, PM) 이해할 수 있는 언어로 표현하여 같이 확인하고 협업 할 수 있게 도움을 주는 필수적인 문서가 될 수 있습니다. 고객은 자신의 인수조건을 같이 정의해 나가면서 테스트를 작성하고 이것은 결국 ‘인수 테스트’ 가 됩니다. 이게 바로 [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) 입니다. 가장 유명한 프레임워크로는 [Cucumber(자바향)](https://github.com/cucumber/cucumber-js)가 있습니다. 아래의 예제를 참고하세요. 이와 비슷하면도 좀 다른 프레임워크로는 UI 각 컴포넌트의 다양한 상태별 실제 화면을 확인하고 어떤 경우 그런 상태가 렌더링 되는지 확인 할 수 있는 [StoryBook](https://storybook.js.org/) 이 있습니다.(예. Grid를 데이터가 있는 경우와 없는 경우, 필터된 경우로 렌더링해 볼 수 있습니다.) 이런 것들은 제품 관계자에게 매력적일 수도 있지만 보통 개발자들에게 개발 문서 처럼 사용됩니다. +❌ **그렇지 않으면:** 테스트 작성을 위해 많은 공수를 들였지만 결과적으로 위와 같은 장점을 놓치지 됩니다.
    -
    Code Examples +
    코드 여제
    -### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js +### :clap: 올바른 예: cucumber-js 의 humnan 언어를 사용한 테스트 코드 ![](https://img.shields.io/badge/🔨%20Example%20using%20Cocumber-blue.svg "Examples using Cucumber") ```javascript -// this is how one can describe tests using cucumber: plain language that allows anyone to understand and collaborate +// Cucumber 를 사용하여 테스트를 설명: 평문을 사용하여 누구든 이해하고 협업 할 수 있다 Feature: Twitter new tweet @@ -1409,7 +1408,8 @@ Feature: Twitter new tweet ``` -### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook +### :clap: 올바른 예: Storybook을 이용한 components 상태, 입력 값별 visualizing + ![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") From 7102658875ba23871dc088fb144a3173340f822b Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 6 Nov 2019 18:59:43 +0900 Subject: [PATCH 122/502] Correct typo --- readme.kr.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index dbcd820d..08870d6b 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1304,7 +1304,7 @@ test('products가 없는 경우, 적절한 메시지 표시하기', () => {
    -
    예제 코드 +
    코드 예제
    @@ -1384,7 +1384,7 @@ it('모든 페이지를 smoke 테스트 할때, 페이지들이 정상적으로
    -
    코드 여제 +
    코드 예제
    From 6d808d257e3d22f11689b067cf4176df0760b4de Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 7 Nov 2019 08:56:04 +0900 Subject: [PATCH 123/502] Section to kor --- readme.kr.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 08870d6b..65205a2e 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -1530,7 +1530,7 @@ cy.eyesCheckWindow('mark as completed');

    -# Section 4️⃣: 테스트 효과 측정 +# 섹션 4️⃣: 테스트 효과 측정

    @@ -1673,7 +1673,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# Section 5️⃣: 지속적인 통합 +# 섹션 5️⃣: 지속적인 통합

    From 4943f41c9cbb019e1b55605c891ce72ff4891ff0 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2019 05:27:25 +0000 Subject: [PATCH 124/502] docs: update readme.md From a2f02eb0c6c07c72fe91f541cf18486948ec0ecc Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2019 05:27:26 +0000 Subject: [PATCH 125/502] docs: update .all-contributorsrc --- .all-contributorsrc | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 12b02e18..ab2a67e4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1,8 +1,24 @@ { - "files": ["readme.md"], + "files": [ + "readme.md" + ], "imageSize": 100, "contributorsPerLine": 7, "badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square)](#contributors)", "skipCi": "true", - "contributors": [] -} \ No newline at end of file + "contributors": [ + { + "login": "stdavis", + "name": "Scott Davis", + "avatar_url": "https://avatars3.githubusercontent.com/u/1326248?v=4", + "profile": "http://geospatialscott.blogspot.com/", + "contributions": [ + "content" + ] + } + ], + "projectName": "javascript-testing-best-practices", + "projectOwner": "goldbergyoni", + "repoType": "github", + "repoHost": "https://github.com" +} From d0135dda540a0b643f8c0da609a49323f59440cd Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 17 Nov 2019 21:47:19 +0200 Subject: [PATCH 126/502] Update readme.md --- readme.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/readme.md b/readme.md index a147e0d1..bd0f5652 100644 --- a/readme.md +++ b/readme.md @@ -26,6 +26,13 @@ Start by understanding the ubiquitous testing practices that are the foundation * Come hear me speak at [LA](https://js.la/), [Verona](https://2019.nodejsday.it/), [Kharkiv](https://kharkivjs.org/), [free webinar](https://zoom.us/webinar/register/1015657064375/WN_Lzvnuv4oQJOYey2jXNqX6A). Future events TBD * [My JavaScript Quality newsletter](https://testjavascript.com/newsletter/) - insights and content only on strategic matters +
    + +### Translations - read in your own language +* 🇨🇳[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) +* Want to translate to your own language? please open an issue 💜 +

    From ad2390dd82d41d8f24bb33e865b62d4c15811e2f Mon Sep 17 00:00:00 2001 From: DouglasMV Date: Tue, 19 Nov 2019 18:49:06 -0300 Subject: [PATCH 127/502] Translated Section 3 --- readme-pt-br.md | 184 ++++++++++++++++++++++++------------------------ 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/readme-pt-br.md b/readme-pt-br.md index 0b6c8bc2..35e953d1 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -100,7 +100,7 @@ A maioria dos conselhos abaixo são derivados desse princípio.
    -❌ **De outra forma:** Uma implantação acabou de falhar, um teste chamado "Adicionar produto" falhou. Isso diz o que exatamente está com defeito? +❌ **Caso contrário:** Uma implantação acabou de falhar, um teste chamado "Adicionar produto" falhou. Isso diz o que exatamente está com defeito?
    @@ -150,7 +150,7 @@ describe('Products Service', function() {
    -❌ **De outra forma:** Você não gata apenas longas horas diárias para entender o código principal, agora também o que deveria ter sido a parte simples do dia (teste) estica seu cérebro +❌ **Caso contrário:** Você não gata apenas longas horas diárias para entender o código principal, agora também o que deveria ter sido a parte simples do dia (teste) estica seu cérebro
    @@ -962,29 +962,29 @@ it("When updating site name, get successful confirmation", async () => {

    -# Section 3️⃣: Frontend Testing +# Seção 3️⃣: Teste de Frontend -## ⚪ ️ 3.1. Separate UI from functionality +## ⚪ ️ 3.1. Separar UI da funcionalidade -:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI +:white_check_mark: **Faça:** Ao focar no teste da lógica dos componentes, os detalhes da interface do usuário se tornam um ruído que deve ser extraído, para que seus testes possam se concentrar em dados puros. Na prática, extraia os dados desejados da marcação de uma maneira abstrata que não seja muito acoplada à implementação gráfica, afirme apenas dados puros (vs detalhes gráficos de HTML/CSS) e desative animações que diminuem a velocidade. Você pode cair na tentação de evitar renderizar e testar apenas a parte de trás da interface do usuário (por exemplo, serviços, ações, armazenamento), mas isso resultará em testes fictícios que não se assemelham à realidade e não revelam casos em que os dados corretos nem chegam na interface do usuário
    -❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation +❌ **Caso contrário:** Os dados puramente calculados do seu teste podem estar prontos em 10 ms, mas o teste inteiro durará 500 ms (100 testes = 1 min) devido a alguma animação sofisticada e irrelevante
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Separating out the UI details +### :clap: Exemplo Fazendo Certo: Separando os detalhes da interface do usuário -![](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") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React-blue.svg + "Exemplos com React") ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React%20Testing%20Library-blue.svg + "Exemplos com react-testing-library") ```javascript test('When users-list is flagged to show only VIP, should display only VIP members', () => { @@ -1007,7 +1007,7 @@ test('When users-list is flagged to show only VIP, should display only VIP membe
    -### :thumbsdown: Anti Pattern Example: Assertion mix UI details and data +### :thumbsdown: Exemplo Anti-padrão: Asserções misturam detalhes da UI e dados ```javascript test('When flagging to show only VIP, should display only VIP members', () => { // Arrange @@ -1033,24 +1033,24 @@ test('When flagging to show only VIP, should display only VIP members', () => {

    -## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change +## ⚪ ️ 3.2 Consultar elementos HTML com base em atributos que provavelmente não serão alterados -:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +:white_check_mark: **Faça*:** Consulte elementos HTML com base em atributos que provavelmente sobreviverão a alterações gráficas, diferentemente dos seletores CSS e sim como os rótulos de formulário. Se o elemento designado não tiver esses atributos, crie um atributo dedicado a teste como 'test-id-submit-button'. Seguir essa rota não apenas garante que seus testes funcionais/lógicos nunca sejam quebrados devido a alterações de aparência, mas também fica claro para toda a equipe que esse elemento e atributo são utilizados por testes e não devem ser removidos
    -❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **Caso contrário:** Você deseja testar a funcionalidade de login que abrange muitos componentes, lógica e serviços, tudo está configurado perfeitamente - stubs, spies, chamadas Ajax são isoladas. Tudo parece perfeito. Em seguida, o teste falha porque o designer alterou a classe CSS da div de 'thick-border' para 'thin-border'
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing +### :clap: Exemplo Fazendo Certo: Consultando um elemento usando um atributo dedicado para teste -![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg - "Examples with React") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React-blue.svg + "Exemplos com React") ```html // the markup code (part of React component) @@ -1077,7 +1077,7 @@ test('When flagging to show only VIP, should display only VIP members', () => {
    -### :thumbsdown: Anti-Pattern Example: Relying on CSS attributes +### :thumbsdown: Exemplo Anti-padrão: Confiando em atributos CSS ```html {value} @@ -1100,28 +1100,28 @@ test('Whenever no data is passed, error metric shows zero', () => {
    -## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component +## ⚪ ️ 3.3 Sempre que possível, teste com um componente realista e totalmente renderizado -:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +:white_check_mark: **Faça:** Sempre que tiver um tamanho razoável, teste seu componente de fora como os usuários, renderize a interface do usuário, atue sobre ela e afirme que a interface do usuário renderizada se comporta conforme o esperado. Evite todo tipo de simulação, renderização parcial e superficial - essa abordagem pode resultar em erros não capturados devido à falta de detalhes e dificultar a manutenção, pois os testes interferem nos internos (veja o marcador 'Favorecer o teste de caixa preta'). Se um dos componentes filhos estiver desacelerando significativamente (por exemplo, animação) ou complicando a instalação - considere substituí-lo explicitamente por um falso -With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children +Com tudo isso dito, uma palavra de cautela é necessária: essa técnica funciona para componentes pequenos/médios que contêm um tamanho razoável de componentes filhos. A renderização completa de um componente com muitos filhos dificultará o raciocínio sobre falhas de teste (análise de causa raiz) e poderá ficar muito lenta. Nesses casos, escreva apenas alguns testes contra esse componente pai pesado e mais testes contra seus filhos
    -❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? +❌ **Caso contrário:** Ao entrar no interno de um componente, invocando seus métodos privados e verificando o estado interno - você teria que refatorar todos os testes ao refatorar a implementação dos componentes. Você realmente tem capacidade para esse nível de manutenção?
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Working realstically with a fully rendered component +### :clap: Exemplo Fazendo Certo: Trabalhando realisticamente com um componente totalmente renderizado -![](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") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React-blue.svg + "Exemplos com React") ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Enzyme-blue.svg + "Exemplos com Enzyme") ```javascript class Calendar extends React.Component { @@ -1153,7 +1153,7 @@ test('Realistic approach: When clicked to show filters, filters are displayed', ``` -### :thumbsdown: Anti-Pattern Example: Mocking the reality with shallow rendering +### :thumbsdown: Exemplo Anti-padrão: Simulando a realidade com renderização superficial ```javascript test('Shallow/mocked approach: When clicked to show filters, filters are displayed', () => { @@ -1176,25 +1176,25 @@ test('Shallow/mocked approach: When clicked to show filters, filters are display
    -## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up +## ⚪ ️ 3.4 Não durma, use o suporte incorporado de frameworks para eventos assíncronos. Também tente acelerar as coisas -:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +:white_check_mark: **Faça:** Em muitos casos, o tempo de conclusão da unidade em teste é desconhecido (por exemplo, a animação suspende a aparência do elemento) - nesse caso, evite dormir (por exemplo, setTimeOut) e prefira métodos mais determinísticos que a maioria das plataformas fornece. Algumas bibliotecas permitem aguardar operações (por exemplo, [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), outras fornecem API para esperar como [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Às vezes, uma maneira mais elegante é esboçar o recurso lento, como a API, por exemplo, e depois que o momento da resposta se torna determinístico, o componente pode ser explicitamente renderizado novamente. Quando, dependendo de algum componente externo que dorme, pode ser útil [apressar o relógio](https://jestjs.io/docs/en/timer-mocks). Dormir é um padrão a ser evitado, porque força o teste a ser lento ou arriscado (ao esperar por um período muito curto). Sempre que dormir e pesquisar for inevitável e não há suporte do framework de teste, algumas bibliotecas do NPM, como [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) podem ajudar com uma solução semi-determinística
    -❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance +❌ **Caso contrário:** Ao dormir por um longo tempo, os testes serão uma ordem de magnitude mais lenta. Ao tentar dormir por pequenos números, o teste falha quando a unidade em teste não responde em tempo hábil. Portanto, tudo se resume a uma troca entre descamação e mau desempenho
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) +### :clap: Exemplo Fazendo Certo: API E2E que resolve somente quando as operações assíncronas são concluídas (Cypress) -![](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") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React-blue.svg + "Exemplos com React") ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React%20Testing%20Library-blue.svg + "Exemplos com react-testing-library") ```javascript // using Cypress @@ -1204,7 +1204,7 @@ cy.wait('@products')// wait for route to appear ``` -### :clap: Doing It Right Example: Testing library that waits for DOM elements +### :clap: Exemplo Fazendo Certo: Biblioteca de teste que aguarda elementos do DOM ```javascript // @testing-library/dom @@ -1222,7 +1222,7 @@ test('movie title appears', async () => { ``` -### :thumbsdown: Anti-Pattern Example: custom sleep code +### :thumbsdown: Exemplo Anti-padrão: código de suspensão personalizado ```javascript test('movie title appears', async () => { @@ -1249,24 +1249,24 @@ test('movie title appears', async () => {
    -## ⚪ ️ 3.5. Watch how the content is served over the network +## ⚪ ️ 3.5. Veja como o conteúdo é servido na rede -![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg - "Examples with Lighthouse") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Google%20LightHouse-blue.svg + "Exemplo com Lighthouse") -✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 +✅ **Faça:** Aplique um monitor ativo que garanta que o carregamento da página na rede real seja otimizado - isso inclui qualquer preocupação de UX, como carregamento lento da página ou pacote não minificado. O mercado de ferramentas de inspeção não é curto: ferramentas básicas como [pingdom](https://www.pingdom.com/), AWS CloudWatch, [gcp StackDriver](https://cloud.google.com/monitoring/uptime-checks/) podem ser facilmente configuradas para verificar se o servidor está ativo e responde sob um SLA razoável. Isso apenas arranha a superfície do que pode estar errado; portanto, é preferível optar por ferramentas especializadas em frontend(por exemplo, [lighthouse](https://developers.google.com/web/tools/lighthouse/), [pagespeed](https://developers.google.com/speed/pagespeed/insights/)) e realizar análises mais ricas. O foco deve estar nos sintomas, nas métricas que afetam diretamente o UX, como o tempo de carregamento da página, [meaningful paint](https://scotch.io/courses/10-web-performance-audit-tips-for-your-next-billion-users-in-2018/fmp-first-meaningful-paint), [ttempo até a página ficar interativa (TTI)](https://calibreapp.com/blog/time-to-interactive/). Além disso, também é possível observar causas técnicas, como garantir que o conteúdo seja compactado, tempo até o primeiro byte, otimizar imagens, garantir tamanho razoável de DOM, SSL e muitos outros. É aconselhável ter esses monitores avançados durante o desenvolvimento, como parte do CI e o mais importante - 24x7 nos servidores de produção/CDN
    -❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration +❌ **Caso contrário:** Deve ser decepcionante perceber que, após um cuidado tão grande na criação de uma interface do usuário, testes 100% funcionais passando e empacotamento sofisticado - o UX é horrível e lento devido à configuração incorreta da CDN
    -
    Code Examples +
    Códigos de Exemplo -### :clap: Doing It Right Example: Lighthouse page load inspection report +### :clap: Exemplo Fazendo Certo: Relatório de inspeção de carregamento de página do Lighthouse -![](/assets/lighthouse2.png "Lighthouse page load inspection report") +![](/assets/lighthouse2.png "Relatório de inspeção de carregamento de página do Lighthouse")
    @@ -1274,25 +1274,25 @@ test('movie title appears', async () => {
    -## ⚪ ️ 3.6 Stub flakky and slow resources like backend APIs +## ⚪ ️ 3.6 Esboce recursos escamosos e lentos, como APIs de backend -:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +:white_check_mark: **Faça:** Ao codificar seus testes convencionais (não os testes E2E), evite envolver qualquer recurso que esteja além de sua responsabilidade e controle como a API de backend e use esboços (por exemplo, teste duplo). Na prática, em vez de chamadas de rede reais para APIs, use alguma biblioteca de teste duplo (como [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) para esboçar a resposta da API. O principal benefício é evitar falhas - APIs de teste ou preparo, por definição, não são altamente estáveis ​​e, de tempos em tempos, serão reprovados em seus testes, embora SEU componente se comporte perfeitamente (o ambiente de produção não foi projetado para testes e geralmente limita as solicitações). Isso permitirá simular vários comportamentos da API que devem direcionar o comportamento do componente como quando nenhum dado foi encontrado ou o caso em que a API gera um erro. Por último, mas não menos importante, as chamadas de rede desacelerarão bastante os testes
    -❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower +❌ **Caso contrário:** Um teste médio é executado em não mais do que alguns ms, uma chamada típica da API dura 100ms>, isso torna cada teste ~ 20x mais lento
    -
    Code Examples +
    Códigos de Exemplo
    -### :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%20Jest-blue.svg - "Examples with react-testing-library") +### :clap: Exemplo Fazendo Certo: fazendo o esboço ou interceptando chamadas de API +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React-blue.svg + "Exemplos com React") ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Jest-blue.svg + "Exemplos com react-testing-library") ```javascript @@ -1332,34 +1332,34 @@ test('When no products exist, show the appropriate message', () => {
    -## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system +## ⚪ ️ 3.7 Tenha muito poucos testes de ponta a ponta que abrangem todo o sistema -:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +:white_check_mark: **Faça:** Embora o E2E (ponta a ponta) geralmente signifique testes apenas da interface do usuário com um navegador real (consulte o item 3.6), para outros, eles significam testes que abrangem todo o sistema, incluindo o backend real. O último tipo de teste é altamente valioso, pois cobre erros de integração entre frontend e backend que podem ocorrer devido a um entendimento incorreto do esquema de troca. Eles também são um método eficiente para descobrir problemas de integração de backend para backend (por exemplo, o microsserviço A envia a mensagem errada para o microsserviço B) e até mesmo para detectar falhas de implantação - não há estruturas de backend para testes E2E que sejam tão amigáveis ​​e maduras quanto as estruturas de interface do usuário, como [Cypress](https://www.cypress.io/) e [Pupeteer](https://github.com/GoogleChrome/puppeteer). A desvantagem de tais testes é o alto custo de configuração de um ambiente com tantos componentes e principalmente sua fragilidade - com 50 microsserviços, mesmo se um falhar, todo o E2E falhou. Por esse motivo, devemos usar essa técnica com moderação e provavelmente ter de 1 a 10 desses e não mais. Dito isto, mesmo um pequeno número de testes E2E provavelmente capturará o tipo de problemas para os quais eles são direcionados - falhas de implantação e integração. É aconselhável executá-las em um ambiente de preparação semelhante à produção
    -❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very differnt than expected +❌ **Caso contrário:** A interface do usuário pode investir muito em testar sua funcionalidade apenas para perceber muito tarde que a carga útil retornada pelo backend (o esquema de dados com o qual a interface do usuário precisa trabalhar) é muito diferente do esperado
    -## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials +## ⚪ ️ 3.8 Acelere os testes do E2E reutilizando credenciais de login -:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). +:white_check_mark: **Faça:** Nos testes E2E que envolvem um back-end real e dependem de um token de usuário válido para chamadas de API, não vale a pena isolar o teste para um nível em que um usuário é criado e conectado a cada solicitação. Em vez disso, efetue login apenas uma vez antes do início da execução dos testes (ou seja, antes de tudo), salve o token em algum armazenamento local e reutilize-o nas solicitações. Isso parece violar um dos principais princípios de teste - mantenha o teste autônomo sem acoplamento de recursos. Embora essa seja uma preocupação válida, nos testes E2E o desempenho é uma preocupação importante e a criação de 1-3 solicitações de API antes de iniciar cada teste individual pode levar a um tempo de execução horrível. Reutilizar credenciais não significa que os testes precisam agir nos mesmos registros do usuário - se depender de registros do usuário (por exemplo, histórico de pagamentos do usuário do teste), certifique-se de gerar esses registros como parte do teste e evitar compartilhar sua existência com outros testes. Lembre-se também de que o backend pode ser falsificado - se seus testes estiverem focados no frontend, pode ser melhor isolá-lo e esboçar a API de backend (consulte o item 3.6).
    -❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again +❌ **Caso contrário:** Dado 200 casos de teste e assumindo login=100ms = 20 segundos apenas para efetuar login novamente e novamente
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Logging-in before-all and not before-each +### :clap: Exemplo Fazendo Certo: Fazer login antes de todos e não antes de cada -![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg - "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Exaeplo%20usando%20Cypress-blue.svg + "Usando Cypress para ilustrar a idea") ```javascript let authenticationToken; @@ -1394,22 +1394,22 @@ beforeEach(setUser => () {
    -## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map +## ⚪ ️ 3.9 Faça um teste de fumaça E2E que viaja pelo mapa do site -:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector +:white_check_mark: **Faça:** Para monitoramento de produção e verificação de integridade do tempo de desenvolvimento, execute um único teste E2E que visite todas/a maioria das páginas do site e garanta que nenhuma quebre. Esse tipo de teste traz um ótimo retorno do investimento, pois é muito fácil de escrever e manter, mas pode detectar qualquer tipo de falha, incluindo problemas funcionais, de rede e de implantação. Outros estilos de verificação de fumaça e sanidade não são tão confiáveis ​​e exaustivos - algumas equipes de operações apenas fazem ping na página inicial (produção) ou desenvolvedores que executam muitos testes de integração os quais não descobrem problemas de empacotamento e de navegador. Nem precisa dizer que o teste de fumaça não substitui os testes funcionais, apenas visa servir como um detector de fumaça rápido
    -❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering +❌ **Caso contrário:** Tudo pode parecer perfeito, todos os testes são aprovados, a verificação de integridade da produção também é positiva, mas o componente Payment teve algum problema de embalagem e apenas a rota /Payment não está sendo processada
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Smoke travelling across all pages +### :clap: Exemplo Fazendo Certo: Fumaça viajando por todas as páginas ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") ```javascript @@ -1430,22 +1430,22 @@ it('When doing smoke testing over all page, should load them all successfully',
    -## ⚪ ️ 3.10 Expose the tests as a live collaborative document +## ⚪ ️ 3.10 Expor os testes como um documento colaborativo vivo -:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. +:white_check_mark: **Faça:** Além de aumentar a confiabilidade do aplicativo, os testes trazem outra oportunidade atraente para a mesa - servem como documentação viva do aplicativo. Como os testes falam inerentemente em uma linguagem menos técnica e de produto/UX, usando as ferramentas certas, eles podem servir como um artefato de comunicação que alinha muito os colegas - desenvolvedores e seus clientes. Por exemplo, algumas estruturas permitem expressar o fluxo e as expectativas (ou seja, plano de testes) usando uma linguagem legível por humanos, para que qualquer parte interessada, incluindo gerentes de produto, possa ler, aprovar e colaborar nos testes que acabaram de se tornar o documento de requisitos dinâmicos. Essa técnica também está sendo chamada de 'teste de aceitação', pois permite ao cliente definir seus critérios de aceitação em linguagem simples. Isso é [BDD (teste orientado ao comportamento)](https://en.wikipedia.org/wiki/Behavior-driven_development) na sua forma mais pura. Um dos frameworks populares que permitem isso é [Cucumber que tem um sabor de JavaScript](https://github.com/cucumber/cucumber-js), veja o exemplo abaixo. Outra oportunidade semelhante, porém diferente, [StoryBook](https://storybook.js.org/), permite expor componentes da interface do usuário como um catálogo gráfico, onde é possível percorrer os vários estados de cada componente (por exemplo. renderizar uma grid sem filtros, renderizar essa grid com várias linhas ou sem nenhuma, etc.), ver como ele se parece e como acionar esse estado - isso também pode atrair as pessoas do produto, mas serve principalmente como documento ativo para desenvolvedores que consomem esses componentes. -❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value +❌ **Otherwise:** Depois de investir muitos recursos em testes, é uma pena não alavancar esse investimento e obter grande valor
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js +### :clap: Exemplo Fazendo Certo: Descrevendo testes em linguagem humana usando cucumber-js -![](https://img.shields.io/badge/🔨%20Example%20using%20Cocumber-blue.svg "Examples using Cucumber") +![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Cocumber-blue.svg "Exemplos usando Cucumber") ```javascript // this is how one can describe tests using cucumber: plain language that allows anyone to understand and collaborate @@ -1463,8 +1463,8 @@ Feature: Twitter new tweet ``` -### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook -![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") +### :clap: Exemplo Fazendo Certo: Visualizando nossos componentes, seus vários estados e entradas usando o Storybook +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Usando StoryBook")
    @@ -1472,33 +1472,33 @@ Feature: Twitter new tweet -## ⚪ ️ 3.11 Detect visual issues with automated tools +## ⚪ ️ 3.11 Detecte problemas visuais com ferramentas automatizadas -:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS]([https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue +:white_check_mark: **Faça:** Configure ferramentas automatizadas para capturar a tela da interface do usuário quando alterações forem apresentadas e detectar problemas visuais, como sobreposição ou quebra de conteúdo. Isso garante que não apenas os dados corretos sejam preparados, mas também que o usuário possa vê-los convenientemente. Essa técnica não é amplamente adotada, nossa mentalidade de teste se inclina para testes funcionais, mas é o visual que o usuário experimenta e, com tantos tipos de dispositivos, é muito fácil ignorar alguns erros desagradáveis ​​da interface do usuário. Algumas ferramentas gratuitas podem fornecer o básico - gerar e salvar capturas de tela para a inspeção dos olhos humanos. Embora essa abordagem possa ser suficiente para aplicativos pequenos, ela é falha como qualquer outro teste manual que exige mão de obra humana sempre que algo muda. Por outro lado, é bastante desafiador detectar problemas de interface do usuário automaticamente devido à falta de definição clara - é aqui que o campo de 'Regressão Visual' entra em cena e resolve esse quebra-cabeça comparando a interface antiga com as alterações mais recentes e detectando diferenças. Algumas ferramentas OSS/gratuitas podem fornecer algumas dessas funcionalidades (por exemplo, [wraith](https://github.com/BBC-News/wraith), [PhantomCSS]([https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)) mas pode cobrar um tempo significativo de configuração. A linha comercial de ferramentas (por exemplo [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) dá um passo adiante ao suavizar a instalação e incluir recursos avançados, como interface de gerenciamento, alerta, captura inteligente, eliminando o 'ruído visual' (por exemplo, anúncios, animações) e até mesmo a análise de causa raiz das alterações no DOM/css que levaram ao problema
    -❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? +❌ **Caso contrário:** Quão boa é uma página de conteúdo que exibe ótimo conteúdo (100% nos testes aprovados), carrega instantaneamente, mas metade da área de conteúdo está oculta?
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti Pattern Example: A typical visual regression - right content that is served badly +### :thumbsdown: Exemplo Anti-padrão: Uma regressão visual típica - conteúdo correto que é mal servido -![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") +![alt text](assets/amazon-visual-regression.jpeg "Quebras de página na Amazon")
    -### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots +### :clap: Exemplo Fazendo Certo: Configurando o wraith para capturar e comparar instantâneos da UI -![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg - "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Wraith-blue.svg + "Usando Cypress para ilustrar a idea") ``` ​# Add as many domains as necessary. Key will act as a label​ @@ -1526,11 +1526,11 @@ paths: path: /subscribe ``` -### :clap: Doing It Right Example: Using Applitools to get snapshot comaprison and other advanced features +### :clap: Exemplo Fazendo Certo: Usando Applitools para obter comparação de captura instantânea e outros recursos avançados -![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg - "Using Cypress to illustrate the idea") ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg - "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20AppliTools-blue.svg + "Usando Cypress to para ilustrar a idea") ![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Cypress-blue.svg + "Usando Cypress para illustrar idea") ```javascript import * as todoPage from '../page-objects/todo-page'; From b4c3a60bd64d42c2691ab787a6a58edd58594878 Mon Sep 17 00:00:00 2001 From: DouglasMV Date: Tue, 19 Nov 2019 19:18:35 -0300 Subject: [PATCH 128/502] Fixes --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index bd0f5652..71532ae9 100644 --- a/readme.md +++ b/readme.md @@ -1453,7 +1453,7 @@ it('When doing smoke testing over all page, should load them all successfully', ### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js -![](https://img.shields.io/badge/🔨%20Example%20using%20Cocumber-blue.svg "Examples using Cucumber") +![](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 @@ -1508,7 +1508,7 @@ Feature: Twitter new tweet ### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots ![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg - "Using Cypress to illustrate the idea") + "Using Wraith") ``` ​# Add as many domains as necessary. Key will act as a label​ @@ -1539,7 +1539,7 @@ paths: ### :clap: Doing It Right Example: Using Applitools to get snapshot comaprison and other advanced features ![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg - "Using Cypress to illustrate the idea") ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg + "Using AppliTools") ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") ```javascript @@ -1601,7 +1601,7 @@ Implementation tips: You may want to configure your continuous integration (CI) ### :clap: Doing It Right Example: Setting up coverage per component (using Jest) ![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg - "Using Cypress to illustrate the idea") + "Using Jest") ![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest)") From 79b4deaab26601f2460660ff495cb1a59f444ad9 Mon Sep 17 00:00:00 2001 From: DouglasMV Date: Thu, 21 Nov 2019 21:28:24 -0300 Subject: [PATCH 129/502] Translated section 4 --- readme-pt-br.md | 74 +++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/readme-pt-br.md b/readme-pt-br.md index 35e953d1..023b460b 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -1584,39 +1584,39 @@ cy.eyesCheckWindow('mark as completed');

    -# Section 4️⃣: Measuring Test Effectiveness +# Section 4️⃣: Medindo a eficácia dos testes

    -## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number +## ⚪ ️ 4.1 Obtenha cobertura suficiente para ter confiança, ~ 80% parece ser o número da sorte -:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. +:white_check_mark: **Faça:** O objetivo do teste é obter confiança suficiente para avançar rapidamente, obviamente, quanto mais código for testado, mais confiante a equipe pode ter. Cobertura é uma medida de quantas linhas de código (e ramificações, instruções etc.) estão sendo alcançadas pelos testes. Então, quanto é suficiente? 10-30% é obviamente muito baixo para ter noção da correção da compilação, por outro lado, 100% é muito caro e pode mudar seu foco dos caminhos críticos para os cantos exóticos do código. A resposta longa é que depende de muitos fatores, como o tipo de aplicativo - se você está construindo a próxima geração do Airbus A380, 100% é uma obrigação, para um site de imagens de desenhos animados 50% pode ser demais. Embora a maioria dos entusiastas do teste afirme que o limite de cobertura correto é contextual, a maioria deles também menciona o número 80% como a regra de ouro ([Fowler: “na casa dos 80% ou 90%”](https://martinfowler.com/bliki/TestCoverage.html)) que presumivelmente, deve satisfazer a maioria das aplicações. -Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets +Dicas de implementação: Convém configurar sua integração contínua (CI) para ter um limite mínimo de cobertura ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) e interrompa uma compilação que não atenda a esse padrão (também é possível configurar o limite por componente, veja o exemplo de código abaixo). Além disso, considere detectar uma diminuição na cobertura da construção (quando um código recém-confirmado tem menos cobertura) — isso fará com que os desenvolvedores aumentem ou, pelo menos, preservem a quantidade de código testado. Tudo isso dito, a cobertura é apenas uma medida, quantitativa, que não é suficiente para demonstrar a robustez dos seus testes. E também pode ser enganada, como ilustrado nos próximos tópicos
    -❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down +❌ **Caso contrário:** Confiança e números andam de mãos dadas, sem realmente saber que você testou a maior parte do sistema - haverá também algum medo. e o medo vai atrasá-lo
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Example: A typical coverage report -![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") +### :clap: Exemplo: um relatório de cobertura típico +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "Um relatório de cobertura típico")
    -### :clap: Doing It Right Example: Setting up coverage per component (using Jest) +### :clap: Exemplo Fazendo Certo: Configurando a cobertura por componente (usando o Jest) -![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg - "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Jest-blue.svg + "Usando Jest") -![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest) +![alt text](assets/bp-18-code-coverage2.jpeg "Configurando a cobertura por componente (usando o Jest)")
    @@ -1624,56 +1624,58 @@ Implementation tips: You may want to configure your continuous integration (CI)

    -## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities +## ⚪ ️ 4.2 Inspecionar relatórios de cobertura para detectar áreas não testadas e outras esquisitices -:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +:white_check_mark: **Faça:** Alguns problemas se escondem logo abaixo do radar e são realmente difíceis de encontrar usando ferramentas tradicionais. Esses não são realmente erros, mas um comportamento surpreendente do aplicativo que pode ter um impacto grave. Por exemplo, geralmente algumas áreas de código nunca ou raramente são invocadas — você pensou que a classe 'PricingCalculator' está sempre definindo o preço do produto, mas, na verdade, nunca é invocada, embora tenhamos 10000 produtos no banco de dados e muitas vendas... Os relatórios de cobertura de código ajudam a perceber se o aplicativo se comporta da maneira que você acredita. Além disso, ele também pode destacar quais tipos de código não foram testados—ser informado que 80% do código é testado não informa se as partes críticas estão cobertas. Gerar relatórios é fácil—basta executar seu aplicativo em produção ou durante o teste com rastreamento de cobertura e ver relatórios coloridos que destacam a frequência com que cada área de código é invocada. Se você dedicar um tempo para vislumbrar esses dados—poderá encontrar algumas pegadinhas
    -❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from +❌ **Caso contrário:** Se você não sabe quais partes do seu código são deixadas sem teste, não sabe de onde os problemas podem surgir
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti-Pattern Example: 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) +### :thumbsdown: Exemplo Anti-padrão: O que há de errado com este relatório de cobertura? + +Com base em um cenário do mundo real, onde rastreamos o uso de nossos aplicativos no controle de qualidade e descobrimos padrões de login interessantes (Dica: a quantidade de falhas de login não é proporcional, algo está claramente errado. Por fim, verificou-se que algum bug do frontend continua atingindo a API de login do back-end) -![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 " que há de errado com este relatório de cobertura?")


    -## ⚪ ️ 4.3 Measure logical coverage using mutation testing +## ⚪ ️ 4.3 Meça a cobertura lógica usando teste de mutação -:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +:white_check_mark: **Faça:** A métrica de cobertura tradicional geralmente mente: Pode mostrar 100% de cobertura do código, mas nenhuma de suas funções, nem mesmo uma, retorna a resposta correta. Por quê? Ele simplesmente mede sobre quais linhas de código o teste visitou, mas não verifica se os testes realmente testaram alguma coisa— afirmou para a resposta certa. Como alguém que viaja a negócios e mostra seus carimbos de passaporte—isso não prova nenhum trabalho, apenas que ele visitou alguns aeroportos e hotéis. -Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: +O teste baseado em mutação está aqui para ajudar, medindo a quantidade de código que foi realmente TESTADO e não apenas VISITADO. [Stryker](https://stryker-mutator.io/) é uma biblioteca JavaScript para teste de mutação e a implementação é realmente legal: -(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations +(1) intencionalmente altera o código e "planta bugs". Por exemplo, o código newOrder.price===0 torna-se newOrder.price!=0. Esses "bugs" são chamados de mutações -(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. +(2) executa os testes, se todos tiverem sucesso, então temos um problema— os testes não serviram ao seu propósito de descobrir bugs, as mutações são chamadas sobreviventes. Se os testes falharem, então ótimo, as mutações foram mortas. -Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +Saber que todas ou a maioria das mutações foram mortas dá uma confiança muito maior do que a cobertura tradicional e o tempo de instalação é semelhante
    -❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code +❌ **Caso contrário:** Você ficará enganado ao acreditar que 85% de cobertura significa que seu teste detectará bugs em 85% do seu código
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti Pattern Example: 100% coverage, 0% testing +### :thumbsdown: Exemplo Anti-padrão: 100% de cobertura, 0% de teste -![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg - "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Stryker-blue.svg + "Usando Stryker") ```javascript function addNewOrder(newOrder) { logger.log(`Adding new order ${newOrder}`); @@ -1690,9 +1692,9 @@ it("Test addNewOrder, don't use such test names", () => { ```
    -### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :clap: Exemplo Fazendo Certo: Stryker reports, uma ferramenta para teste de mutação, detecta e conta a quantidade de código que não foi testado (mutações) -![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/bp-20-yoni-goldberg-mutation-testing.jpeg "Stryker reports, uma ferramenta para teste de mutação, detecta e conta a quantidade de código que não foi testado (mutações)")
    @@ -1700,22 +1702,22 @@ it("Test addNewOrder, don't use such test names", () => {

    -## ⚪ ️4.4 Preventing test code issues with Test linters +## ⚪ ️4.4 Impedindo problemas de código de teste com os linters de teste -:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +:white_check_mark: **Faça:** Um conjunto de plugins ESLint foi construído especificamente para inspecionar os padrões de código de testes e descobrir problemas. Por exemplo, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) avisará quando um teste for escrito em nível global (não é filho de uma declaração describe()) ou quando os testes são [pulados](https://mochajs.org/#inclusive-tests) o que pode levar a uma falsa crença de que todos os testes estão passando. Similarmente, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) pode, por exemplo, avisar quando um teste não tem asserções (não verificando nada)
    -❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation +❌ **Caso contrário:** Ver 90% de cobertura de código e 100% de testes verdes fará com que seu rosto seja um grande sorriso apenas até você perceber que muitos testes não afirmam nada e que muitos conjuntos de testes foram ignorados. Tomara que você não tenha implantado nada com base nessa observação falsa
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti Pattern Example: A test case full of errors, luckily all are caught by Linters +### :thumbsdown: Exemplo Anti-padrão: Um caso de teste cheio de erros, felizmente, todos são pegos por Linters ```javascript describe("Too short description", () => { From ea636e10e49ea07c6e6326d71298a963298d1e29 Mon Sep 17 00:00:00 2001 From: Kyle Martin Date: Fri, 6 Dec 2019 20:14:45 +1300 Subject: [PATCH 130/502] attempt at fixing bot --- .all-contributorsrc | 9 --------- readme.md | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index ab2a67e4..60c51340 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -7,15 +7,6 @@ "badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square)](#contributors)", "skipCi": "true", "contributors": [ - { - "login": "stdavis", - "name": "Scott Davis", - "avatar_url": "https://avatars3.githubusercontent.com/u/1326248?v=4", - "profile": "http://geospatialscott.blogspot.com/", - "contributions": [ - "content" - ] - } ], "projectName": "javascript-testing-best-practices", "projectOwner": "goldbergyoni", diff --git a/readme.md b/readme.md index cc9a5825..8129f5c7 100644 --- a/readme.md +++ b/readme.md @@ -1995,8 +1995,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    - -## [Bruno Scheufler](https://github.com/BrunoScheufler) +## [Bruno Scheufler](https://github.com/BrunoScheufler) **Role:** Tech reviewer and advisor @@ -2017,3 +2016,14 @@ Took care to revise, improve, lint and polish all the texts **Role:** Helps keep this project running, and reviews security related practices **About:** Loves working on Node.js projects and web application security. + +## Contributors ✨ + +Thanks goes to these wonderful people who have contributed to this repository! + + + + +
    + + From fbfbedc5b400b5f1060c222a35a34163e950e0d6 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:17:50 +0000 Subject: [PATCH 131/502] docs: update readme.md [skip ci] --- readme.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 8129f5c7..6b3dc3b3 100644 --- a/readme.md +++ b/readme.md @@ -2022,8 +2022,14 @@ Took care to revise, improve, lint and polish all the texts Thanks goes to these wonderful people who have contributed to this repository! - + + + + +
    Scott Davis
    Scott Davis

    🖋
    + + From 638e5a487de614174e2e5bfd85542fc08eecb0e1 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:17:51 +0000 Subject: [PATCH 132/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 60c51340..6cb4fc07 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -5,8 +5,17 @@ "imageSize": 100, "contributorsPerLine": 7, "badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square)](#contributors)", - "skipCi": "true", + "skipCi": true, "contributors": [ + { + "login": "stdavis", + "name": "Scott Davis", + "avatar_url": "https://avatars3.githubusercontent.com/u/1326248?v=4", + "profile": "http://geospatialscott.blogspot.com/", + "contributions": [ + "content" + ] + } ], "projectName": "javascript-testing-best-practices", "projectOwner": "goldbergyoni", From 325956786342f26ef695fddd3150e35470bcf317 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:18:42 +0000 Subject: [PATCH 133/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 6b3dc3b3..50e43934 100644 --- a/readme.md +++ b/readme.md @@ -2027,6 +2027,7 @@ Thanks goes to these wonderful people who have contributed to this repository! +
    Scott Davis
    Scott Davis

    🖋
    Adrien REDON
    Adrien REDON

    🖋
    From fb413618a75bc7badaaac0be543ac3e5783bd90d Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:18:43 +0000 Subject: [PATCH 134/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 6cb4fc07..31f3610b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -15,6 +15,15 @@ "contributions": [ "content" ] + }, + { + "login": "AdrienRedon", + "name": "Adrien REDON", + "avatar_url": "https://avatars2.githubusercontent.com/u/5978436?v=4", + "profile": "https://github.com/AdrienRedon", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From ae2944f65ce961072877d41db0caaebeaa85557c Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:19:33 +0000 Subject: [PATCH 135/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 50e43934..2c68a37a 100644 --- a/readme.md +++ b/readme.md @@ -2028,6 +2028,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Scott Davis
    Scott Davis

    🖋 Adrien REDON
    Adrien REDON

    🖋 + Stefano Magni
    Stefano Magni

    🖋 From 26d2057ab84b6105798d0226af4f6c387ca41151 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:19:34 +0000 Subject: [PATCH 136/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 31f3610b..28ce24b3 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -24,6 +24,15 @@ "contributions": [ "content" ] + }, + { + "login": "NoriSte", + "name": "Stefano Magni", + "avatar_url": "https://avatars0.githubusercontent.com/u/173663?v=4", + "profile": "https://twitter.com/NoriSte", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 7cc9e29c98931f046dead713cd70ac8badc19791 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:20:12 +0000 Subject: [PATCH 137/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 2c68a37a..0b201f67 100644 --- a/readme.md +++ b/readme.md @@ -2029,6 +2029,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Scott Davis
    Scott Davis

    🖋 Adrien REDON
    Adrien REDON

    🖋 Stefano Magni
    Stefano Magni

    🖋 + Yeoh Joer
    Yeoh Joer

    🖋 From 05eb98723bd276f05b55b78e0ed8e1976d905f53 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:20:14 +0000 Subject: [PATCH 138/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 28ce24b3..6e4fadb4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -33,6 +33,15 @@ "contributions": [ "content" ] + }, + { + "login": "yjoer", + "name": "Yeoh Joer", + "avatar_url": "https://avatars2.githubusercontent.com/u/47742486?v=4", + "profile": "https://www.joer.im", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From c5be7807aa8b5a2ebc91a9ba175b6733fe4ada80 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:20:58 +0000 Subject: [PATCH 139/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 0b201f67..6422b8e8 100644 --- a/readme.md +++ b/readme.md @@ -2030,6 +2030,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Adrien REDON
    Adrien REDON

    🖋 Stefano Magni
    Stefano Magni

    🖋 Yeoh Joer
    Yeoh Joer

    🖋 + Jhonny Moreira
    Jhonny Moreira

    🖋 From 4fc18a68a70f1adb369bd197bfad090879cad389 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:20:59 +0000 Subject: [PATCH 140/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 6e4fadb4..30ba7658 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -42,6 +42,15 @@ "contributions": [ "content" ] + }, + { + "login": "jhonnymoreira", + "name": "Jhonny Moreira", + "avatar_url": "https://avatars0.githubusercontent.com/u/2177742?v=4", + "profile": "http://jhonnymoreira.dev", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 818d2bc1071e56c4cbe11cc0d5778084e42c9566 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:21:41 +0000 Subject: [PATCH 141/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 6422b8e8..6720a1bb 100644 --- a/readme.md +++ b/readme.md @@ -2031,6 +2031,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Stefano Magni
    Stefano Magni

    🖋 Yeoh Joer
    Yeoh Joer

    🖋 Jhonny Moreira
    Jhonny Moreira

    🖋 + Ian Germann
    Ian Germann

    🖋 From 825f1829b610a521e46b0c4a7e2ff1f9bb32a473 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:21:42 +0000 Subject: [PATCH 142/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 30ba7658..6f13e0d3 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -51,6 +51,15 @@ "contributions": [ "content" ] + }, + { + "login": "Germanika", + "name": "Ian Germann", + "avatar_url": "https://avatars2.githubusercontent.com/u/8846678?v=4", + "profile": "https://github.com/Germanika", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 3d92fb5f6954e7b076cf99d99cd66480abb55c7d Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:22:20 +0000 Subject: [PATCH 143/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 6720a1bb..12fbc0e8 100644 --- a/readme.md +++ b/readme.md @@ -2032,6 +2032,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Yeoh Joer
    Yeoh Joer

    🖋 Jhonny Moreira
    Jhonny Moreira

    🖋 Ian Germann
    Ian Germann

    🖋 + Hafez
    Hafez

    🖋 From 26a971ed8f371f3091fee8d7a305c937a8747e29 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:22:21 +0000 Subject: [PATCH 144/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 6f13e0d3..1373cf76 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -60,6 +60,15 @@ "contributions": [ "content" ] + }, + { + "login": "AbdelrahmanHafez", + "name": "Hafez", + "avatar_url": "https://avatars3.githubusercontent.com/u/19984935?v=4", + "profile": "https://github.com/AbdelrahmanHafez", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From be2c406b04d98e0ab414052ee44d488ec645adaf Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:23:02 +0000 Subject: [PATCH 145/502] docs: update readme.md [skip ci] --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index 12fbc0e8..13f9855b 100644 --- a/readme.md +++ b/readme.md @@ -2034,6 +2034,9 @@ Thanks goes to these wonderful people who have contributed to this repository! Ian Germann
    Ian Germann

    🖋 Hafez
    Hafez

    🖋 + + Ruxandra Fediuc
    Ruxandra Fediuc

    🖋 + From abbc7f57dc50b4bcf5bed2da40c46978181e4791 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:23:04 +0000 Subject: [PATCH 146/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 1373cf76..70b27419 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -69,6 +69,15 @@ "contributions": [ "content" ] + }, + { + "login": "ruxandrafed", + "name": "Ruxandra Fediuc", + "avatar_url": "https://avatars1.githubusercontent.com/u/11021586?v=4", + "profile": "http://www.ruxandrafediuc.com", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From c960766041e71687a509d903c0c82d38895b6598 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:23:48 +0000 Subject: [PATCH 147/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 13f9855b..1f58153c 100644 --- a/readme.md +++ b/readme.md @@ -2036,6 +2036,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Ruxandra Fediuc
    Ruxandra Fediuc

    🖋 + Jack
    Jack

    🖋 From 32377172cd4224c81d910a38e35687f820f581c4 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:23:49 +0000 Subject: [PATCH 148/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 70b27419..2ddd24c9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -78,6 +78,15 @@ "contributions": [ "content" ] + }, + { + "login": "jacklee814", + "name": "Jack", + "avatar_url": "https://avatars0.githubusercontent.com/u/9951291?v=4", + "profile": "https://github.com/jacklee814", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 965ace939447549d2fb89745fe2440cbb3dcc885 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:24:23 +0000 Subject: [PATCH 149/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 1f58153c..91d2ff90 100644 --- a/readme.md +++ b/readme.md @@ -2037,6 +2037,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Ruxandra Fediuc
    Ruxandra Fediuc

    🖋 Jack
    Jack

    🖋 + Peter Carrero
    Peter Carrero

    🖋 From bcbe6233ba7d24f856b7b6271070d86809871225 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:24:24 +0000 Subject: [PATCH 150/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 2ddd24c9..1e3881bd 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -87,6 +87,15 @@ "contributions": [ "content" ] + }, + { + "login": "aloyr", + "name": "Peter Carrero", + "avatar_url": "https://avatars0.githubusercontent.com/u/231727?v=4", + "profile": "https://www.petercarrero.com", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 87a28f02ccb9e45229dd2a56974ec33e68690832 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:24:55 +0000 Subject: [PATCH 151/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 91d2ff90..f01d64ee 100644 --- a/readme.md +++ b/readme.md @@ -2038,6 +2038,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Ruxandra Fediuc
    Ruxandra Fediuc

    🖋 Jack
    Jack

    🖋 Peter Carrero
    Peter Carrero

    🖋 + Huhgawz
    Huhgawz

    🖋 From 1ba02f089104ae8580d0ceee1af1e3f9068abad3 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:24:56 +0000 Subject: [PATCH 152/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 1e3881bd..d916a532 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -96,6 +96,15 @@ "contributions": [ "content" ] + }, + { + "login": "huhgawz", + "name": "Huhgawz", + "avatar_url": "https://avatars3.githubusercontent.com/u/369338?v=4", + "profile": "https://github.com/huhgawz", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 5ab4ced0aebced45999e35647386204a2b05d705 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:26:54 +0000 Subject: [PATCH 153/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index f01d64ee..d75c6ffb 100644 --- a/readme.md +++ b/readme.md @@ -2039,6 +2039,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Jack
    Jack

    🖋 Peter Carrero
    Peter Carrero

    🖋 Huhgawz
    Huhgawz

    🖋 + Haakon Borch
    Haakon Borch

    🖋 From 3b7763157af3e8791a2dd49890ae7414b73cf23a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:26:55 +0000 Subject: [PATCH 154/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index d916a532..75dd40f4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -105,6 +105,15 @@ "contributions": [ "content" ] + }, + { + "login": "haakonmb", + "name": "Haakon Borch", + "avatar_url": "https://avatars1.githubusercontent.com/u/7099302?v=4", + "profile": "https://github.com/haakonmb", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From f15fefd963eee517c064acb51e0b72e06b894ef2 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:30:06 +0000 Subject: [PATCH 155/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index d75c6ffb..35822294 100644 --- a/readme.md +++ b/readme.md @@ -2040,6 +2040,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Peter Carrero
    Peter Carrero

    🖋 Huhgawz
    Huhgawz

    🖋 Haakon Borch
    Haakon Borch

    🖋 + Jaime Mendoza
    Jaime Mendoza

    🖋 From 3056b32ebda2ef83ad3c2713909158cfb1e54ac3 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:30:07 +0000 Subject: [PATCH 156/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 75dd40f4..31e06aba 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -114,6 +114,15 @@ "contributions": [ "content" ] + }, + { + "login": "jaimemendozadev", + "name": "Jaime Mendoza", + "avatar_url": "https://avatars3.githubusercontent.com/u/5395811?v=4", + "profile": "https://jaimemendoza.com/", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 2b6783fc2db615ec654a129c24a518094d4c06c2 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:30:48 +0000 Subject: [PATCH 157/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 35822294..38b250a1 100644 --- a/readme.md +++ b/readme.md @@ -2041,6 +2041,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Huhgawz
    Huhgawz

    🖋 Haakon Borch
    Haakon Borch

    🖋 Jaime Mendoza
    Jaime Mendoza

    🖋 + Cameron Dunford
    Cameron Dunford

    🖋 From 23cf186d75bdbbdd4d437123a989042e9c5e138a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:30:49 +0000 Subject: [PATCH 158/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 31e06aba..fe6268fc 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -123,6 +123,15 @@ "contributions": [ "content" ] + }, + { + "login": "camerondunford", + "name": "Cameron Dunford", + "avatar_url": "https://avatars0.githubusercontent.com/u/840612?v=4", + "profile": "https://github.com/camerondunford", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From deadd5845cdbb3455804c1a06f6c39214054770d Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:32:10 +0000 Subject: [PATCH 159/502] docs: update readme.md [skip ci] --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index 38b250a1..47177918 100644 --- a/readme.md +++ b/readme.md @@ -2043,6 +2043,9 @@ Thanks goes to these wonderful people who have contributed to this repository! Jaime Mendoza
    Jaime Mendoza

    🖋 Cameron Dunford
    Cameron Dunford

    🖋 + + John Gee
    John Gee

    🖋 + From 47e623e9a0420c2fb662c98f6d5502c2e5bfc334 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:32:12 +0000 Subject: [PATCH 160/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index fe6268fc..4252f2e8 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -132,6 +132,15 @@ "contributions": [ "content" ] + }, + { + "login": "shadowspawn", + "name": "John Gee", + "avatar_url": "https://avatars1.githubusercontent.com/u/15719847?v=4", + "profile": "https://github.com/shadowspawn", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 59fd4bd4d1a2522c0bfc91aa37005ececabb17e5 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:32:54 +0000 Subject: [PATCH 161/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 47177918..d0578403 100644 --- a/readme.md +++ b/readme.md @@ -2045,6 +2045,7 @@ Thanks goes to these wonderful people who have contributed to this repository! John Gee
    John Gee

    🖋 + Aurelijus Rožėnas
    Aurelijus Rožėnas

    🖋 From 99ff49cbe746080015f20a0cfc9bf49e61c3aedf Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:32:55 +0000 Subject: [PATCH 162/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 4252f2e8..726dfb3a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -141,6 +141,15 @@ "contributions": [ "content" ] + }, + { + "login": "aurelijusrozenas", + "name": "Aurelijus Rožėnas", + "avatar_url": "https://avatars0.githubusercontent.com/u/3273544?v=4", + "profile": "https://github.com/aurelijusrozenas", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From f8f64342a1cff619b5c27a4d521b980067553d3a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:35:05 +0000 Subject: [PATCH 163/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index d0578403..fff1c61f 100644 --- a/readme.md +++ b/readme.md @@ -2046,6 +2046,7 @@ Thanks goes to these wonderful people who have contributed to this repository! John Gee
    John Gee

    🖋 Aurelijus Rožėnas
    Aurelijus Rožėnas

    🖋 + Aaron
    Aaron

    🖋 From 9344a075d5986f3c4980835a63387f1664048e49 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:35:07 +0000 Subject: [PATCH 164/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 726dfb3a..d3ead90f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -150,6 +150,15 @@ "contributions": [ "content" ] + }, + { + "login": "aaronshivers", + "name": "Aaron", + "avatar_url": "https://avatars2.githubusercontent.com/u/42848750?v=4", + "profile": "http://aaronshivers.com", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 0d563fe54bccf6d2090414beb3fef2226cb6435f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:35:46 +0000 Subject: [PATCH 165/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index fff1c61f..eb252160 100644 --- a/readme.md +++ b/readme.md @@ -2047,6 +2047,7 @@ Thanks goes to these wonderful people who have contributed to this repository! John Gee
    John Gee

    🖋 Aurelijus Rožėnas
    Aurelijus Rožėnas

    🖋 Aaron
    Aaron

    🖋 + Tom Nagle
    Tom Nagle

    🖋 From 3ac721b77d3a1a5bad06063d3b866773570000d3 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:35:47 +0000 Subject: [PATCH 166/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index d3ead90f..4f1e30fe 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -159,6 +159,15 @@ "contributions": [ "content" ] + }, + { + "login": "tomanagle", + "name": "Tom Nagle", + "avatar_url": "https://avatars1.githubusercontent.com/u/8683577?v=4", + "profile": "https://tomdoes.tech/", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 72aa674a7e1effd79b9f4ae5385c1e196043b102 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:36:25 +0000 Subject: [PATCH 167/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index eb252160..ed75a413 100644 --- a/readme.md +++ b/readme.md @@ -2048,6 +2048,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Aurelijus Rožėnas
    Aurelijus Rožėnas

    🖋 Aaron
    Aaron

    🖋 Tom Nagle
    Tom Nagle

    🖋 + Yves yao
    Yves yao

    🖋 From d41247feac95365444e4c0f09db53a24e16cac31 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:36:26 +0000 Subject: [PATCH 168/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 4f1e30fe..424df1ae 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -168,6 +168,15 @@ "contributions": [ "content" ] + }, + { + "login": "yvesyao", + "name": "Yves yao", + "avatar_url": "https://avatars0.githubusercontent.com/u/7723729?v=4", + "profile": "https://github.com/yvesyao", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 0510e6c9e7cc8344bf53a396e7b5c954b01697e0 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:38:27 +0000 Subject: [PATCH 169/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index ed75a413..fa9a6d6d 100644 --- a/readme.md +++ b/readme.md @@ -2049,6 +2049,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Aaron
    Aaron

    🖋 Tom Nagle
    Tom Nagle

    🖋 Yves yao
    Yves yao

    🖋 + Userbit
    Userbit

    🖋 From f442ce1ff6f82fa8262458c8770692b6613571ab Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2019 07:38:28 +0000 Subject: [PATCH 170/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 424df1ae..85eaeea7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -177,6 +177,15 @@ "contributions": [ "content" ] + }, + { + "login": "Userbit", + "name": "Userbit", + "avatar_url": "https://avatars1.githubusercontent.com/u/34487074?v=4", + "profile": "https://github.com/Userbit", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 6317cc154dc915ede0f042318f1424052fe7d707 Mon Sep 17 00:00:00 2001 From: Nicolas DUBIEN Date: Sun, 8 Dec 2019 21:10:34 +0100 Subject: [PATCH 171/502] Update property based example to use fast-check --- readme-zh-CN.md | 24 ++++++++++++------------ readme.kr.md | 27 ++++++++++++++------------- readme.md | 24 ++++++++++++------------ 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/readme-zh-CN.md b/readme-zh-CN.md index cc39e6fc..beed8ce9 100644 --- a/readme-zh-CN.md +++ b/readme-zh-CN.md @@ -441,25 +441,25 @@ it("Better: When adding new valid product, get successful confirmation", async (
    -### :clap: 正例: 使用“mocha-testcheck”测试输入的组合 +### :clap: 正例: 使用“fast-check”测试输入的组合 -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ```javascript -require('mocha-testcheck').install(); -const {expect} = require('chai'); +import fc from "fast-check"; -describe('Product service', () => { - describe('Adding new', () => { +describe("Product service", () => { + describe("Adding new", () => { //this will run 100 times with different random properties - check.it('Add new product with random yet valid properties, always successful', - gen.int, gen.string, (id, name) => { - expect(addNewProduct(id, name).status).to.equal('approved'); - }); - }) + 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"); + }) + )); + }); }); - ```
    diff --git a/readme.kr.md b/readme.kr.md index 65205a2e..8eae2199 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -419,23 +419,24 @@ it("더 나은 것: 유효한 제품이 추가된다면, 성공을 얻는다.",
    -### :clap: 올바른 예: “mocha-testcheck”를 사용하여 다양한 인풋 조합으로 테스트 하십시오. +### :clap: 올바른 예: “fast-check”를 사용하여 다양한 인풋 조합으로 테스트 하십시오. -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ```javascript -require('mocha-testcheck').install(); -const {expect} = require('chai'); - -describe('Product service', () => { - describe('Adding new', () => { - //서로 다른 무작위 값으로 100회 호출됩니다. - check.it('Add new product with random yet valid properties, always successful', - gen.int, gen.string, (id, name) => { - expect(addNewProduct(id, name).status).to.equal('approved'); - }); - }) +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"); + }) + )); + }); }); ``` diff --git a/readme.md b/readme.md index fa9a6d6d..4cfe7445 100644 --- a/readme.md +++ b/readme.md @@ -451,25 +451,25 @@ it("Better: When adding new valid product, get successful confirmation", async (
    -### :clap: Doing It Right Example: Testing many input permutations with “mocha-testcheck” +### :clap: Doing It Right Example: Testing many input permutations with “fast-check” -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ```javascript -require('mocha-testcheck').install(); -const {expect} = require('chai'); +import fc from "fast-check"; -describe('Product service', () => { - describe('Adding new', () => { +describe("Product service", () => { + describe("Adding new", () => { //this will run 100 times with different random properties - check.it('Add new product with random yet valid properties, always successful', - gen.int, gen.string, (id, name) => { - expect(addNewProduct(id, name).status).to.equal('approved'); - }); - }) + 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"); + }) + )); + }); }); - ```
    From e3eb9634e3cc06e017cf78d70dc60c05a83eb415 Mon Sep 17 00:00:00 2001 From: Hafez Date: Thu, 19 Dec 2019 00:22:38 +0200 Subject: [PATCH 172/502] Fix code example --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 4cfe7445..36cca01b 100644 --- a/readme.md +++ b/readme.md @@ -235,7 +235,7 @@ 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}); - const admin1Found, adming2Found = false; + let admin1Found, adming2Found = false; allAdmins.forEach(aSingleUser => { if(aSingleUser === "user1"){ From 31b68df0376db654fb8fc70f1ffeb3ea2f394617 Mon Sep 17 00:00:00 2001 From: DouglasMV Date: Fri, 20 Dec 2019 18:26:16 -0300 Subject: [PATCH 173/502] Finished section 1 --- readme-pt-br.md | 322 ++++++++++++++++++++++++------------------------ 1 file changed, 161 insertions(+), 161 deletions(-) diff --git a/readme-pt-br.md b/readme-pt-br.md index 023b460b..7438c2c3 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -33,27 +33,27 @@ Comece entendendo as práticas de teste onipresentes que são a base para qualqu #### [`Seção 0: A Regra de ouro`](#section-0️⃣-the-golden-rule) -Um único conselho que inspira todos os outros (1 marcador especial) +Um único conselho que inspira todos os outros (1 tópico especial) #### [`Seção 1: A Anatomia do Teste`](#section-1-the-test-anatomy-1) -A fundação - estruturando testes limpos (12 marcadores) +A fundação - estruturando testes limpos (12 tópicos) #### [`Seção 2: Backend`](#section-2️⃣-backend-testing) -Escrevendo testes de back-end e microsserviços com eficiência (8 marcadores) +Escrevendo testes de back-end e microsserviços com eficiência (8 tópicos) #### [`Seção 3: Frontend`](#section-3️⃣-frontend-testing) -Escrevendo testes para interface do usuário da web, incluindo testes de componentes e E2E (11 marcadores) +Escrevendo testes para interface do usuário da web, incluindo testes de componentes e E2E (11 tópicos) #### [`Seção 4: Metrificando Testes Efetivamente`](#section-4️⃣-measuring-test-effectiveness) -Observando o vigia - medindo a qualidade do teste (4 marcadores) +Observando o vigia - medindo a qualidade do teste (4 tópicos) #### [`Seção 5: Integração Contínua`](#section-5️⃣-ci-and-other-quality-measures) -Diretrizes para CI no mundo JS (9 marcadores) +Diretrizes para CI no mundo JS (9 tópicos)

    @@ -68,13 +68,13 @@ Diretrizes para CI no mundo JS (9 marcadores) :white_check_mark: **Faça:** O código de teste não é como o código de produção - projete-o para ser simples, curto, sem abstrações, plano, agradável de se trabalhar, enxuto. Deve-se olhar para um teste e obter a intenção instantaneamente. -Nossas mentes estão cheias com o código principal de produção, não temos 'espaço de sobra' para complexidade adicional. Se tentarmos espremer outro código desafiador em nosso cérebro fraco, a equipe ficará mais lenta, o que vai de encontro com a razão pela qual fazemos os testes. Praticamente é aqui que muitas equipes abandonam os testes. +Nossas mentes estão cheias com o código principal de produção, não temos 'espaço de sobra' para complexidade adicional. Se tentarmos espremer outro código desafiador em nosso cérebro fraco, a equipe ficará mais lenta, o que vai de encontro com a razão pela qual fazemos os testes. Na prática é aqui que muitas equipes abandonam os testes. Os testes são uma oportunidade para outra coisa - um assistente amigável e sorridente, que é agradável de trabalhar e oferece grande valor para um investimento tão pequeno. A ciência diz que temos dois sistemas cerebrais: o sistema 1, usado para atividades sem esforço, como dirigir um carro em uma estrada vazia, e o sistema 2, destinado a operações complexas e conscientes, como resolver uma equação matemática. Projete seu teste para o sistema 1, ao analisar o código de teste, ele deve parecer tão fácil quanto modificar um documento HTML e não como resolver um equação 2X (17 × 24). Isso pode ser alcançado através de técnicas, ferramentas e alvos de teste selecionados de forma econômica, que são econômicos e proporcionam um ótimo ROI. Teste apenas o necessário, esforce-se para mantê-lo ágil, às vezes vale a pena abandonar alguns testes e trocar a confiabilidade por agilidade e simplicidade. -![alt text](/assets/headspace.png "We have no head room for additional complexity") +![alt text](/assets/headspace.png "Não temos espaço para complexidade adicional") A maioria dos conselhos abaixo são derivados desse princípio. @@ -104,21 +104,21 @@ A maioria dos conselhos abaixo são derivados desse princípio.
    -**👇 Nota:** Cada marcador possui exemplos de código e alguns tem ilustrações. Clique para expandir +**👇 Nota:** Cada tópico possui exemplos de código e alguns tem ilustrações. Clique para expandir
    Códigos de Exemplo
    -### :clap: Exemplo: um nome de teste que constitui 3 partes +### :clap: Exemplo Fazendo Certo: um nome de teste que constitui 3 partes -![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg - "Using Mocha to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Mocha-blue.svg + "Usando o Mocha para ilustrar a ideia") ```javascript -//1. unit under test +//1. unidade em teste describe('Products Service', function() { describe('Add new product', function() { - //2. scenario and 3. expectation + //2. cenário e 3. expectativa it('When no price is specified, then the product status is pending approval', ()=> { const newProduct = new ProductService().add(...); expect(newProduct.status).to.equal('pendingApproval'); @@ -129,8 +129,8 @@ describe('Products Service', function() { ```
    -### :clap: Exemplo: um nome de teste que constitui 3 partes -![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") +### :clap: Exemplo Fazendo Certo: um nome de teste que constitui 3 partes +![alt text](/assets/bp-1-3-parts.jpeg "Um nome de teste que constitui 3 partes")
    @@ -138,11 +138,11 @@ describe('Products Service', function() { ## ⚪ ️ 1.2 Testes de estrutura pelo padrão em inglês AAA -:white_check_mark: **Faça:** Estruture seus testes com 3 seções bem separadas: Organizar, Atuar e Afirmar (OAA). Seguir essa estrutura garante que o leitor não gaste CPU do cérebro na compreensão do plano de teste: +:white_check_mark: **Faça:** Estruture seus testes com 3 seções bem separadas: Ajeitar, Atuar e Afirmar (AAA). Seguir essa estrutura garante que o leitor não gaste CPU do cérebro na compreensão do plano de teste: -1st O - Organizar: todo o código de configuração para levar o sistema ao cenário que o teste pretende simular. Isso pode incluir instanciar a unidade sob o construtor de teste, adicionar registros de banco de dados, mockar/stubbing de objetos e qualquer outro código de preparação +1st A - Ajeitar: todo o código de configuração para levar o sistema ao cenário que o teste pretende simular. Isso pode incluir instanciar a unidade sob o construtor de teste, adicionar registros de banco de dados, mockar/stubbing de objetos e qualquer outro código de preparação -2nd A - Ato: Execute teste em unidade. Geralmente 1 linha de código +2nd A - Atuar: Execute teste em unidade. Geralmente 1 linha de código 3rd A - Afirmar: Garanta que o valor recebido satisfaça a expectativa. Geralmente 1 linha de código @@ -150,7 +150,7 @@ describe('Products Service', function() {
    -❌ **Caso contrário:** Você não gata apenas longas horas diárias para entender o código principal, agora também o que deveria ter sido a parte simples do dia (teste) estica seu cérebro +❌ **Caso contrário:** Você não gasta apenas longas horas diárias para entender o código principal, agora também o que deveria ter sido a parte simples do dia (teste) estica seu cérebro
    @@ -158,24 +158,24 @@ describe('Products Service', function() {
    -### :clap: Doing It Right Example: A test structured with the AAA pattern +### :clap: Exemplo Fazendo Certo: Um teste estruturado com o padrão 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 Jest") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Jest-blue.svg + "Exemplos com Jest") ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Mocha-blue.svg + "Examples com Mocha") ```javascript describe('Customer classifier', () => { test('When customer spent more than 500$, should be classified as premium', () => { - //Arrange + //Ajeitar const customerToClassify = {spent:505, joined: new Date(), id:1} const DBStub = sinon.stub(dataAccess, "getCustomer") .reply({id:1, classification: 'regular'}); - //Act + //Atuar const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); - //Assert + //Afirmar expect(receivedClassification).toMatch('premium'); }); }); @@ -183,7 +183,7 @@ describe('Customer classifier', () => {
    -### :thumbsdown: Anti Pattern Example: No separation, one bulk, harder to interpret +### :thumbsdown: Exemplo Anti-padrão: Sem separação, um grande volume, mais difícil de interpretar ```javascript test('Should be classified as premium', () => { @@ -205,27 +205,27 @@ test('Should be classified as premium', () => { -## ⚪ ️1.3 Describe expectations in a product language: use BDD-style assertions +## ⚪ ️1.3 Descrever expectativas em um idioma do produto: use afirmações no estilo BDD -:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write an imperative code that is packed with conditional logic the reader is thrown away to an effortful mental mood. In that sense, code the expectation in a human-like language, declarative BDD style using expect or should and not using custom code. If Chai & Jest don’t include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +:white_check_mark: **Faça:** Codificar seus testes em um estilo declarativo permite que o leitor entenda instantaneamente, sem gastar nem um único ciclo de CPU do cérebro. Quando você escreve um código imperativo, repleto de lógica condicional, o leitor entra em um estado mental de esforço. Nesse sentido, codifique a expectativa em uma linguagem humana, estilo declarativo de BDD usando expect ou should e não usando código personalizado. Se Chai e Jest não incluem a afirmação desejada e são altamente repetíveis, considere [estender o Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) ou escrever um [plugin Chai personalizado](https://www.chaijs.com/guide/plugins/)
    -❌ **Otherwise:** The team will write less test and decorate the annoying ones with .skip() +❌ **Caso Contrário:** A equipe escreverá menos testes e decorará os irritantes com .skip()
    -
    Code Examples
    +
    Códigos de Exemplo
    -![](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") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Mocha-blue.svg + "Exemplos com Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Jest-blue.svg + "Exemplos com Jest") - ### :thumbsdown: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story + ### :thumbsdown: Exemplo Anti-padrão: O leitor deve percorrer códigos não tão curtos e imperativos apenas para chegar a história do teste ```javascript test("When asking for an admin, ensure only ordered admins in results" , () => { - //assuming we've added here two admins "admin1", "admin2" and "user1" + //supondo que adicionamos aqui dois administradores "admin1", "admin2" e "user1" const allAdmins = getUsers({adminOnly:true}); const admin1Found, adming2Found = false; @@ -250,12 +250,12 @@ test("When asking for an admin, ensure only ordered admins in results" , () => { ```
    -### :clap: Doing It Right Example: Skimming through the following declarative test is a breeze +### :clap: Exemplo Fazendo Certo: Percorrer o teste declarativo a seguir é fácil ```javascript it("When asking for an admin, ensure only ordered admins in results" , () => { - //assuming we've added here two admins + //supondo que adicionamos aqui dois administradores const allAdmins = getUsers({adminOnly:true}); expect(allAdmins).to.include.ordered.members(["admin1" , "admin2"]) @@ -270,31 +270,31 @@ it("When asking for an admin, ensure only ordered admins in results" , () => {

    -## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods +## ⚪ ️ 1.4 Atenha-se ao teste de caixa preta: teste apenas métodos públicos -:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API deliver the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as behavioral testing. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine- this dramatically increases the maintenance burden +:white_check_mark: **Faça:** Testar os componentes internos gera uma enorme sobrecarga por quase nada. Se o seu código/API fornecer os resultados certos, você deve realmente investir suas próximas 3 horas em testes de COMO funcionou internamente e depois manter esses testes frágeis? Sempre que um comportamento público é verificado, a implementação privada também é implicitamente testada e seus testes serão interrompidos apenas se houver um determinado problema (por exemplo, saída incorreta). Essa abordagem também é chamada de teste comportamental. Por outro lado, se você testar os componentes internos (abordagem de caixa branca) — seu foco muda do planejamento do resultado do componente para detalhes minuciosos e seu teste pode ser interrompido devido a pequenas refatorações de código, embora os resultados sejam bons- isso aumenta drasticamente a carga de manutenção
    -❌ **Otherwise:** Your test behaves like the [child who cries wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shoot out loud false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday a real bug will get ignored… +❌ **Caso Contrário:** Seu teste se comporta como [O Pastor Mentiroso e o Lobo](https://pt.wikipedia.org/wiki/O_Pastor_Mentiroso_e_o_Lobo): gera falsos positivos (por exemplo, um teste falha porque um nome de variável privada foi alterado). Sem surpresa, as pessoas logo começarão a ignorar as notificações de IC até que um dia um bug real seja ignorado…
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti Pattern Example: A test case is testing the internals for no good reason -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Mocha & Chai") +### :thumbsdown: Exemplo Anti-padrão: Um caso de teste está testando os internos sem um bom motivo +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Mocha-blue.svg + "Exemplos com Mocha & Chai") ```javascript class ProductService{ - //this method is only used internally - //Change this name will make the tests fail + //esse método é usado apenas internamente + //Alterar este nome fará com que os testes falhem calculateVAT(priceWithoutVAT){ return {finalPrice: priceWithoutVAT * 1.2}; - //Change the result format or key name above will make the tests fail + //Alterar o formato do resultado ou o nome da chave acima fará com que os testes falhem } - //public method + //método público getPrice(productId){ const desiredProduct= DB.getProduct(productId); finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice; @@ -303,7 +303,7 @@ class ProductService{ 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 + //Não é necessário permitir que os usuários calculem o VAT, apenas mostrem o preço final. No entanto, insistimos aqui falsamente para testar os internos da classe expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0); }); @@ -316,32 +316,32 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response",

    -## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies +## ⚪ ️ ️1.5 Escolha os dublês de teste certos: evite mocks a favor de stubs e spies -:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide an immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **Faça:** Os dublês de teste são um mal necessário, porque são acoplados às aplicações internas, no entanto, alguns fornecem um imenso valor ([Leia aqui um lembrete sobre dublês de teste: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). -Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a smell of white-box testing. +Antes de usar dublês de teste, faça uma pergunta muito simples: Eu o uso para testar funcionalidades que aparecem ou podem aparecer no documento de requisitos? Se não, isso cheira a teste de caixa branca. -For example, if you want to test what your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently +Por exemplo, se você quiser testar se seu aplicativo se comporta razoavelmente quando o serviço de pagamento estiver inativo, você pode desconsiderar (stub) o serviço de pagamento e acionar um retorno ‘No Response’ para garantir que a unidade em teste retorne o valor correto. Isso verifica o comportamento/resposta/resultado do aplicativo em certos cenários. Você também pode usar um spy para afirmar que um email foi enviado quando esse serviço está inoperante — isso é novamente uma verificação comportamental que provavelmente aparecerá em um documento de requisitos (“Envie um email se o pagamento não puder ser salvo”). Por outro lado, se você criar um mock do serviço de pagamento e garantir que ele foi chamado com os tipos de JavaScript certos— então seu teste será focado em itens internos que não tem nada a ver com a funcionalidade do aplicativo e provavelmente mudarão frequentemente
    -❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend +❌ **Caso Contrário:** Qualquer refatoração de código exige a pesquisa de todas as simulações no código e a atualização em conformidade. Os testes se tornam um fardo e não um amigo útil
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti-pattern example: Mocks focus on the internals -![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg - "Examples with Mocha & Chai") +### :thumbsdown: Exemplo Anti-padrão: Mocks foca nos internos +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Sinon-blue.svg + "Exemplo com 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 + //Suponha que já adicionamos um produto const dataAccessMock = sinon.mock(DAL); - //hmmm BAD: testing the internals is actually our main goal here, not just a side-effect + //hmmm RUIM: testar os internos é realmente nosso principal objetivo aqui, não apenas um efeito colateral dataAccessMock.expects("deleteProduct").once().withArgs(DBConfig, theProductWeJustAdded, true, false); new ProductService().deletePrice(theProductWeJustAdded); dataAccessMock.verify(); @@ -349,14 +349,14 @@ it("When a valid product is about to be deleted, ensure data access DAL was call ```
    -### :clap:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals +### :clap: Exemplo Fazendo Certo: spies concentram-se em testar os requisitos, mas como efeito colateral inevitavelmente tocam os internos ```javascript it("When a valid product is about to be deleted, ensure an email is sent", async () => { - //Assume we already added here a product + //Suponha que já adicionamos aqui um produto 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) + //hmmm OK: lidamos com internos? Sim, mas como efeito colateral do teste dos requisitos (envio de um email) }); ``` @@ -366,57 +366,57 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

    -## ⚪ ️1.6 Don’t “foo”, use realistic input data +## ⚪ ️1.6 Não use “foo”, use dados de entrada realistas -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not instead) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? see next bullet (property-based testing). +:white_check_mark: **Faça:** Muitas vezes, os bugs de produção são revelados com informações muito específicas e surpreendentes— quanto mais realista for a entrada de teste, maiores serão as chances de detectar bugs mais cedo. Use bibliotecas dedicadas como [Faker](https://www.npmjs.com/package/faker) gerar dados pseudo-reais que se assemelham à variedade e forma dos dados de produção. Por exemplo, essas bibliotecas podem gerar números de telefone, nomes de usuários, cartões de crédito, nomes de empresas e até mesmo textos 'lorem ipsum' realistas. Você também pode criar alguns testes (além dos testes de unidade) que randomizam os dados dos fakers para esticar sua unidade sob teste ou até importar dados reais do seu ambiente de produção. Quer elevar para o próximo nível? Veja o próximo tópico (teste baseado em propriedades).
    -❌ **Otherwise:** All your development testing will falsely seem green when you use synthetic inputs like “Foo” but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **Caso Contrário:** Todos os seus testes de desenvolvimento parecerão falsamente verdes quando você usar entradas sintéticas como “Foo”, mas a produção poderá ficar vermelha quando um hacker passar uma string desagradável como “@ 3e2ddsf. ## '1 fdsfds. fds432 AAAA ”
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data +### :thumbsdown: Exemplo Anti-padrão: Uma suíte de testes aprovada devido a dados não realistas -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg - "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Jest-blue.svg + "Exemplos com Jest") ```javascript const addProduct = (name, price) =>{ - const productNameRegexNoSpace = /^\S*$/;//no white-space allowd + const productNameRegexNoSpace = /^\S*$/;//nenhum espaço em branco permitido if(!productNameRegexNoSpace.test(name)) - return false;//this path never reached due to dull input + return false;//esse caminho nunca foi alcançado devido a entradas não realistas - //some logic here + //alguma lógica aqui 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 + //A string "Foo" usada em todos os testes nunca dispara um resultado falso const addProductResult = addProduct("Foo", 5); expect(addProductResult).toBe(true); - //Positive-false: the operation succeeded because we never tried with long - //product name including spaces + //Positivo-falso: a operação foi bem-sucedida porque nunca tentamos com + //nome de produto longo incluindo espaços }); ```
    -### :clap:Doing It Right Example: Randomizing realistic input +### :clap: Exemplo Fazendo Certo: Randomizando entrada realista ```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} + //Entrada aleatória gerada: {'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! + //O teste falhou, a entrada aleatória acionou um caminho que nunca planejamos. + //Descobrimos um bug cedo! }); ``` @@ -427,25 +427,25 @@ it("Better: When adding new valid product, get successful confirmation", async (

    -## ⚪ ️ 1.7 Test many input combinations using Property-based testing +## ⚪ ️ 1.7 Teste muitas combinações de entrada usando testes baseados em propriedades -:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +:white_check_mark: **Faça:** Normalmente, escolhemos algumas amostras de entrada para cada teste. Mesmo quando o formato de entrada se assemelha a dados do mundo real (veja o tópico ‘Não use foo’), cobrimos apenas algumas combinações de entrada (method(‘’, true, 1), method(“string” , false” , 0)), No entanto, em produção, uma API chamada com 5 parâmetros pode ser chamada com milhares de permutações diferentes, uma delas pode tornar nosso processo inativo ([consulte Teste do Fuzz](https://pt.wikipedia.org/wiki/Fuzzing)). E se você pudesse escrever um único teste que envie 1000 permutações de entradas diferentes automaticamente e capte para qual entrada nosso código falhou em retornar a resposta correta? O teste baseado em propriedades é uma técnica que faz exatamente isso: ao enviar todas as combinações de entradas possíveis para sua unidade em teste, aumenta a possibilidade de encontrar um bug. Por exemplo, dado um método — addNewProduct(id, name, isDiscount) — as bibliotecas de suporte chamarão esse método com muitas combinações de (number, string, boolean) como (1, “iPhone”, false), (2, “Galaxy”, true). Você pode executar testes baseados em propriedades usando seu test runner favorito (Mocha, Jest, etc) usando bibliotecas como [js-verify](https://github.com/jsverify/jsverify) ou [testcheck](https://github.com/leebyron/testcheck-js) (documentação muito melhor). Atualização: Nicolas Dubien sugere nos comentários abaixo [verificar check-fast](https://github.com/dubzzz/fast-check#readme) que parece oferecer alguns recursos adicionais e também ser mantido ativamente
    -❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs +❌ **Caso Contrário:** Inconscientemente, você escolhe as entradas de teste que cobrem apenas os caminhos de código que funcionam bem. Infelizmente, isso diminui a eficiência dos testes como veículo para expor caminhos de bugs que funcionam bem.
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Testing many input permutations with “mocha-testcheck” +### :clap: Exemplo Fazendo Certo: Testando muitas permutações de entrada com “mocha-testcheck” -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Mocha-blue.svg + "Exemplos usando Mocha") ```javascript require('mocha-testcheck').install(); @@ -453,7 +453,7 @@ const {expect} = require('chai'); describe('Product service', () => { describe('Adding new', () => { - //this will run 100 times with different random properties + //isso será executado 100 vezes com diferentes propriedades aleatórias check.it('Add new product with random yet valid properties, always successful', gen.int, gen.string, (id, name) => { expect(addNewProduct(id, name).status).to.equal('approved'); @@ -470,58 +470,58 @@ describe('Product service', () => {

    -## ⚪ ️ 1.8 If needed, use only short & inline snapshots +## ⚪ ️ 1.8 Se necessário, use apenas snapshots curtas e em linha -:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. +:white_check_mark: **Faça:** Quando houver necessidade de [testes de snapshot](https://jestjs.io/docs/en/snapshot-testing), use apenas snapshots curtas e focadas (ou seja, 3-7 linhas) incluídas como parte do teste ([Snapshot em Linha](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) e não dentro de arquivos externos. Manter essa diretriz garantirá que seus testes continuem auto-explicativos e menos frágeis. -On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much +Por outro lado, os tutoriais e ferramentas de "clássicos de snapshot" incentivam o armazenamento de arquivos grandes (por exemplo. marcação de renderização de componente, resultado JSON da API) em algum meio externo e garantir, sempre que o teste for executado, que seja comparado o resultado recebido com a versão salva. Isso, por exemplo, pode implicitamente associar nosso teste a 1000 linhas com 3000 valores de dados sobre os quais o autor do teste nunca leu e argumentou. Por que isso está errado? Ao fazer isso, existem 1000 razões para o seu teste falhar - basta alterar uma única linha para que a snapshot fique inválida e é provável que isso aconteça muito. Com que frequência? Para cada espaço, comentário ou pequena alteração de CSS/HTML. Não apenas isso, o nome do teste não daria uma pista sobre a falha, pois apenas verifica se 1000 linhas não foram alteradas; também incentiva o redator do teste a aceitar como a verdade desejada um longo documento que ele não pôde inspecionar e verificar. Todos estes são sintomas de teste obscuro e ansioso, que não são focados e têm como objetivo alcançar muito -It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +Vale ressaltar que existem poucos casos em que snapshots longos e externos são aceitáveis - ao afirmar no schema e não nos dados (extrair valores e focar em campos) ou quando o documento recebido raramente muda
    -❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **Caso Contrário:** Um teste de UI falha. O código parece correto, a tela renderiza pixels perfeitos, o que aconteceu? seu teste de snapshot acabou de encontrar uma diferença do documento de origem para o atual recebido - um único caractere de espaço foi adicionado ao markdown...
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code +### :thumbsdown: Exemplo Anti-padrão: Acoplando nosso teste a 2000 linhas de código invisíveis -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg - "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Jest-blue.svg + "Exemplos com Jest") ```javascript it('TestJavaScript.com is renderd correctly', () => { -//Arrange +//Ajeitar -//Act +//Atuar const receivedPage = renderer .create( Test JavaScript < /DisplayPage>) .toJSON(); -//Assert +//Afirmar expect(receivedPage).toMatchSnapshot(); -//We now implicitly maintain a 2000 lines long document -//every additional line break or comment - will break this test +//Agora mantemos implicitamente um documento com 2000 linhas +//cada quebra de linha ou comentário adicional - interromperá este teste }); ```
    -### :clap: Doing It Right Example: Expectations are visible and focused +### :clap: Exemplo Fazendo Certo: As expectativas são visíveis e focadas ```javascript it('When visiting TestJavaScript.com home page, a menu is displayed', () => { -//Arrange +//Ajeitar -//Act +//Atuar receivedPage tree = renderer .create( Test JavaScript < /DisplayPage>) .toJSON(); -//Assert +//Afirmar const menu = receivedPage.content.menu; expect(menu).toMatchInlineSnapshot(` @@ -539,51 +539,51 @@ expect(menu).toMatchInlineSnapshot(`

    -## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test +## ⚪ ️1.9 Evite acessórios de teste e sementes globais, adicione dados por teste -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +:white_check_mark: **Faça:** Seguindo a regra de ouro (tópico 0), cada teste deve adicionar e agir em seu próprio conjunto de linhas de banco de dados para evitar o acoplamento e raciocinar facilmente sobre o fluxo de teste. Na realidade, isso geralmente é violado por testadores que propagam o banco de dados com dados antes de executar os testes ([também conhecido como "acessórios de teste"](https://en.wikipedia.org/wiki/Test_fixture)) por uma questão de melhoria de desempenho. Embora o desempenho seja realmente uma preocupação válida— pode ser mitigado (consulte o tópico "Teste de componentes"), no entanto, a complexidade do teste é uma tarefa muito dolorosa que deve governar outras considerações na maioria das vezes. Na prática, faça com que cada caso de teste inclua explicitamente os registros do banco de dados necessários e atue somente nesses registros. Se o desempenho se tornar uma preocupação crítica — um compromisso equilibrado pode vir na forma de propagação do único conjunto de testes que não está alterando dados (por exemplo, consultas)
    -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **Caso Contrário:** Poucos testes falham, uma implantação é abortada, nossa equipe gastará um tempo precioso agora, temos um bug? Vamos investigar, oh não - parece que dois testes estavam modificando os mesmos dados iniciais
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :thumbsdown: Exemplo Anti-padrão: testes não são independentes e dependem de algum gancho global para alimentar dados globais de banco de dados -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Mocha-blue.svg + "Exemplos com Mocha") ```javascript before(() => { - //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + //adicionando dados de sites e administradores ao nosso banco de dados. Onde estão os dados? lado de fora. Em alguma estrutura json ou de migração externa 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 + //Eu sei que o nome do site "portal" existe - eu vi nos arquivos de sementes 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 + //Eu sei que o nome do site "portal" existe - eu vi nos arquivos de sementes const siteToCheck = await SiteService.getSiteByName("Portal"); - expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ + expect(siteToCheck.name).to.be.equal("Portal"); //Falha! O teste anterior altera o nome :[ }); ```
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: Exemplo Fazendo Certo: Podemos permanecer dentro do teste, cada teste atua em seu próprio conjunto de dados ```javascript it("When updating site name, get successful confirmation", async () => { - //test is adding a fresh new records and acting on the records only + //teste está adicionando novos registros novos e atuando apenas nos registros const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); @@ -600,26 +600,26 @@ it("When updating site name, get successful confirmation", async () => {
    -## ⚪ ️ 1.10 Don’t catch errors, expect them -:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations +## ⚪ ️ 1.10 Não pegue erros, espere-os +:white_check_mark: **Faça:** Ao tentar afirmar que alguma entrada aciona um erro, pode parecer correto usar try-catch-finally e afirmar que a entramos na cláusula catch. O resultado é um caso de teste estranho e detalhado (exemplo abaixo) que oculta a intenção simples do teste e as expectativas do resultado -A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +Uma alternativa mais elegante é o uso da asserção Chai dedicada de uma linha: expect(method).to.throw (ou no Jest: expect(method).toThrow()). É absolutamente obrigatório também garantir que a exceção contenha uma propriedade que indique o tipo de erro; caso contrário, apenas um erro genérico que o aplicativo não poderá fazer muito, em vez de mostrar uma mensagem decepcionante ao usuário
    -❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +❌ **Caso contrário:** Será um desafio deduzir dos relatórios de teste (por exemplo, relatórios de IC) o que deu errado
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch +### :thumbsdown: Exemplo Anti-padrão: Um longo caso de teste que tenta afirmar a existência de erro com try-catch -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Mocha-blue.svg + "Exemplos com Mocha") ```javascript it("When no product name, it throws error 400", async() => { @@ -631,14 +631,14 @@ catch (error) { 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 +//se essa afirmação falhar, os resultados/relatórios dos testes mostrarão apenas +//que algum valor é null, não haverá uma palavra sobre um erro de ausência }); ```
    -### :clap: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM +### :clap: Exemplo Fazendo Certo: Uma expectativa legível por humanos que pode ser entendida facilmente, talvez até pelo controle de qualidade ou pelo gerente de produto ```javascript it.only("When no product name, it throws error 400", async() => { @@ -654,32 +654,32 @@ it.only("When no product name, it throws error 400", async() => {

    -## ⚪ ️ 1.11 Tag your tests +## ⚪ ️ 1.11 Marque seus testes -:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +:white_check_mark: **Faça:** Testes diferentes devem ser executados em diferentes cenários: testes rápidos de fumaça, sem IO, devem ser executados quando um desenvolvedor salva ou dá commit em um arquivo, testes completos de ponta a ponta geralmente são executados quando uma nova pull request é enviada, etc. Isso pode ser alcançado marcando testes com palavras-chave como #cold #api #sanity para que você possa selecionar com sua ferramenta de teste e chamar o subconjunto desejado. Por exemplo, é assim que você invocaria apenas o grupo de teste de sanidade com Mocha: mocha — grep ‘sanity’
    -❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests +❌ **Caso Contrário:** A execução de todos os testes, incluindo testes que executam dezenas de consultas ao banco de dados, sempre que um desenvolvedor faz uma pequena alteração pode ser extremamente lenta e mantém os desenvolvedores longe de executar testes
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) +### :clap: Exemplo Fazendo Certo: Marcando testes como ‘#cold-test’ permite que a ferramenta de teste execute apenas testes rápidos (Cold===testes rápidos que não fazem IO e podem ser executados com freqüência, mesmo quando o desenvolvedor está digitando) -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg - "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Jest-blue.svg + "Exemplos com Jest") ```javascript -//this test is fast (no DB) and we're tagging it correspondigly -//now the user/CI can run it frequently +//esse teste é rápido (sem banco de dados) e estamos marcando de forma correspondente +//agora o usuário/IC pode executá-lo com frequência 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 + //lógica de código aqui }); }); }); @@ -694,14 +694,14 @@ describe('Order service', function() {

    -## ⚪ ️1.12 Other generic good testing hygiene -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +## ⚪ ️1.12 Outra boa higiene genérica para testes +:white_check_mark: **Faça:** Esta postagem é focada em conselhos de teste relacionados ou pelo menos podem ser exemplificados com Node JS. Este tópico, no entanto, agrupa algumas dicas não relacionadas a Node que são bem conhecidas -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +Aprenda e pratique [princípios TDD](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — eles são extremamente valiosos para muitos, mas não se intimidem se não se encaixarem no seu estilo, você não é o único. Considere escrever os testes antes do código em um [estilo vermelho-verde-refatorar](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), certifique-se de que cada teste verifica exatamente uma coisa, quando você encontrar um erro—antes de corrigir, escreva um teste que detectará esse erro no futuro, deixe que cada teste falhe pelo menos uma vez antes de ficar verde, inicie um módulo escrevendo um código rápido e simplista que satisfaça o teste - refatore gradualmente e leve-o a um nível de produção, evitar qualquer dependência do ambiente (caminhos, SO, etc)
    -❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades +❌ **Caso Contrário:** Você sentirá falta das pérolas de sabedoria que foram coletadas por décadas

    @@ -1007,7 +1007,7 @@ test('When users-list is flagged to show only VIP, should display only VIP membe
    -### :thumbsdown: Exemplo Anti-padrão: Asserções misturam detalhes da UI e dados +### :thumbsdown: Exemplo Anti-padrão: Afirmações misturam detalhes da UI e dados ```javascript test('When flagging to show only VIP, should display only VIP members', () => { // Arrange @@ -1102,7 +1102,7 @@ test('Whenever no data is passed, error metric shows zero', () => { ## ⚪ ️ 3.3 Sempre que possível, teste com um componente realista e totalmente renderizado -:white_check_mark: **Faça:** Sempre que tiver um tamanho razoável, teste seu componente de fora como os usuários, renderize a interface do usuário, atue sobre ela e afirme que a interface do usuário renderizada se comporta conforme o esperado. Evite todo tipo de simulação, renderização parcial e superficial - essa abordagem pode resultar em erros não capturados devido à falta de detalhes e dificultar a manutenção, pois os testes interferem nos internos (veja o marcador 'Favorecer o teste de caixa preta'). Se um dos componentes filhos estiver desacelerando significativamente (por exemplo, animação) ou complicando a instalação - considere substituí-lo explicitamente por um falso +:white_check_mark: **Faça:** Sempre que tiver um tamanho razoável, teste seu componente de fora como os usuários, renderize a interface do usuário, atue sobre ela e afirme que a interface do usuário renderizada se comporta conforme o esperado. Evite todo tipo de simulação, renderização parcial e superficial - essa abordagem pode resultar em erros não capturados devido à falta de detalhes e dificultar a manutenção, pois os testes interferem nos internos (veja o tópico 'Favorecer o teste de caixa preta'). Se um dos componentes filhos estiver desacelerando significativamente (por exemplo, animação) ou complicando a instalação - considere substituí-lo explicitamente por um falso Com tudo isso dito, uma palavra de cautela é necessária: essa técnica funciona para componentes pequenos/médios que contêm um tamanho razoável de componentes filhos. A renderização completa de um componente com muitos filhos dificultará o raciocínio sobre falhas de teste (análise de causa raiz) e poderá ficar muito lenta. Nesses casos, escreva apenas alguns testes contra esse componente pai pesado e mais testes contra seus filhos @@ -1704,7 +1704,7 @@ it("Test addNewOrder, don't use such test names", () => { ## ⚪ ️4.4 Impedindo problemas de código de teste com os linters de teste -:white_check_mark: **Faça:** Um conjunto de plugins ESLint foi construído especificamente para inspecionar os padrões de código de testes e descobrir problemas. Por exemplo, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) avisará quando um teste for escrito em nível global (não é filho de uma declaração describe()) ou quando os testes são [pulados](https://mochajs.org/#inclusive-tests) o que pode levar a uma falsa crença de que todos os testes estão passando. Similarmente, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) pode, por exemplo, avisar quando um teste não tem asserções (não verificando nada) +:white_check_mark: **Faça:** Um conjunto de plugins ESLint foi construído especificamente para inspecionar os padrões de código de testes e descobrir problemas. Por exemplo, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) avisará quando um teste for escrito em nível global (não é filho de uma declaração describe()) ou quando os testes são [pulados](https://mochajs.org/#inclusive-tests) o que pode levar a uma falsa crença de que todos os testes estão passando. Similarmente, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) pode, por exemplo, avisar quando um teste não tem afirmações (não verificando nada)
    @@ -1980,7 +1980,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    -# Team +# Time @@ -1990,20 +1990,20 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -**Role:** Writer +**Função:** Escritor -**About:** I'm an independent consultant who works with 500 fortune corporates 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) +**Sobre:** Sou um consultor independente que trabalha com 500 empresas afortunadas e startups de garagem para aprimorar seus aplicativos JS & Node.js. Mais do que qualquer outro tópico, me fascina e tenho como objetivo dominar a arte de testar. Eu também sou o autor de [Melhores práticas do Node.js.](https://github.com/goldbergyoni/nodebestpractices)
    -**Workshop:** 👨‍🏫 Want to learn all these practices and techniques at your offices (Europe & USA)? [Register here for my testing workshop](https://testjavascript.com/) +**Oficina:** 👨‍🏫 Deseja aprender todas essas práticas e técnicas em seus escritórios (Europa & EUA)? [Registre-se aqui para minha oficina de testes](https://testjavascript.com/)
    -**Follow:** +**Siga:** * [🐦 Twitter](https://twitter.com/goldbergyoni/) -* [📞 Contact](https://testjavascript.com/contact-2/) -* [✉️ Newsletter](https://testjavascript.com/newsletter//) +* [📞 Contato](https://testjavascript.com/contact-2/) +* [✉️ Boletim de Notícias](https://testjavascript.com/newsletter//)

    @@ -2012,16 +2012,16 @@ An efficient update policy may allow some ‘vesting period’ — let the c ## [Bruno Scheufler](https://github.com/BrunoScheufler) -**Role:** Tech reviewer and advisor +**Função:** Revisor e consultor técnico -Took care to revise, improve, lint and polish all the texts +Teve o cuidado de revisar, melhorar, usar lint e polir todos os textos -**About:** full-stack web engineer, Node.js & GraphQL enthusiast +**Sobre:** full-stack web engineer, entusiasta de Node.js e GraphQL

    ## [Ido Richter](https://github.com/idori) -**Role:** Concept, design and great advice +**Função:** Conceito, design e ótimos conselhos -**About:** A savvy frontend developer, CSS expert and emojis freak +**Sobre:** Um desenvolvedor front-end esclarecido, especialista em CSS e emojis From 813869a58213344d7e2a33c495c26ffb69812edf Mon Sep 17 00:00:00 2001 From: DouglasMV Date: Sat, 21 Dec 2019 18:09:11 -0300 Subject: [PATCH 174/502] Translated Section 2 --- readme-pt-br.md | 144 ++++++++++++++++++++++++------------------------ 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/readme-pt-br.md b/readme-pt-br.md index 7438c2c3..41963fec 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -31,23 +31,23 @@ Comece entendendo as práticas de teste onipresentes que são a base para qualqu ## `Índice` -#### [`Seção 0: A Regra de ouro`](#section-0️⃣-the-golden-rule) +#### [`Seção 0: A Regra de ouro`](#seção-0️⃣-a-regra-de-ouro) Um único conselho que inspira todos os outros (1 tópico especial) -#### [`Seção 1: A Anatomia do Teste`](#section-1-the-test-anatomy-1) +#### [`Seção 1: A Anatomia do Teste`](#seção-1-a-anatomia-do-teste) A fundação - estruturando testes limpos (12 tópicos) -#### [`Seção 2: Backend`](#section-2️⃣-backend-testing) +#### [`Seção 2: Teste de Backend`](#seção-2️⃣-teste-de-backend) Escrevendo testes de back-end e microsserviços com eficiência (8 tópicos) -#### [`Seção 3: Frontend`](#section-3️⃣-frontend-testing) +#### [`Seção 3: Teste de Frontend`](#seção-3️⃣-teste-de-frontend) Escrevendo testes para interface do usuário da web, incluindo testes de componentes e E2E (11 tópicos) -#### [`Seção 4: Metrificando Testes Efetivamente`](#section-4️⃣-measuring-test-effectiveness) +#### [`Seção 4: Medindo a Eficácia dos Testes`](#seção-4️⃣-medindo-a-eficácia-dos-testes) Observando o vigia - medindo a qualidade do teste (4 tópicos) @@ -583,7 +583,7 @@ it("When querying by site name, get the right site", async () => { ```javascript it("When updating site name, get successful confirmation", async () => { - //teste está adicionando novos registros novos e atuando apenas nos registros + //teste está adicionando registros novos e atuando apenas nos registros const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); @@ -706,38 +706,38 @@ Aprenda e pratique [princípios TDD](https://www.sm-cloud.com/book-review-test-d

    -# Section 2️⃣: Backend Testing +# Seção 2️⃣: Teste de Backend -## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid +## ⚪ ️2.1 Enriqueça seu portfólio de testes: Olhe além dos testes de unidade e da pirâmide -:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **Faça:** A [pirâmide de testes](https://martinfowler.com/bliki/TestPyramid.html), apesar de ter 10> anos de idade, é um modelo excelente e relevante que sugere três tipos de teste e influencia a estratégia de teste da maioria dos desenvolvedores. Ao mesmo tempo, mais de um punhado de novas e brilhantes técnicas de teste surgiram e estão escondidas nas sombras da pirâmide de testes. Dadas todas as mudanças dramáticas que vimos nos últimos 10 anos (Microsserviços, cloud, serverless), é possível que um modelo bastante antigo seja adequado a *todos* os tipos de aplicações? O mundo dos testes não deveria considerar acolher novas técnicas de teste? -Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. +Não me interpretem mal, em 2019 a pirâmide de testes, TDD e testes de unidade ainda são técnicas poderosas e provavelmente são as mais compatíveis para muitas aplicações. Apenas como qualquer outro modelo, apesar de sua utilidade, [às vezes está errado](https://en.wikipedia.org/wiki/All_models_are_wrong). Por exemplo, considere um aplicativo IOT que ingere muitos eventos em um padronizador de mensagens como Kafka/RabbitMQ, que fluem para algum armazenamento de dados e, eventualmente, são consultados por algum UI. Deveríamos realmente gastar 50% do nosso orçamento em testes escrevendo testes de unidade para um aplicativo centrado na integração e quase sem lógica? À medida que a diversidade de tipos de aplicativos aumenta (bots, crypto, Alexa-skills) maiores são as chances de encontrar cenários em que a pirâmide de teste não é a melhor correspondência. -It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks +É hora de enriquecer seu portfólio de testes e se familiarizar com mais tipos de modelos mentais de testes (os próximos tópicos sugerem poucas idéias), como a pirâmide de testes, mas também combinar tipos de teste com problemas do mundo real que você está enfrentando (‘Ei, nossa API está quebrada, vamos escrever testes de contrato orientados ao consumidor!’), diversifique seus testes como um investidor que constrói um portfólio com base na análise de risco — avaliar onde os problemas podem surgir e combinar algumas medidas de prevenção para mitigar esses riscos potenciais -A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] +Uma palavra de cautela: o argumento TDD no mundo do software tem uma cara típica de dicotomia, alguns pregam para usá-lo em todo lugar, outros acham que ele é o diabo. Todo mundo que fala em absoluto está errado :]
    -❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes +❌ **Caso Contrário:** Você perderá algumas ferramentas com um ROI incrível, algumas como Fuzz, lint e mutation podem fornecer valor em 10 minutos
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘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’") +### :clap: Exemplo Fazendo Certo: Cindy Sridharan sugere um rico portfólio de testes em seu incrível post "Testing Microservices - the sane way" +![alt text](assets/bp-12-rich-testing.jpeg "Cindy Sridharan sugere um rico portfólio de testes em seu incrível 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) +☺️Example: [YouTube: “Além dos testes de unidade: 5 tipos de teste Node.JS brilhante (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") +![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "Um nome de teste que constitui 3 partes")
    @@ -747,53 +747,53 @@ A word of caution: the TDD argument in the software world takes a typical false-

    -## ⚪ ️2.2 Component testing might be your best affair +## ⚪ ️2.2 O teste de componentes pode ser o seu melhor caso -:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +:white_check_mark: **Faça:** Cada teste de unidade cobre uma pequena parte do aplicativo e é caro cobrir o todo, enquanto os testes de ponta-a-ponta cobrem muito terreno, mas são escamosos e mais lentos, por que não aplicar uma abordagem equilibrada e escrever testes maiores que os testes unitários, mas menores que os testes de ponta-a-ponta? Teste de componentes é a música desconhecida do mundo dos testes— eles fornecem o melhor dos dois mundos: desempenho razoável e possibilidade de aplicar padrões TDD + cobertura realista e ótima. -Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +Os testes de componentes concentram-se na 'unidade' do Microsservico, eles trabalham contra a API, não fazem mock de nada que pertença ao próprio Microsserviço (por exemplo. banco de dados real ou pelo menos a versão na memória desse banco de dados) mas fazem stub (desconsideram) qualquer coisa externa como chamadas para outros Microsserviços. Ao fazer isso, testamos o que implementamos, abordamos o aplicativo de fora para dentro e obtemos grande confiança em um período de tempo razoável.
    -❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage +❌ **Caso Contrário:** Você pode passar longos dias escrevendo testes de unidade para descobrir que possui apenas 20% de cobertura do sistema
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) +### :clap: Exemplo Fazendo Certo: O Supertest permite abordar a API Express em processo (rápido e cobre muitas camadas) -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Mocha-blue.svg + "Exemplos com 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)") +![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) permite abordar a API Express em processo (rápido e cobre muitas camadas)")


    -## ⚪ ️2.3 Ensure new releases don’t break the API using +## ⚪ ️2.3 Verifique se os novos releases não quebram a API em uso -:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +:white_check_mark: **Faça:** Então, seu Microsserviço possui vários clientes e você executa várias versões do serviço por motivos de compatibilidade (mantendo todos felizes). Então você muda algum campo e ‘boom!’, algum cliente importante que depende desse campo fica irritado. Este é o Catch-22 do mundo da integração: É muito desafiador para o lado do servidor considerar todas as múltiplas expectativas dos clientes— Por outro lado, os clientes não podem realizar nenhum teste porque o servidor controla as datas de lançamento. [Contratos orientados ao consumidor e o framework PACT](https://docs.pact.io/) nasceram para formalizar esse processo com uma abordagem muito perturbadora — não é o servidor que define o plano de teste por si mesmo, mas o cliente define os testes do… servidor! PACT pode gravar a expectativa do cliente e colocar em um local compartilhado, “corretor”, para que o servidor possa puxar as expectativas e executar em cada build usando a biblioteca PACT para detectar contratos quebrados— uma expectativa do cliente que não é atendida. Ao fazer isso, todas as incompatibilidades da API do servidor-cliente são detectadas cedo durante a compilação/IC e podem poupar muita frustração
    -❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear +❌ **Caso Contrário:** As alternativas são exaustivos testes manuais ou medo de implantação
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: +### :clap: Exemplo Fazendo Certo: -![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg - "Examples with PACT") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20PACT-blue.svg + "Exemplos com PACT") ![alt text](assets/bp-14-testing-best-practices-contract-flow.png ) @@ -805,31 +805,31 @@ Component tests focus on the Microservice ‘unit’, they work against the API,

    -## ⚪ ️ 2.4 Test your middlewares in isolation +## ⚪ ️ 2.4 Teste seus Middlewares isoladamente -:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +:white_check_mark: **Faça:** Muitos evitam os testes de Middleware porque representam uma pequena parte do sistema e requerem um servidor Express ativo. Ambas as razões estão erradas — Os Middlewares são pequenos, mas afetam todas ou a maioria das solicitações e podem ser testados facilmente como funções puras que recebem objetos JS {req, res}. Para testar uma função de middleware, basta invocá-la e espionar ([usando o Sinon por exemplo](https://www.npmjs.com/package/sinon)) na interação com os objetos {req, res} para garantir que a função executou a ação correta. A biblioteca [node-mock-http](https://www.npmjs.com/package/node-mocks-http) vai ainda mais longe e fatora os objetos {req, res}, além de espionar seu comportamento. Por exemplo, ela pode afirmar se o status http que foi definido no objeto res corresponde à expectativa (veja o exemplo abaixo)
    -❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests +❌ **Caso Contrário:** Um bug no middleware Express === um bug em todas ou na maioria das solicitações
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :clap: Exemplo Fazendo Certo: Testando o middleware isoladamente sem emitir chamadas de rede e acordar toda a máquina Express -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg - "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Jest-blue.svg + "Exemplos com Jest") ```javascript -//the middleware we want to test +//o middleware que queremos testar const unitUnderTest = require('./middleware') const httpMocks = require('node-mocks-http'); -//Jest syntax, equivelant to describe() & it() in Mocha +//Sintaxe Jest, equivalente a describe() & it() no Mocha test('A request without authentication header, should return http status 403', () => { const request = httpMocks.createRequest({ method: 'GET', @@ -852,29 +852,29 @@ test('A request without authentication header, should return http status 403', (

    -## ⚪ ️2.5 Measure and refactor using static analysis tools -:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) +## ⚪ ️2.5 Meça e refatore usando ferramentas de análise estática +:white_check_mark: **Faça:** O uso de ferramentas de análise estática ajuda a fornecer maneiras objetivas de melhorar a qualidade do código e manter seu código sustentável. Você pode adicionar ferramentas de análise estática à sua compilação de IC para abortar quando encontrar mal cheiros no código. Suas principais vantagens em relação a usar simplesmente um linter são a capacidade de inspecionar a qualidade no contexto de vários arquivos (por exemplo. detectar duplicações), executar análise avançada (por exemplo, complexidade do código) e seguir o histórico e o progresso dos problemas de código. Dois exemplos de ferramentas que você pode usar são: [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) e [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) -Credit:: [Keith Holliday](https://github.com/TheHollidayInn) +Créditos:: [Keith Holliday](https://github.com/TheHollidayInn)
    -❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix +❌ **Caso Contrário:** Com baixa qualidade de código, bugs e desempenho sempre serão um problema que nenhuma nova biblioteca brilhante ou recursos avançados podem corrigir
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: +### :clap: Exemplo Fazendo Certo: CodeClimate, uma ferramenta comercial que pode identificar métodos complexos: -![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg - "Examples with CodeClimate") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Code%20Climate-blue.svg + "Exemplos com CodeClimate") -![alt text](assets/bp-16-yoni-goldberg-quality.png " CodeClimat, a commercial tool that can identify complex methods:") +![alt text](assets/bp-16-yoni-goldberg-quality.png " CodeClimat, uma ferramenta comercial que pode identificar métodos complexos:")
    @@ -883,72 +883,72 @@ Credit::
    -## ⚪ ️2.7 Avoid global test fixtures and seeds, add data per-test +## ⚪ ️2.7 Evite acessórios de teste e sementes globais, adicione dados por teste -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +:white_check_mark: **Faça:** Seguindo a regra de ouro (tópico 0), cada teste deve adicionar e agir em seu próprio conjunto de linhas de banco de dados para evitar o acoplamento e raciocinar facilmente sobre o fluxo de teste. Na realidade, isso geralmente é violado por testadores que propagam o banco de dados com dados antes de executar os testes (também conhecido como "acessórios de teste") por uma questão de melhoria de desempenho. Embora o desempenho seja realmente uma preocupação válida— pode ser mitigado (consulte o tópico "Teste de componentes"), no entanto, a complexidade do teste é uma tarefa muito dolorosa que deve governar outras considerações na maioria das vezes. Na prática, faça com que cada caso de teste inclua explicitamente os registros do banco de dados necessários e atue somente nesses registros. Se o desempenho se tornar uma preocupação crítica — um compromisso equilibrado pode vir na forma de propagação do único conjunto de testes que não está alterando dados (por exemplo, consultas)
    -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **Caso Contrário:** Poucos testes falham, uma implantação é abortada, nossa equipe gastará um tempo precioso agora, temos um bug? Vamos investigar, oh não - parece que dois testes estavam modificando os mesmos dados iniciais
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :thumbsdown: exemplo Anti-padrão: testes não são independentes e dependem de algum gancho global para alimentar dados globais de banco de dados -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Mocha") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Mocha-blue.svg + "Exemplos com Mocha") ```javascript before(() => { - //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + //adicionando dados de sites e administradores ao nosso banco de dados. Onde estão os dados? Do lado de fora. Em alguma estrutura json ou de migração externa 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 + //Eu sei que o nome do site "portal" existe - eu vi nos arquivos de sementes 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 + //Eu sei que o nome do site "portal" existe - eu vi nos arquivos de sementes const siteToCheck = await SiteService.getSiteByName("Portal"); - expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ + expect(siteToCheck.name).to.be.equal("Portal"); //Falha! O teste anterior altera o nome :[ }); ```
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: Exemplo Fazendo Certo: Podemos permanecer dentro do teste, cada teste atua em seu próprio conjunto de dados ```javascript it("When updating site name, get successful confirmation", async () => { - //test is adding a fresh new records and acting on the records only + //teste está adicionando registros novos e atuando apenas nos registros const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); @@ -1584,7 +1584,7 @@ cy.eyesCheckWindow('mark as completed');

    -# Section 4️⃣: Medindo a eficácia dos testes +# Seção 4️⃣: Medindo a Eficácia dos Testes

    From 350bf83df13ffe1f69a1bb127650a580399c9d9b Mon Sep 17 00:00:00 2001 From: DouglasMV Date: Sun, 22 Dec 2019 22:30:47 -0300 Subject: [PATCH 175/502] Section 5 and update based on main file --- readme-pt-br.md | 271 +++++++++++++++++++++++++++--------------------- 1 file changed, 151 insertions(+), 120 deletions(-) diff --git a/readme-pt-br.md b/readme-pt-br.md index 41963fec..719f65f2 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -26,6 +26,13 @@ Comece entendendo as práticas de teste onipresentes que são a base para qualqu * Venha me ouvir falar em [LA](https://js.la/), [Verona](https://2019.nodejsday.it/), [Kharkiv](https://kharkivjs.org/), [free webinar](https://zoom.us/webinar/register/1015657064375/WN_Lzvnuv4oQJOYey2jXNqX6A). Eventos futuros TBD * [Newsletter informativo de qualidade sobre JavaScript](https://testjavascript.com/newsletter/) - insights e conteúdo apenas em assuntos estratégicos +
    + +### 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) +* Deseja traduzir para o seu próprio idioma? abra uma issue 💜 +

    @@ -228,7 +235,7 @@ test("When asking for an admin, ensure only ordered admins in results" , () => { //supondo que adicionamos aqui dois administradores "admin1", "admin2" e "user1" const allAdmins = getUsers({adminOnly:true}); - const admin1Found, adming2Found = false; + let admin1Found, adming2Found = false; allAdmins.forEach(aSingleUser => { if(aSingleUser === "user1"){ @@ -290,7 +297,7 @@ it("When asking for an admin, ensure only ordered admins in results" , () => { class ProductService{ //esse método é usado apenas internamente //Alterar este nome fará com que os testes falhem - calculateVAT(priceWithoutVAT){ + calculateVATAdd(priceWithoutVAT){ return {finalPrice: priceWithoutVAT * 1.2}; //Alterar o formato do resultado ou o nome da chave acima fará com que os testes falhem } @@ -298,6 +305,7 @@ class ProductService{ getPrice(productId){ const desiredProduct= DB.getProduct(productId); finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice; + return finalPrice; } } @@ -357,6 +365,7 @@ 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); //hmmm OK: lidamos com internos? Sim, mas como efeito colateral do teste dos requisitos (envio de um email) + expect(spy.calledOnce).to.be.true; }); ``` @@ -442,25 +451,25 @@ it("Better: When adding new valid product, get successful confirmation", async (
    -### :clap: Exemplo Fazendo Certo: Testando muitas permutações de entrada com “mocha-testcheck” +### :clap: Exemplo Fazendo Certo: Testando muitas permutações de entrada com “fast-check” -![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Mocha-blue.svg - "Exemplos usando Mocha") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Jest-blue.svg + "Exemplos usando Jest") ```javascript -require('mocha-testcheck').install(); -const {expect} = require('chai'); +import fc from "fast-check"; -describe('Product service', () => { - describe('Adding new', () => { +describe("Product service", () => { + describe("Adding new", () => { //isso será executado 100 vezes com diferentes propriedades aleatórias - check.it('Add new product with random yet valid properties, always successful', - gen.int, gen.string, (id, name) => { - expect(addNewProduct(id, name).status).to.equal('approved'); - }); - }) -}); - + 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"); + }) + )); + }); + }); ```
    @@ -517,7 +526,7 @@ it('When visiting TestJavaScript.com home page, a menu is displayed', () => { //Ajeitar //Atuar -receivedPage tree = renderer +const receivedPage tree = renderer .create( Test JavaScript < /DisplayPage>) .toJSON(); @@ -625,8 +634,8 @@ Uma alternativa mais elegante é o uso da asserção Chai dedicada de uma linha: it("When no product name, it throws error 400", async() => { let errorWeExceptFor = null; try { - const result = await addNewProduct({name:'nest'});} -catch (error) { + const result = await addNewProduct({}); + } catch (error) { expect(error.code).to.equal('InvalidInput'); errorWeExceptFor = error; } @@ -642,7 +651,7 @@ expect(errorWeExceptFor).not.to.be.null; ```javascript it.only("When no product name, it throws error 400", async() => { - expect(addNewProduct)).to.eventually.throw(AppError).with.property('code', "InvalidInput"); + await expect(addNewProduct({})).to.eventually.throw(AppError).with.property('code', "InvalidInput"); }); ``` @@ -1070,7 +1079,7 @@ test('When flagging to show only VIP, should display only VIP members', () => { // Act const { getByTestId } = render(); - expect(getByTestId('errorsLabel')).text()).toBe("0"); + expect(getByTestId('errorsLabel').text()).toBe("0"); }); ``` @@ -1445,7 +1454,7 @@ it('When doing smoke testing over all page, should load them all successfully', ### :clap: Exemplo Fazendo Certo: Descrevendo testes em linguagem humana usando cucumber-js -![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Cocumber-blue.svg "Exemplos usando Cucumber") +![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Cucumber-blue.svg "Exemplos usando Cucumber") ```javascript // this is how one can describe tests using cucumber: plain language that allows anyone to understand and collaborate @@ -1466,10 +1475,12 @@ Feature: Twitter new tweet ### :clap: Exemplo Fazendo Certo: Visualizando nossos componentes, seus vários estados e entradas usando o Storybook ![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Usando StoryBook") +![alt text](assets/story-book.jpg "Storybook") -
    +
    +

    ## ⚪ ️ 3.11 Detecte problemas visuais com ferramentas automatizadas @@ -1498,7 +1509,7 @@ Feature: Twitter new tweet ### :clap: Exemplo Fazendo Certo: Configurando o wraith para capturar e comparar instantâneos da UI ![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Wraith-blue.svg - "Usando Cypress para ilustrar a idea") + "Usando Wraith") ``` ​# Add as many domains as necessary. Key will act as a label​ @@ -1529,48 +1540,25 @@ paths: ### :clap: Exemplo Fazendo Certo: Usando Applitools para obter comparação de captura instantânea e outros recursos avançados ![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20AppliTools-blue.svg - "Usando Cypress to para ilustrar a idea") ![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Cypress-blue.svg + "Usando AppliTools") ![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Cypress-blue.svg "Usando Cypress para illustrar 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'); - -}); - + 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'); + }); }); ``` @@ -1738,27 +1726,27 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# Section 5️⃣: CI and Other Quality Measures +# Seção 5️⃣: IC e Outras Medidas de Qualidade

    -## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues +## ⚪ ️ 5.1 Enriqueça seus linters e aborte construções que tenham problemas de lint -:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash._map(…) +:white_check_mark: **Faça:** Linters são um almoço grátis, com 5 minutos de configuração, você obtém gratuitamente um piloto automático que protege seu código e captura de problemas significativos enquanto digita. Já se foram os dias em que linting era apenas por beleza (sem ponto e vírgula!). Hoje em dia, Linters podem detectar problemas graves, como erros que não são lançados corretamente e perda de informações. Além do seu conjunto básico de regras (como [ESLint padrão](https://www.npmjs.com/package/eslint-plugin-standard) ou [estilo Airbnb](https://www.npmjs.com/package/eslint-config-airbnb)), considere incluir alguns Linters especializados como [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) que pode descobrir testes sem asserções, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) pode descobrir promessas sem resolução (seu código nunca vai continuar), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) que pode descobrir expressões regulares inseguras que podem ser usadas para ataques do DOS e[eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) é capaz de alarmar quando o código usa métodos da biblioteca de utilitários que fazem parte dos métodos principais do V8, como Lodash._map(…)
    -❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day +❌ **Caso Contrário:** Considere um dia chuvoso em que sua produção continua travando, mas os logs não exibem o rastreamento do stack de erros. O que aconteceu? Seu código lançou um objeto sem erro por engano e o rastreamento do stack foi perdido, uma boa razão para bater a cabeça contra uma parede de tijolos. Uma configuração de linter de 5 minutos pode detectar esse erro de DIGITAÇÃO e salvar seu dia
    -
    Code Examples +
    Códigos de Exemplo
    -### :thumbsdown: Anti Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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") +### :thumbsdown: Exemplo Anti-padrão: O objeto sem a propriedade erro é lançado por engano, nenhum rastreamento do stack será exibido para esse erro. Felizmente, o ESLint capta o próximo bug de produção +![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "O objeto sem a propriedade erro é lançado por engano, nenhum rastreamento do stack será exibido para esse erro. Felizmente, o ESLint capta o próximo bug de produção")
    @@ -1767,24 +1755,24 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI +# ⚪ ️ 5.2 Encurte o ciclo de feedback com o IC de desenvolvedor local -:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +:white_check_mark: **Faça:** Usando uma IC com inspeções de qualidade brilhantes, como testes, linting, verificação de vulnerabilidades, etc? Ajude os desenvolvedores a executar esse pipeline também localmente para solicitar feedback instantâneo e diminuir o [ciclo de feedback](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Por quê? um processo de teste eficiente constitui muitos loops iterativos: (1) tentativas -> (2) feedback -> (3) refatoração. Quanto mais rápido o feedback, mais iterações de aprimoramento um desenvolvedor pode executar por módulo e aperfeiçoar os resultados. Por outro lado, quando o feedback chegar atrasado, menos iterações de melhoria poderão ser agrupadas em um único dia, a equipe já pode ter avançado para outro tópico/tarefa/módulo e pode não estar apta a refinar esse módulo. -Practically, some CI vendors (Example: [CircleCI load CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +Na prática alguns fornecedores de IC (exemplo: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) permitir a execução do pipeline localmente. Algumas ferramentas comerciais como [wallaby fornece informações valiosas e intuições de teste](https://wallabyjs.com/) como um protótipo de desenvolvedor (sem afiliação). Como alternativa, você pode apenas adicionar um npm script no package.json que executa todos os comandos de qualidade (por exemplo teste, lint, vulnerabilidades) — use ferramentas como [concurrently](https://www.npmjs.com/package/concurrently) para paralelismo e código de saída diferente de zero, se uma das ferramentas falhar. Agora o desenvolvedor deve apenas chamar um comando— por exemplo ‘npm run quality’ — para obter feedback instantâneo. Considere também abortar um commit se a verificação de qualidade falhar usando um githook ([husky pode ajudar](https://github.com/typicode/husky))
    -❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact +❌ **Caso Contrário:** Quando os resultados da qualidade chegam no dia seguinte ao código, o teste não se torna uma parte fluente do desenvolvimento, e sim um artefato formal após o fato
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: Exemplo Fazendo Certo: npm scripts que realizam inspeção de qualidade de código, todos são executados em paralelo sob demanda ou quando um desenvolvedor está tentando enviar um novo código ```javascript "scripts": { "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", @@ -1811,24 +1799,24 @@ Practically, some CI vendors (Example: [CircleCI load CLI](https://circleci.com/

    -# ⚪ ️5.3 Perform e2e testing over a true production-mirror +# ⚪ ️5.3 Realize testes e2eem um verdadeiro espelho de produção -:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. +: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. -The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +O enorme ecossistema Kubernetes ainda não formalizou uma ferramenta conveniente padrão para espelhamento local e de IC, embora muitas novas ferramentas sejam lançadas com frequência. Uma abordagem é executar ‘minimized-Kubernetes’ usando ferramentas como [Minikube](https://kubernetes.io/docs/setup/minikube/) e [MicroK8s](https://microk8s.io/) que se assemelham-se à coisa real só vêm com menos sobrecarga. Outra abordagem é testar em um ambiente remoto ‘real-Kubernetes’, alguns provedores de IC (por exemplo, [Codefresh](https://codefresh.io/)) possuem integração nativa com ambiente Kubernetes e facilitam a execução do pipeline do IC sobre o ambiente real, outros permitem scripts personalizados em um Kubernetes remoto.
    -❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated +❌ **Caso Contrário:** O uso de tecnologias diferentes para demandas de produção e teste mantém dois modelos de implantação e mantém os desenvolvedores e a equipe de operações separados
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly
    ([Credit: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) +### :clap: Exemplo: um pipeline de IC que gera clusters Kubernetes em tempo real ([Créditos: 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
    @@ -1840,21 +1828,21 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo

    -## ⚪ ️5.4 Parallelize test execution -:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes +## ⚪ ️5.4 Paralelizar a execução do teste +:white_check_mark: **Faça:** Quando bem feito, o teste é seu amigo 24/7, fornecendo feedback quase instantâneo. Na prática, a execução de 500 testes de unidade limitados à CPU em um único thread pode levar muito tempo. Felizmente, executores de teste modernos e plataformas de IC (como [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) e [extenções Mocha](https://github.com/yandex/mocha-parallel-tests)) podem paralelizar os testes em vários processos e obter uma melhoria significativa no tempo de feedback. Alguns fornecedores de IC também paralelam testes entre contêineres (!) o que reduz ainda mais o ciclo de feedback. Seja localmente em vários processos ou em alguma ILC na nuvem usando várias máquinas— paralelizando a demanda, mantendo os testes autônomos, pois cada um pode ser executado em diferentes processos -❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant +❌ **Caso Contrário:** Obter resultados de testes 1 hora após o envio do novo código, enquanto você codifica os próximos recursos, é uma ótima receita para tornar os testes menos relevantes
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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)") +### :clap: Exemplo Fazendo Certo: Mocha parallel & Jest superam facilmente o Mocha tradicional graças ao teste de paralelização ([Créditos: 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 superam facilmente o Mocha tradicional graças ao teste de paralelização (Créditos: JavaScript Test-Runners Benchmark)")
    @@ -1863,24 +1851,24 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo

    -## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights +## ⚪ ️5.5 Fique longe de questões legais usando licença e verificação de plágio +:white_check_mark: **Faça:** Problemas de licenciamento e plágio provavelmente não são sua principal preocupação no momento, mas por que não resolver isso também em 10 minutos? Um monte de pacotes npm como [license check](https://www.npmjs.com/package/license-checker) e [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (comercial com plano gratuito) podem ser facilmente incorporado ao seu pipeline de IC e inspecionar problemas, como dependências com licenças restritivas ou código que foi copiado e colado do Stackoverflow e aparentemente viola alguns direitos autorais -❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues +❌ **Caso Contrário:** Involuntariamente, os desenvolvedores podem usar pacotes com licenças inadequadas ou copiar e colar código comercial e enfrentar problemas legais
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Doing It Right Example: +### :clap: Exemplo Fazendo Certo: ```javascript -//install license-checker in your CI environment or also locally +//instale license-checker no seu ambiente IC ou também localmente 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 +//solicite que verifique todas as licenças e falhe com o código de saída diferente de 0 se encontrar uma licença não autorizada. O sistema de IC deve detectar essa falha e interromper a construção license-checker --summary --failOn BSD ``` @@ -1896,22 +1884,20 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.6 Constantly inspect for vulnerable dependencies -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like license check and plagiarism check (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights -
    - +## ⚪ ️5.6 Inspecionar constantemente as dependências vulneráveis +:white_check_mark: **Faça:** Mesmo as dependências mais respeitáveis, como o Express, têm vulnerabilidades conhecidas. Isso pode ser facilmente domado usando ferramentas da comunidade, como [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), ou ferramentas comerciais como [snyk](https://snyk.io/) (oferece também uma versão comunitária gratuita). Ambos podem ser chamados a partir do seu IC em cada build -❌ **Otherwise:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +❌ **Caso Contrário:** Para manter seu código livre de vulnerabilidades sem ferramentas dedicadas, é necessário seguir constantemente as publicações on-line sobre novas ameaças. Bastante tedioso
    -
    Code Examples +
    Códigos de Exemplos
    -### :clap: Example: NPM Audit result -![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") +### :clap: Exemplo: Resultado de NPM Audit +![alt text](assets/bp-26-npm-audit-snyk.png "Resultado de NPM Audit")
    @@ -1920,28 +1906,28 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.7 Automate dependency updates -:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: +## ⚪ ️5.7 Automatize atualizações de dependência +:white_check_mark: **Faça:** Yarn e npm recentemente incluiram package-lock.json que introduziu um sério desafio (o caminho para o inferno é pavimentado com boas intenções) — por padrão agora, os pacotes não estão mais recebendo atualizações. Mesmo uma equipe executando muitas implantações novas com ‘npm install’ & ‘npm update’ não receberão novas atualizações. Isso leva a versões abaixo dos pacotes de dependência, na melhor das hipóteses, ou ao código vulnerável, na pior das hipóteses. As equipes agora contam com a boa vontade e a memória dos desenvolvedores para atualizar manualmente o package.json ou usar ferramentas [como ncu](https://www.npmjs.com/package/npm-check-updates) manualmente. Uma maneira mais confiável seria automatizar o processo de obtenção das versões de dependência mais confiáveis, embora não haja uma solução certeira ainda, existem duas vias de automação possíveis: -(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. +(1) O IC pode falhar nas construções que possuem dependências obsoletas — usando ferramentas como [‘npm outdated’](https://docs.npmjs.com/cli/outdated) ou ‘npm-check-updates (ncu)’ . Fazer isso forçará os desenvolvedores a atualizar dependências. -(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). +(2) Use ferramentas comerciais que varrem o código e enviam automaticamente pull requests com dependências atualizadas. Uma questão interessante que resta é qual deve ser a política de atualização de dependência— a atualização em cada patch gera muitos custos indiretos; a atualização quando uma versão nova é lançado pode apontar para uma versão instável (muitos pacotes são descobertos como vulneráveis ​​nos primeiros dias após o lançamento, [veja o](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) incidente do eslint-scope). -An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +Uma política de atualização eficiente pode permitir um ‘período de acomodação’ — deixe o código ficar atrás do @latest por algum tempo e versões antes de considerar a cópia local como obsoleta (por exemplo. versão local é 1.3.1 e a versão do repositório é 1.3.8)
    -❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky +❌ **Caso Contrário:** Sua produção executará pacotes que foram explicitamente marcados pelo autor como arriscados
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions -![alt text](assets/bp-27-yoni-goldberg-npm.png "Nncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions") +### :clap: Exemplo: [ncu](https://www.npmjs.com/package/npm-check-updates) pode ser usado manualmente ou em um pipeline de IC para detectar quanto o código está atrasado em relação às versões mais recentes +![alt text](assets/bp-27-yoni-goldberg-npm.png "ncu pode ser usado manualmente ou em um pipeline de IC para detectar quanto o código está atrasado em relação às versões mais recentes")
    @@ -1949,32 +1935,32 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    -## ⚪ ️ 5.8 Other, non-Node related, CI tips -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +## ⚪ ️ 5.8 Outras dicas de IC não relacionadas ao Node +:white_check_mark: **Faça:** Esta postagem é focada em conselhos de teste relacionados ou pelo menos podem ser exemplificados com o Node JS. Este marcador, no entanto, agrupa algumas dicas não relacionadas ao nó que são bem conhecidas -
    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
    +
    1. Use uma sintaxe declarativa. Essa é a única opção para a maioria dos fornecedores, mas as versões mais antigas do Jenkins permitem o uso de código ou interface do usuário.
    2. Opte por um fornecedor que tenha suporte nativo ao Docker
    3. Falhe cedo, execute seus testes mais rápidos primeiro. Crie uma etapa/meta de "Teste de fumaça" que agrupe várias inspeções rápidas (por exemplo linting, testes unitários) e fornecer feedback instantâneo para o responsável pelo código
    4. Facilite a varredura de todos os artefatos de construção, incluindo relatórios de teste, relatórios de cobertura, relatórios de mutação, logs, etc.
    5. Crie vários pipelines/trabalhos para cada evento, reutilize as etapas entre eles. Por exemplo, configure um trabalho para commits de branches de recursos e outro para PR na master. Permita que cada uma reutilize a lógica usando etapas compartilhadas (a maioria dos fornecedores fornece algum mecanismo para reutilização de código)
    6. Nunca incorpore segredos em uma declaração de trabalho, pegue-os em um armazenamento secreto ou na configuração do trabalho
    7. Explicitamente aumente a versão em uma compilação de versão ou pelo menos garanta que o desenvolvedor o fez
    8. Compileapenas uma vez e execute todas as inspeções no artefato de construção único (por exemplo, imagem do Docker)
    9. Teste em um ambiente efêmero que não varia de estado entre compilações. Armazenar em cache node_modules pode ser a única exceção

    -❌ **Otherwise:** You‘ll miss years of wisdom +❌ **Caso Contrário:** Você perderá anos de sabedoria

    -## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions -:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +## ⚪ ️ 5.9 Matriz de construção: execute as mesmas etapas de IC usando várias versões do Node +:white_check_mark: **Faça:** A verificação da qualidade é sobre acaso, quanto mais você cobrir, mais sorte terá na detecção de problemas mais cedo. Ao desenvolver pacotes reutilizáveis ​​ou executar uma produção de vários clientes com várias configurações e versões do Node, o IC deve executar o pipeline de testes em todas as permutações de configurações. Por exemplo, supondo que usamos o MySQL para alguns clientes e o Postgres para outros — alguns fornecedores de IC suportam um recurso chamado "Matriz" que permitem executar o processo de teste contra todas as permutações do MySQL, Postgres e várias versões do Node, como 8, 9 e 10. Isso é feito usando a configuração apenas sem nenhum esforço adicional (supondo que você tenha testes ou quaisquer outras verificações de qualidade). Outros ICs que não suportam Matrix podem ter extensões ou ajustes para permitir isso
    -❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? +❌ **Caso Contrário:** Então, depois de fazer todo esse trabalho duro de escrever testes, vamos permitir que os bugs entrem apenas por causa de problemas de configuração?
    -
    Code Examples +
    Códigos de Exemplo
    -### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +### :clap: Exemplo: Usando a definição de construção do Travis (fornecedor de IC) para executar o mesmo teste em várias versões do Node
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    @@ -2009,8 +1995,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    - -## [Bruno Scheufler](https://github.com/BrunoScheufler) +## [Bruno Scheufler](https://github.com/BrunoScheufler) **Função:** Revisor e consultor técnico @@ -2025,3 +2010,49 @@ Teve o cuidado de revisar, melhorar, usar lint e polir todos os textos **Função:** Conceito, design e ótimos conselhos **Sobre:** Um desenvolvedor front-end esclarecido, especialista em CSS e emojis + +## [Kyle Martin](https://github.com/js-kyle) + +**Função:** Ajuda a manter esse projeto em execução e analisa práticas relacionadas à segurança + +**Sobre:** Adora trabalhar em projetos Node.js. e segurança de aplicativos da web. + +## Contribuidores ✨ +Agradecemos a essas pessoas maravilhosas que contribuíram para este repositório! + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Scott Davis
    Scott Davis

    🖋
    Adrien REDON
    Adrien REDON

    🖋
    Stefano Magni
    Stefano Magni

    🖋
    Yeoh Joer
    Yeoh Joer

    🖋
    Jhonny Moreira
    Jhonny Moreira

    🖋
    Ian Germann
    Ian Germann

    🖋
    Hafez
    Hafez

    🖋
    Ruxandra Fediuc
    Ruxandra Fediuc

    🖋
    Jack
    Jack

    🖋
    Peter Carrero
    Peter Carrero

    🖋
    Huhgawz
    Huhgawz

    🖋
    Haakon Borch
    Haakon Borch

    🖋
    Jaime Mendoza
    Jaime Mendoza

    🖋
    Cameron Dunford
    Cameron Dunford

    🖋
    John Gee
    John Gee

    🖋
    Aurelijus Rožėnas
    Aurelijus Rožėnas

    🖋
    Aaron
    Aaron

    🖋
    Tom Nagle
    Tom Nagle

    🖋
    Yves yao
    Yves yao

    🖋
    Userbit
    Userbit

    🖋
    + + + + From 8be1cdd8ecbff5009f249f0245beb38dcef58b33 Mon Sep 17 00:00:00 2001 From: DouglasMV Date: Mon, 23 Dec 2019 02:24:05 -0300 Subject: [PATCH 176/502] fixes --- readme.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index 0bb34f5d..153796d6 100644 --- a/readme.md +++ b/readme.md @@ -169,7 +169,7 @@ describe('Products Service', function() { ![](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 Jest") + "Examples with Mocha") ```javascript describe('Customer classifier', () => { @@ -344,7 +344,7 @@ For example, if you want to test that your app behaves reasonably when the payme ### :thumbsdown: Anti-pattern example: Mocks focus on the internals ![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg - "Examples with Mocha & Chai") + "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 @@ -566,7 +566,7 @@ expect(menu).toMatchInlineSnapshot(` ### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Jest") + "Examples with Mocha") ```javascript before(() => { @@ -628,7 +628,7 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e ### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Jest") + "Examples with Mocha") ```javascript it("When no product name, it throws error 400", async() => { @@ -776,7 +776,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Jest") + "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)") @@ -1201,8 +1201,9 @@ test('Shallow/mocked approach: When clicked to show filters, filters are display ### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) -![](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 +![](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 @@ -1300,7 +1301,7 @@ test('movie title appears', async () => { ### :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%20Jest-blue.svg + "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Examples with react-testing-library") ```javascript @@ -1663,7 +1664,7 @@ Knowing that all or most of the mutations were killed gives much higher confiden ### :thumbsdown: Anti Pattern Example: 100% coverage, 0% testing ![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg - "Using Cypress to illustrate the idea") + "Using Stryker") ```javascript function addNewOrder(newOrder) { logger.log(`Adding new order ${newOrder}`); @@ -1759,7 +1760,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test :white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. -Practically, some CI vendors (Example: [CircleCI load CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky))
    @@ -1927,7 +1928,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    ### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions -![alt text](assets/bp-27-yoni-goldberg-npm.png "Nncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest 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")
    From 4e37dd34f6bf66c46e44b6e4f622419c8dd20783 Mon Sep 17 00:00:00 2001 From: DouglasMV Date: Mon, 23 Dec 2019 02:28:53 -0300 Subject: [PATCH 177/502] fixes --- readme-pt-br.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/readme-pt-br.md b/readme-pt-br.md index 719f65f2..273109cb 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -1201,8 +1201,9 @@ test('Shallow/mocked approach: When clicked to show filters, filters are display ### :clap: Exemplo Fazendo Certo: API E2E que resolve somente quando as operações assíncronas são concluídas (Cypress) -![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React-blue.svg - "Exemplos com React") ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React%20Testing%20Library-blue.svg +![](https://img.shields.io/badge/🔨%20Exemplo%20usando%20Cypress-blue.svg + "Usando Cypress para ilustrar a ideia") +![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React%20Testing%20Library-blue.svg "Exemplos com react-testing-library") ```javascript @@ -1300,7 +1301,7 @@ test('movie title appears', async () => { ### :clap: Exemplo Fazendo Certo: fazendo o esboço ou interceptando chamadas de API ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React-blue.svg - "Exemplos com React") ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Jest-blue.svg + "Exemplos com React") ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20React%20Testing%20Library.svg "Exemplos com react-testing-library") ```javascript From 7883dc89039ee44f1c71a021fcfbff4b0e31f6de Mon Sep 17 00:00:00 2001 From: Jens Oliver Meiert Date: Wed, 8 Jan 2020 17:32:14 +0100 Subject: [PATCH 178/502] style: fix typos, improve punctuation, make formatting consistent Felt free to fix a few things and align formatting. --- readme.md | 185 +++--------------------------------------------------- 1 file changed, 8 insertions(+), 177 deletions(-) diff --git a/readme.md b/readme.md index 0bb34f5d..7e90df07 100644 --- a/readme.md +++ b/readme.md @@ -9,11 +9,9 @@ ## 📗 45+ best practices: Super-comprehensive and exhaustive This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and curates for you dozens of the best blog posts, books and tools the market has to offer - ## 🚢 Advanced: Goes 10,000 miles beyond the basics Hop into a journey that travels way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average - ## 🌐 Full-stack: front, backend, CI, anything Start by understanding the ubiquitous testing practices that are the foundation for any application tier. Then, delve into your area of choice: frontend/UI, backend, CI or maybe all of them? @@ -33,7 +31,6 @@ Start by understanding the ubiquitous testing practices that are the foundation * 🇰🇷[Korean](readme.kr.md) - courtesy of [Rain Byun](https://github.com/ragubyun) * Want to translate to your own language? please open an issue 💜 -

    ## `Table of Contents` @@ -62,10 +59,8 @@ Watching the watchman - measuring test quality (4 bullets) Guidelines for CI in the JS world (9 bullets) -

    - # Section 0️⃣: The Golden Rule
    @@ -87,7 +82,6 @@ Most of the advice below are derivatives of this principle. ### Ready to start? -

    # Section 1: The Test Anatomy @@ -106,7 +100,6 @@ Most of the advice below are derivatives of this principle.
    - ❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning?
    @@ -153,10 +146,8 @@ describe('Products Service', function() { 3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code -
    - ❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain
    @@ -202,22 +193,15 @@ test('Should be classified as premium', () => { }); ``` -
    - -

    - - - ## ⚪ ️1.3 Describe expectations in a product language: use BDD-style assertions :white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/)
    - ❌ **Otherwise:** The team will write less tests and decorate the annoying ones with .skip()
    @@ -249,7 +233,7 @@ test("When asking for an admin, ensure only ordered admins in results" , () => { } }); - if(!admin1Found || !admin2Found ){ + if(!admin1Found || !admin2Found){ throw new Error("Not all admins were returned"); } }); @@ -259,7 +243,6 @@ test("When asking for an admin, ensure only ordered admins in results" , () => { ### :clap: Doing It Right Example: Skimming through the following declarative test is a breeze - ```javascript it("When asking for an admin, ensure only ordered admins in results" , () => { //assuming we've added here two admins @@ -273,16 +256,13 @@ it("When asking for an admin, ensure only ordered admins in results" , () => {
    -

    - ## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods :white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden
    - ❌ **Otherwise:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored…
    @@ -309,7 +289,6 @@ class ProductService{ } } - 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); @@ -319,9 +298,6 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response",
    - - -

    ## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies @@ -333,7 +309,6 @@ Before using test doubles, ask a very simple question: Do I use it to test funct For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently
    - ❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend
    @@ -371,8 +346,6 @@ it("When a valid product is about to be deleted, ensure an email is sent", async
    - -

    ## ⚪ ️1.6 Don’t “foo”, use realistic input data @@ -380,10 +353,8 @@ it("When a valid product is about to be deleted, ensure an email is sent", async :white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing).
    - ❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” -
    Code Examples @@ -431,9 +402,6 @@ it("Better: When adding new valid product, get successful confirmation", async (
    - - -

    ## ⚪ ️ 1.7 Test many input combinations using Property-based testing @@ -441,10 +409,8 @@ it("Better: When adding new valid product, get successful confirmation", async ( :white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained
    - ❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs -
    Code Examples @@ -474,9 +440,6 @@ describe("Product service", () => {
    - - -

    ## ⚪ ️ 1.8 If needed, use only short & inline snapshots @@ -545,7 +508,6 @@ expect(menu).toMatchInlineSnapshot(`
    -

    ## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test @@ -553,10 +515,8 @@ expect(menu).toMatchInlineSnapshot(` :white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries)
    - ❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data -
    Code Examples @@ -606,7 +566,6 @@ it("When updating site name, get successful confirmation", async () => {
    -
    ## ⚪ ️ 1.10 Don’t catch errors, expect them @@ -615,10 +574,8 @@ it("When updating site name, get successful confirmation", async () => { A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user
    - ❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong -
    Code Examples @@ -658,9 +615,6 @@ it.only("When no product name, it throws error 400", async() => {
    - - -

    ## ⚪ ️ 1.11 Tag your tests @@ -668,10 +622,8 @@ it.only("When no product name, it throws error 400", async() => { :white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’
    - ❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests -
    Code Examples @@ -693,14 +645,10 @@ describe('Order service', function() { }); }); - ```
    - - -

    ## ⚪ ️1.12 Other generic good testing hygiene @@ -709,12 +657,10 @@ describe('Order service', function() { Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc)
    - ❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades

    - # Section 2️⃣: Backend Testing ## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid @@ -729,10 +675,8 @@ A word of caution: the TDD argument in the software world takes a typical false-
    - ❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes -
    Code Examples @@ -748,12 +692,8 @@ A word of caution: the TDD argument in the software world takes a typical false- ![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "A test name that constitutes 3 parts") -
    - - -

    ## ⚪ ️2.2 Component testing might be your best affair @@ -763,10 +703,8 @@ A word of caution: the TDD argument in the software world takes a typical false- Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time.
    - ❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage -
    Code Examples @@ -789,10 +727,8 @@ Component tests focus on the Microservice ‘unit’, they work against the API, :white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration
    - ❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear -
    Code Examples @@ -804,25 +740,19 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ![](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/bp-14-testing-best-practices-contract-flow.png)
    - -

    - ## ⚪ ️ 2.4 Test your middlewares in isolation :white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below)
    - ❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests -
    Code Examples @@ -856,22 +786,17 @@ test('A request without authentication header, should return http status 403', (
    - - -

    ## ⚪ ️2.5 Measure and refactor using static analysis tools :white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) -Credit:: [Keith Holliday](https://github.com/TheHollidayInn) +Credit: [Keith Holliday](https://github.com/TheHollidayInn)
    - ❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix -
    Code Examples @@ -883,23 +808,18 @@ Credit:: - - -

    ## ⚪ ️ 2.6 Check your readiness for Node-related chaos -:white_check_mark: **Do:** Weirdly, most software testings are about logic & data only, but some of the worst things that happen (and are really hard to mitigate ) are infrastructural issues. For example, did you ever test what happens when your process memory is overloaded, or when the server/process dies, or does your monitoring system realizes when the API becomes 50% slower?. To test and mitigate these type of bad things — [Chaos engineering](https://principlesofchaos.org/) was born by Netflix. It aims to provide awareness, frameworks and tools for testing our app resiliency for chaotic issues. For example, one of its famous tools, [the chaos monkey](https://github.com/Netflix/chaosmonkey), randomly kills servers to ensure that our service can still serve users and not relying on a single server (there is also a Kubernetes version, [kube-monkey](https://github.com/asobti/kube-monkey), that kills pods). All these tools work on the hosting/platform level, but what if you wish to test and generate pure Node chaos like check how your Node process copes with uncaught errors, unhandled promise rejection, v8 memory overloaded with the max allowed of 1.7GB or whether your UX stays satisfactory when the event loop gets blocked often? to address this I’ve written, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) which provides all sort of Node-related chaotic acts +:white_check_mark: **Do:** Weirdly, most software testings are about logic & data only, but some of the worst things that happen (and are really hard to mitigate) are infrastructural issues. For example, did you ever test what happens when your process memory is overloaded, or when the server/process dies, or does your monitoring system realizes when the API becomes 50% slower?. To test and mitigate these type of bad things — [Chaos engineering](https://principlesofchaos.org/) was born by Netflix. It aims to provide awareness, frameworks and tools for testing our app resiliency for chaotic issues. For example, one of its famous tools, [the chaos monkey](https://github.com/Netflix/chaosmonkey), randomly kills servers to ensure that our service can still serve users and not relying on a single server (there is also a Kubernetes version, [kube-monkey](https://github.com/asobti/kube-monkey), that kills pods). All these tools work on the hosting/platform level, but what if you wish to test and generate pure Node chaos like check how your Node process copes with uncaught errors, unhandled promise rejection, v8 memory overloaded with the max allowed of 1.7GB or whether your UX stays satisfactory when the event loop gets blocked often? to address this I’ve written, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) which provides all sort of Node-related chaotic acts
    - ❌ **Otherwise:** No escape here, Murphy’s law will hit your production without mercy -
    Code Examples @@ -918,10 +838,8 @@ Credit:: { :white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI -
    ❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation -
    Code Examples @@ -1036,12 +952,8 @@ test('When flagging to show only VIP, should display only VIP members', () => {
    - - -

    - ## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change :white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed @@ -1101,12 +1013,8 @@ test('Whenever no data is passed, error metric shows zero', () => { }); ``` -
    - - -
    ## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component @@ -1119,7 +1027,6 @@ With all that said, a word of caution is in order: this technique works for smal ❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? -
    Code Examples @@ -1159,7 +1066,6 @@ test('Realistic approach: When clicked to show filters, filters are displayed', // This is how the user will approach this element: by text }) - ``` ### :thumbsdown: Anti-Pattern Example: Mocking the reality with shallow rendering @@ -1184,7 +1090,6 @@ test('Shallow/mocked approach: When clicked to show filters, filters are display
    - ## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up :white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution @@ -1192,7 +1097,6 @@ test('Shallow/mocked approach: When clicked to show filters, filters are display ❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance -
    Code Examples @@ -1255,7 +1159,6 @@ test('movie title appears', async () => {
    -
    ## ⚪ ️ 3.5. Watch how the content is served over the network @@ -1277,13 +1180,11 @@ test('movie title appears', async () => { ![](/assets/lighthouse2.png "Lighthouse page load inspection report") -
    -
    -## ⚪ ️ 3.6 Stub flakky and slow resources like backend APIs +## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs :white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests @@ -1291,7 +1192,6 @@ test('movie title appears', async () => { ❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower -
    Code Examples @@ -1398,9 +1298,6 @@ beforeEach(setUser => () {
    - - -
    ## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map @@ -1411,7 +1308,6 @@ beforeEach(setUser => () { ❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering -
    Code Examples @@ -1436,7 +1332,6 @@ it('When doing smoke testing over all page, should load them all successfully',
    -
    ## ⚪ ️ 3.10 Expose the tests as a live collaborative document @@ -1445,7 +1340,6 @@ it('When doing smoke testing over all page, should load them all successfully', ❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value -
    Code Examples @@ -1477,22 +1371,18 @@ Feature: Twitter new tweet ![alt text](assets/story-book.jpg "Storybook") -


    - ## ⚪ ️ 3.11 Detect visual issues with automated tools - :white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS]([https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue
    ❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? -
    Code Examples @@ -1505,7 +1395,6 @@ Feature: Twitter new tweet
    - ### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots ![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg @@ -1526,7 +1415,6 @@ screen_widths: - 1024​ - 1280​ - ​# Type page URL paths below, here are a couple of examples​ paths: about: @@ -1562,15 +1450,9 @@ describe('visual validation', () => { }); ``` - - -
    - -

    - # Section 4️⃣: Measuring Test Effectiveness @@ -1584,10 +1466,8 @@ Implementation tips: You may want to configure your continuous integration (CI)
    - ❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down -
    Code Examples @@ -1608,8 +1488,6 @@ Implementation tips: You may want to configure your continuous integration (CI)
    - -

    ## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities @@ -1617,10 +1495,8 @@ Implementation tips: You may want to configure your continuous integration (CI) :white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas
    - ❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from -
    Code Examples @@ -1635,7 +1511,6 @@ Based on a real-world scenario where we tracked our application usage in QA and
    -

    ## ⚪ ️ 4.3 Measure logical coverage using mutation testing @@ -1651,7 +1526,6 @@ Mutation-based testing is here to help by measuring the amount of code that was Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar
    - ❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code
    @@ -1686,8 +1560,6 @@ it("Test addNewOrder, don't use such test names", () => {
    - -

    ## ⚪ ️4.4 Preventing test code issues with Test linters @@ -1696,10 +1568,8 @@ it("Test addNewOrder, don't use such test names", () => {
    - ❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation -
    Code Examples @@ -1724,7 +1594,6 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test


    - # Section 5️⃣: CI and Other Quality Measures @@ -1735,10 +1604,8 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test :white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash._map(…)
    - ❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day -
    Code Examples @@ -1750,22 +1617,17 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    - - -

    -# ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI +## ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI :white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. Practically, some CI vendors (Example: [CircleCI load CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky))
    - ❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact -
    Code Examples @@ -1794,22 +1656,17 @@ Practically, some CI vendors (Example: [CircleCI load CLI](https://circleci.com/
    - - -

    -# ⚪ ️5.3 Perform e2e testing over a true production-mirror +## ⚪ ️5.3 Perform e2e testing over a true production-mirror :white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes.
    - ❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated -
    Code Examples @@ -1822,19 +1679,13 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    - - - -

    ## ⚪ ️5.4 Parallelize test execution :white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes - ❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant -
    Code Examples @@ -1846,9 +1697,6 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    - - -

    ## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check @@ -1856,7 +1704,6 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo ❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues -
    Code Examples @@ -1877,11 +1724,8 @@ license-checker --summary --failOn BSD ![alt text](assets/bp-25-nodejs-licsense.png) -
    - -

    ## ⚪ ️5.6 Constantly inspect for vulnerable dependencies @@ -1889,7 +1733,6 @@ license-checker --summary --failOn BSD ❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious -
    Code Examples @@ -1901,9 +1744,6 @@ license-checker --summary --failOn BSD
    - - -

    ## ⚪ ️5.7 Automate dependency updates @@ -1916,10 +1756,8 @@ license-checker --summary --failOn BSD An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8)
    - ❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky -
    Code Examples @@ -1929,10 +1767,8 @@ An efficient update policy may allow some ‘vesting period’ — let the c ### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions ![alt text](assets/bp-27-yoni-goldberg-npm.png "Nncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions") -
    -

    ## ⚪ ️ 5.8 Other, non-Node related, CI tips @@ -1941,7 +1777,6 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    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

    - ❌ **Otherwise:** You‘ll miss years of wisdom

    @@ -1950,10 +1785,8 @@ An efficient update policy may allow some ‘vesting period’ — let the c :white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that
    - ❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? -
    Code Examples @@ -1968,8 +1801,6 @@ An efficient update policy may allow some ‘vesting period’ — let the c # Team - - ## Yoni Goldberg
    From fc290d3ab4e029792503fe1c4927cf0a4fd5ce2d Mon Sep 17 00:00:00 2001 From: Daniel Karski Date: Thu, 9 Jan 2020 12:12:07 +0100 Subject: [PATCH 179/502] Removed unneeded `.only()` running a single spec isn't needed in this an example case. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0bb34f5d..438d86bc 100644 --- a/readme.md +++ b/readme.md @@ -650,7 +650,7 @@ expect(errorWeExceptFor).not.to.be.null; ### :clap: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM ```javascript -it.only("When no product name, it throws error 400", async() => { +it("When no product name, it throws error 400", async() => { await expect(addNewProduct({})).to.eventually.throw(AppError).with.property('code', "InvalidInput"); }); From 052f7fee8a6338ed6777f909e06f2a70e9f6af53 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Mon, 20 Jan 2020 21:55:19 +0200 Subject: [PATCH 180/502] Update readme.md --- readme.md | 1 - 1 file changed, 1 deletion(-) diff --git a/readme.md b/readme.md index a994b79a..86231275 100644 --- a/readme.md +++ b/readme.md @@ -21,7 +21,6 @@ Start by understanding the ubiquitous testing practices that are the foundation * A JavaScript & Node.js consultant * 👨‍🏫 [My testing workshop](https://www.testjavascript.com) - learn about [my workshops](https://www.testjavascript.com) in Europe & US * [Follow me on Twitter ](https://twitter.com/goldbergyoni/) -* Come hear me speak at [LA](https://js.la/), [Verona](https://2019.nodejsday.it/), [Kharkiv](https://kharkivjs.org/), [free webinar](https://zoom.us/webinar/register/1015657064375/WN_Lzvnuv4oQJOYey2jXNqX6A). Future events TBD * [My JavaScript Quality newsletter](https://testjavascript.com/newsletter/) - insights and content only on strategic matters
    From 1ad58b1deece9f397361151c5f0b4a1d7aad009e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 2 Feb 2020 09:57:59 +0000 Subject: [PATCH 181/502] docs: update readme.md [skip ci] --- readme.md | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/readme.md b/readme.md index f5f43774..0c088c6d 100644 --- a/readme.md +++ b/readme.md @@ -1857,30 +1857,31 @@ Thanks goes to these wonderful people who have contributed to this repository! - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - + + + + + + +
    Scott Davis
    Scott Davis

    🖋
    Adrien REDON
    Adrien REDON

    🖋
    Stefano Magni
    Stefano Magni

    🖋
    Yeoh Joer
    Yeoh Joer

    🖋
    Jhonny Moreira
    Jhonny Moreira

    🖋
    Ian Germann
    Ian Germann

    🖋
    Hafez
    Hafez

    🖋

    Scott Davis

    🖋

    Adrien REDON

    🖋

    Stefano Magni

    🖋

    Yeoh Joer

    🖋

    Jhonny Moreira

    🖋

    Ian Germann

    🖋

    Hafez

    🖋
    Ruxandra Fediuc
    Ruxandra Fediuc

    🖋
    Jack
    Jack

    🖋
    Peter Carrero
    Peter Carrero

    🖋
    Huhgawz
    Huhgawz

    🖋
    Haakon Borch
    Haakon Borch

    🖋
    Jaime Mendoza
    Jaime Mendoza

    🖋
    Cameron Dunford
    Cameron Dunford

    🖋

    Ruxandra Fediuc

    🖋

    Jack

    🖋

    Peter Carrero

    🖋

    Huhgawz

    🖋

    Haakon Borch

    🖋

    Jaime Mendoza

    🖋

    Cameron Dunford

    🖋
    John Gee
    John Gee

    🖋
    Aurelijus Rožėnas
    Aurelijus Rožėnas

    🖋
    Aaron
    Aaron

    🖋
    Tom Nagle
    Tom Nagle

    🖋
    Yves yao
    Yves yao

    🖋
    Userbit
    Userbit

    🖋

    John Gee

    🖋

    Aurelijus Rožėnas

    🖋

    Aaron

    🖋

    Tom Nagle

    🖋

    Yves yao

    🖋

    Userbit

    🖋

    Glaucia Lemos

    🚧
    From 5d3f85a5cd1345800a24d4125ac1b2af033a366a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 2 Feb 2020 09:58:00 +0000 Subject: [PATCH 182/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 85eaeea7..f0af9daf 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -186,6 +186,15 @@ "contributions": [ "content" ] + }, + { + "login": "glaucia86", + "name": "Glaucia Lemos", + "avatar_url": "https://avatars0.githubusercontent.com/u/1631477?v=4", + "profile": "https://glaucialemos.netlify.com/", + "contributions": [ + "maintenance" + ] } ], "projectName": "javascript-testing-best-practices", From 0afd174a88efe1c486332a8240260cc102015af2 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 2 Feb 2020 20:54:29 +0200 Subject: [PATCH 183/502] Update readme.md --- readme.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 0c088c6d..3f7bb5d0 100644 --- a/readme.md +++ b/readme.md @@ -19,9 +19,8 @@ Start by understanding the ubiquitous testing practices that are the foundation ### Written By Yoni Goldberg * A JavaScript & Node.js consultant -* 👨‍🏫 [My testing workshop](https://www.testjavascript.com) - learn about [my workshops](https://www.testjavascript.com) in Europe & US +* 📗 [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/) -* [My JavaScript Quality newsletter](https://testjavascript.com/newsletter/) - insights and content only on strategic matters
    @@ -347,6 +346,12 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

    +## 📗 Want to learn all these practices with live video? + +### Visit my online course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +

    + ## ⚪ ️1.6 Don’t “foo”, use realistic input data :white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). @@ -1811,9 +1816,8 @@ An efficient update policy may allow some ‘vesting period’ — let the c **About:** I'm an independent consultant who works with 500 fortune corporates 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) -**Workshop:** 👨‍🏫 Want to learn all these practices and techniques at your offices (Europe & USA)? [Register here for my testing workshop](https://testjavascript.com/)
    **Follow:** From 1a149dff57a2829d1cc74ef1ef8b9c1640743001 Mon Sep 17 00:00:00 2001 From: koooge Date: Thu, 13 Feb 2020 22:11:17 +0100 Subject: [PATCH 184/502] Update readme.md --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 3f7bb5d0..4f376b9e 100644 --- a/readme.md +++ b/readme.md @@ -63,7 +63,7 @@ Guidelines for CI in the JS world (9 bullets)
    -## ⚪️ 0. The Golden Rule: Design for lean testing +## ⚪️ 0 The Golden Rule: Design for lean testing :white_check_mark: **Do:** Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. @@ -895,7 +895,7 @@ it("When updating site name, get successful confirmation", async () => { # Section 3️⃣: Frontend Testing -## ⚪ ️ 3.1. Separate UI from functionality +## ⚪ ️ 3.1 Separate UI from functionality :white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI @@ -1166,7 +1166,7 @@ test('movie title appears', async () => {
    -## ⚪ ️ 3.5. Watch how the content is served over the network +## ⚪ ️ 3.5 Watch how the content is served over the network ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") From f41cf450eb54a4fa3266f1f6dc889abb636ec09a Mon Sep 17 00:00:00 2001 From: koooge Date: Thu, 13 Feb 2020 22:14:14 +0100 Subject: [PATCH 185/502] Update readme.kr.md --- readme.kr.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 8eae2199..fa1822a9 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -61,7 +61,7 @@ JavaScript 및 Node.js에 대한 A부터 Z까지의 믿음직한 가이드입니
    -## ⚪ ️ 0. 황금률: 린 테스트를 위한 설계 +## ⚪ ️ 0 황금률: 린 테스트를 위한 설계 :white_check_mark: **이렇게 해라:** 테스트 코드는 제품 코드와 다릅니다. 단순하고, 짧고, 추상화가 없고, 무난하고, 작업하기에 편리하고, 린하게 디자인 하십시오. 테스트를 보고 즉시 의미를 알아챌 수 있어야 합니다. @@ -918,7 +918,7 @@ it("사이트 이름을 변경하면, 성공 결과값을 받아온다", async ( # 섹션 3️⃣: 프론트엔드 테스트 -## ⚪ ️ 3.1. 기능으로부터 화면을 분리하십시오 +## ⚪ ️ 3.1 기능으로부터 화면을 분리하십시오 :white_check_mark: **이렇게 해라:** 컴포넌트 로직을 테스트할때, 화면의 세부사항들은 제외되어야할 노이즈가 됩니다. 그것을 제외함으로써 당신의 테스트들은 순수한 데이터에 집중할 수 있습니다. 실제로, 그래픽 구현에 너무 결합되지 않는 추상적인 방법을 통해 요구되어지는 데이터를 마크업으로부터 추출하십시오. 그리고 느리게 만드는 애니메이션들을 제외한 오직 순수한 데이터를 검증하십시오(vs HTML/CSS 화면 세부사항). 당신은 렌더링하는 것을 피하고 오직 화면의 뒷부분(서비스, 액션, 스토어등과 같은)만을 테스트 하려고 할 수도 있습니다. 하지만, 이것은 실제와 같지도 않으며 심지어 화면에 올바른 데이터가 도달하지 않은 경우를 나타내지도 않는 가짜 테스트에서의 결과가 될 것 입니다. @@ -1202,7 +1202,7 @@ test('movie title appears', async () => {
    -## ⚪ ️ 3.5. 화면의 내용이 네트워크를 통해 어떻게 제공될지 확인하십시오 +## ⚪ ️ 3.5 화면의 내용이 네트워크를 통해 어떻게 제공될지 확인하십시오 ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") From ee3035471b66878f70ae2b9e3d4d85df74af4e55 Mon Sep 17 00:00:00 2001 From: koooge Date: Thu, 13 Feb 2020 22:15:38 +0100 Subject: [PATCH 186/502] Update readme-zh-CN.md --- readme-zh-CN.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme-zh-CN.md b/readme-zh-CN.md index beed8ce9..777107b1 100644 --- a/readme-zh-CN.md +++ b/readme-zh-CN.md @@ -62,7 +62,7 @@ JS 领域的 CI 指南(9 条)
    -## ⚪️ 0. 黄金法则:设计瘦测试 +## ⚪️ 0 黄金法则:设计瘦测试 :white_check_mark: **建议:** 测试代码与生产代码不同,要使它变得极其简单、短小、没有抽象、扁平化、使人愉悦、瘦。一段测试代码需要做到让人一眼就能看出其目的。 @@ -970,7 +970,7 @@ it("When updating site name, get successful confirmation", async () => { # 第三章: 前端测试 -## ⚪ ️ 3.1. 将 UI 与功能分离 +## ⚪ ️ 3.1 将 UI 与功能分离 :white_check_mark: **建议:** 当专注于测试组件逻辑时,UI 细节就变成了应该剔除的噪音,这样您的测试就可以集中在纯数据上。实际上,通过抽象从代码中提取所需的数据将降低与图形实现的耦合,仅对纯数据 (vs HTML/CSS 图形细节) 断言,并禁用会拖慢速度的动画。您可能会试图避免渲染,仅测试 UI 后面的部分(例如,服务、操作、存储),但这将导致测试与实际情况不太相符,「正确的数据根本无法到达 UI」这种问题就无法发现。 @@ -1255,7 +1255,7 @@ test('movie title appears', async () => {
    -## ⚪ ️ 3.5. 观察内容是如何通过网络提供的 +## ⚪ ️ 3.5 观察内容是如何通过网络提供的 ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") From 93795383e66be6f3b16131125a49524da15ce407 Mon Sep 17 00:00:00 2001 From: koooge Date: Thu, 13 Feb 2020 22:16:22 +0100 Subject: [PATCH 187/502] Update readme-pt-br.md --- readme-pt-br.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme-pt-br.md b/readme-pt-br.md index 273109cb..894954dd 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -70,7 +70,7 @@ Diretrizes para CI no mundo JS (9 tópicos)
    -## ⚪️ 0. A Regra de Ouro: Design para testes enxutos +## ⚪️ 0 A Regra de Ouro: Design para testes enxutos :white_check_mark: **Faça:** O código de teste não é como o código de produção - projete-o para ser simples, curto, sem abstrações, plano, agradável de se trabalhar, enxuto. Deve-se olhar para um teste e obter a intenção instantaneamente. @@ -973,7 +973,7 @@ it("When updating site name, get successful confirmation", async () => { # Seção 3️⃣: Teste de Frontend -## ⚪ ️ 3.1. Separar UI da funcionalidade +## ⚪ ️ 3.1 Separar UI da funcionalidade :white_check_mark: **Faça:** Ao focar no teste da lógica dos componentes, os detalhes da interface do usuário se tornam um ruído que deve ser extraído, para que seus testes possam se concentrar em dados puros. Na prática, extraia os dados desejados da marcação de uma maneira abstrata que não seja muito acoplada à implementação gráfica, afirme apenas dados puros (vs detalhes gráficos de HTML/CSS) e desative animações que diminuem a velocidade. Você pode cair na tentação de evitar renderizar e testar apenas a parte de trás da interface do usuário (por exemplo, serviços, ações, armazenamento), mas isso resultará em testes fictícios que não se assemelham à realidade e não revelam casos em que os dados corretos nem chegam na interface do usuário @@ -1259,7 +1259,7 @@ test('movie title appears', async () => {
    -## ⚪ ️ 3.5. Veja como o conteúdo é servido na rede +## ⚪ ️ 3.5 Veja como o conteúdo é servido na rede ![](https://img.shields.io/badge/🔧%20Exemplo%20usando%20Google%20LightHouse-blue.svg "Exemplo com Lighthouse") From 094efda0d67c1d6e3d4b3a9f2bed24a80aae5a14 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2020 15:57:33 +0000 Subject: [PATCH 188/502] docs: update readme.md [skip ci] --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index 3f7bb5d0..0240e0a8 100644 --- a/readme.md +++ b/readme.md @@ -1887,6 +1887,9 @@ Thanks goes to these wonderful people who have contributed to this repository!

    Userbit

    🖋
    Glaucia Lemos

    🚧 + +
    koooge

    🖋 + From 76a0dcfdc0cfea25ea08604dceed6bd7e9cb2432 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2020 15:57:34 +0000 Subject: [PATCH 189/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index f0af9daf..431fa410 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -195,6 +195,15 @@ "contributions": [ "maintenance" ] + }, + { + "login": "koooge", + "name": "koooge", + "avatar_url": "https://avatars2.githubusercontent.com/u/7419215?v=4", + "profile": "https://twitter.com/koooge", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 368b46304c6a4c4871011995e19888306052cc39 Mon Sep 17 00:00:00 2001 From: Yoni Date: Thu, 26 Mar 2020 23:10:03 +0200 Subject: [PATCH 190/502] New BP: Categorize test suites --- .DS_Store | Bin 0 -> 10244 bytes assets/flat-report.png | Bin 0 -> 206040 bytes assets/hirearchical-report.png | Bin 0 -> 201570 bytes readme.md | 877 ++++++++++++++++++--------------- 4 files changed, 468 insertions(+), 409 deletions(-) create mode 100644 .DS_Store create mode 100644 assets/flat-report.png create mode 100644 assets/hirearchical-report.png diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..dffbf785c4002bb11d627ca4f64393b582d5ada7 GIT binary patch literal 10244 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 z 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 q+L8YTJAQ*F|9sZpXCq5VGU2Nu*cW_ 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 v-<(9C-0Zjc{l -# 👇 Why this guide can take your testing skills to the next level +# 👇 Why this guide can take your testing skills to the next level
    ## 📗 45+ best practices: Super-comprehensive and exhaustive + This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and curates for you dozens of the best blog posts, books and tools the market has to offer ## 🚢 Advanced: Goes 10,000 miles beyond the basics + Hop into a journey that travels way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average ## 🌐 Full-stack: front, backend, CI, anything + Start by understanding the ubiquitous testing practices that are the foundation for any application tier. Then, delve into your area of choice: frontend/UI, backend, CI or maybe all of them?
    ### Written By 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/) + +- 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/)
    ### Translations - read in your own language -* 🇨🇳[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) -* Want to translate to your own language? please open an issue 💜 + +- 🇨🇳[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) +- Want to translate to your own language? please open an issue 💜

    @@ -65,17 +70,17 @@ Guidelines for CI in the JS world (9 bullets) ## ⚪️ 0 The Golden Rule: Design for lean testing -:white_check_mark: **Do:** -Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. +:white_check_mark: **Do:** +Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. - -The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should *feel* as easy as modifying an HTML document and not like solving 2X(17 × 24). + +The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. ![alt text](/assets/headspace.png "We have no head room for additional complexity") - + Most of the advice below are derivatives of this principle. ### Ready to start? @@ -103,14 +108,14 @@ Most of the advice below are derivatives of this principle.
    **👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +
    Code Examples
    ### :clap: Doing It Right Example: A test name that constitutes 3 parts -![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg - "Using Mocha to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Using Mocha to illustrate the idea") ```javascript //1. unit under test @@ -125,9 +130,11 @@ describe('Products Service', function() { }); ``` +
    ### :clap: Doing It Right Example: A test name that constitutes 3 parts + ![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts")
    @@ -156,24 +163,21 @@ describe('Products Service', function() { ### :clap: Doing It Right Example: A test structured with the AAA pattern -![](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") - +![](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'); - }); +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"); + }); }); ``` @@ -182,13 +186,12 @@ describe('Customer classifier', () => { ### :thumbsdown: Anti Pattern Example: No separation, one bulk, harder to interpret ```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'); - }); +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"); +}); ```

    @@ -206,57 +209,56 @@ test('Should be classified as premium', () => {
    Code Examples
    -![](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: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story +![](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: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story ```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; - } - }); +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; - if(!admin1Found || !admin2Found){ - throw new Error("Not all admins were returned"); + 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: Doing It Right Example: Skimming through the following declarative test is a breeze ```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}); +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"]); + expect(allAdmins) + .to.include.ordered.members(["admin1", "admin2"]) + .but.not.include.ordered.members(["user1"]); }); - ```


    -## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods +## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods :white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden
    @@ -269,29 +271,29 @@ it("When asking for an admin, ensure only ordered admins in results" , () => {
    ### :thumbsdown: Anti Pattern Example: A test case is testing the internals for no good reason -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Mocha & Chai") + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") + ```javascript -class ProductService{ +class ProductService { //this method is only used internally //Change this name will make the tests fail - calculateVATAdd(priceWithoutVAT){ - return {finalPrice: priceWithoutVAT * 1.2}; + 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); + 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); + //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); }); - ```
    @@ -300,7 +302,7 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response", ## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies -:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. @@ -316,29 +318,34 @@ For example, if you want to test that your app behaves reasonably when the payme
    ### :thumbsdown: Anti-pattern example: Mocks focus on the internals -![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg - "Examples with Sinon") + +![](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(); + //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:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals ```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; + //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; }); ``` @@ -354,7 +361,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 Don’t “foo”, use realistic input data -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing).
    ❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” @@ -367,40 +374,38 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg - "Examples with Jest") - - +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") + ```javascript -const addProduct = (name, price) =>{ +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 + if (!productNameRegexNoSpace.test(name)) return false; //this path never reached due to dull input - //some logic here - return true; + //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 + //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:Doing It Right Example: Randomizing realistic input + ```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! + 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! }); ``` @@ -421,10 +426,9 @@ it("Better: When adding new valid product, get successful confirmation", async (
    -### :clap: Doing It Right Example: Testing many input permutations with “fast-check” +### :clap: Doing It Right Example: Testing many input permutations with “fast-check” -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg - "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ```javascript import fc from "fast-check"; @@ -455,7 +459,7 @@ On the other hand, ‘classic snapshots’ tutorials and tools encourage to stor It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes
    -❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown...
    @@ -465,42 +469,41 @@ It’s worth noting that there are few cases where long & external snapshots are ### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code -![](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 < /DisplayPage>) -.toJSON(); - -//Assert -expect(receivedPage).toMatchSnapshot(); -//We now implicitly maintain a 2000 lines long document -//every additional line break or comment - will break this test +![](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: Doing It Right Example: Expectations are visible and focused + ```javascript -it('When visiting TestJavaScript.com home page, a menu is displayed', () => { -//Arrange +it("When visiting TestJavaScript.com home page, a menu is displayed", () => { + //Arrange -//Act -const receivedPage = 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(`
    • Home
    • About
    • @@ -529,9 +532,8 @@ expect(menu).toMatchInlineSnapshot(` ### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Mocha") - +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") + ```javascript before(() => { //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework @@ -550,6 +552,7 @@ it("When querying by site name, get the right site", async () => { }); ``` +
      ### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data @@ -560,12 +563,11 @@ 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); }); - ```
    @@ -573,7 +575,8 @@ it("When updating site name, get successful confirmation", async () => {
    ## ⚪ ️ 1.10 Don’t catch errors, expect them -:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations + +:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user
    @@ -588,33 +591,33 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e ### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Mocha") - +![](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 +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: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM ```javascript -it("When no product name, it throws error 400", async() => { - await expect(addNewProduct({})).to.eventually.throw(AppError).with.property('code', "InvalidInput"); +it("When no product name, it throws error 400", async () => { + await expect(addNewProduct({})) + .to.eventually.throw(AppError) + .with.property("code", "InvalidInput"); }); - ```
    @@ -623,7 +626,7 @@ it("When no product name, it throws error 400", async() => { ## ⚪ ️ 1.11 Tag your tests -:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’
    ❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests @@ -636,27 +639,90 @@ it("When no product name, it throws error 400", async() => { ### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg - "Examples with Jest") +![](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() { +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 Categorize tests under at least 2 levels +:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and prtscn below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) + + +
    + +❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below + +![](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/hirearchical-report.png) + +
    + +### :thumbsdown: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests + +![](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.12 Other generic good testing hygiene -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +## ⚪ ️1.13 Other generic good testing hygiene + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc)
    @@ -669,7 +735,7 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr ## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid -:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit _all_ types of applications? shouldn’t the testing world consider welcoming new testing techniques? Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. @@ -688,6 +754,7 @@ A word of caution: the TDD argument in the software world takes a typical false-
    ### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘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) @@ -717,8 +784,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg - "Examples with Mocha") +![](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)") @@ -728,7 +794,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ## ⚪ ️2.3 Ensure new releases don’t break the API using -:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration
    ❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear @@ -741,9 +807,8 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ### :clap: Doing It Right Example: -![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg - "Examples with PACT") - +![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") + ![alt text](assets/bp-14-testing-best-practices-contract-flow.png)
    @@ -765,27 +830,25 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg - "Examples with Jest") +![](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'); +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', () => { +test("A request without authentication header, should return http status 403", () => { const request = httpMocks.createRequest({ - method: 'GET', - url: '/user/42', + method: "GET", + url: "/user/42", headers: { - authentication: '' + authentication: "" } }); const response = httpMocks.createResponse(); unitUnderTest(request, response); expect(response.statusCode).toBe(403); }); - ```
    @@ -793,6 +856,7 @@ test('A request without authentication header, should return http status 403', (

    ## ⚪ ️2.5 Measure and refactor using static analysis tools + :white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) Credit: [Keith Holliday](https://github.com/TheHollidayInn) @@ -807,11 +871,10 @@ Credit: @@ -819,10 +882,11 @@ Credit: @@ -852,9 +917,8 @@ Credit: { //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework @@ -873,6 +937,7 @@ it("When querying by site name, get the right site", async () => { }); ``` +
    ### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data @@ -886,7 +951,6 @@ it("When updating site name, get successful confirmation", async () => { const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); expect(updateNameResult).to.be(true); }); - ```
    @@ -911,47 +975,38 @@ it("When updating site name, get successful confirmation", async () => { ### :clap: Doing It Right Example: Separating out the UI details -![](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") +![](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', () => { +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 } - ]; + const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; // Act - const { getAllByTestId } = render(); + 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); + 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: Anti Pattern Example: Assertion mix UI details and data + ```javascript -test('When flagging to show only VIP, should display only VIP members', () => { +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 } - ]; + const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; // Act - const { getAllByTestId } = render(); + const { getAllByTestId } = render(); // Assert - Mix UI & data in assertion - expect(getAllByTestId('user')).toEqual('[
  • John Doe
  • ]'); + expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); }); - ```
    @@ -974,47 +1029,48 @@ test('When flagging to show only VIP, should display only VIP members', () => { ### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing -![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg - "Examples with React") - +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") + ```html // the markup code (part of React component)

    - {value} + {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"); - }); +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: Anti-Pattern Example: Relying on CSS attributes + ```html -{value} +{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"); - }); +test("Whenever no data is passed, error metric shows zero", () => { + // ... + + expect(wrapper.find("[className='d-flex-column']").text()).toBe("0"); +}); ```
    @@ -1039,55 +1095,54 @@ With all that said, a word of caution is in order: this technique works for smal ### :clap: Doing It Right Example: Working realstically with a fully rendered component -![](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") - +![](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} - + 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'); +test("Realistic approach: When clicked to show filters, filters are displayed", () => { + // Arrange + const wrapper = mount(); - // Assert - expect(wrapper.text().includes('Choose Filter')); - // This is how the user will approach this element: by text -}) + // 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: Anti-Pattern Example: Mocking the reality with 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? -}) +```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? +}); ```
    @@ -1096,7 +1151,7 @@ test('Shallow/mocked approach: When clicked to show filters, filters are display ## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up -:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution
    ❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance @@ -1109,57 +1164,51 @@ test('Shallow/mocked approach: When clicked to show filters, filters are display ### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (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") +![](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 +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: Doing It Right Example: Testing library that waits for DOM elements ```javascript // @testing-library/dom -test('movie title appears', async () => { - // element is initially not present... +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')) -}) + // 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: Anti-Pattern Example: custom sleep code -```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')) -}) +```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")); +}); ```
    @@ -1168,8 +1217,7 @@ test('movie title appears', async () => { ## ⚪ ️ 3.5 Watch how the content is served over the network -![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg - "Examples with Lighthouse") +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") ✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 @@ -1204,42 +1252,39 @@ test('movie title appears', async () => {
    ### :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 +![](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) +export default function ProductsList() { + const [products, setProducts] = useState(false); - const fetchProducts = async() => { - const products = await axios.get('api/products') - setProducts(products); - } + const fetchProducts = async () => { + const products = await axios.get("api/products"); + setProducts(products); + }; - useEffect(() => { - fetchProducts(); - }, []); + useEffect(() => { + fetchProducts(); + }, []); - return products ?
    {products}
    :
    No products
    + return products ?
    {products}
    :
    No products
    ; } // test -test('When no products exist, show the appropriate message', () => { - // Arrange - nock("api") - .get(`/products`) - .reply(404); +test("When no products exist, show the appropriate message", () => { + // Arrange + nock("api") + .get(`/products`) + .reply(404); - // Act - const {getByTestId} = render(); + // Act + const { getByTestId } = render(); - // Assert - expect(getByTestId('no-products-message')).toBeTruthy(); + // Assert + expect(getByTestId("no-products-message")).toBeTruthy(); }); - ```
    @@ -1258,7 +1303,7 @@ test('When no products exist, show the appropriate message', () => { ## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials -:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). +:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6).
    @@ -1272,8 +1317,7 @@ test('When no products exist, show the appropriate message', () => { ### :clap: Doing It Right Example: Logging-in before-all and not before-each -![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg - "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") ```javascript let authenticationToken; @@ -1320,19 +1364,20 @@ beforeEach(setUser => () {
    ### :clap: Doing It Right Example: Smoke travelling across all pages -![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg - "Using Cypress to illustrate the idea") + +![](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'); - }) +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"); +}); ```
    @@ -1353,25 +1398,27 @@ it('When doing smoke testing over all page, should load them all successfully', ### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js -![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") +![](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 type "Hello followers!" in the textbox Given I click on "Submit" button Then I see message "Tweet saved" - + ``` ### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook + ![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") ![alt text](assets/story-book.jpg "Storybook") @@ -1382,7 +1429,7 @@ Feature: Twitter new tweet ## ⚪ ️ 3.11 Detect visual issues with automated tools -:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS]([https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue +:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue
    @@ -1402,8 +1449,7 @@ Feature: Twitter new tweet ### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots -![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg - "Using 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​ @@ -1432,33 +1478,31 @@ paths: ### :clap: Doing It Right Example: Using Applitools to get snapshot comaprison and other advanced features -![](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") +![](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'); - }); +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️⃣: Measuring Test Effectiveness

    @@ -1471,7 +1515,7 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down +❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down
    @@ -1480,14 +1524,14 @@ Implementation tips: You may want to configure your continuous integration (CI)
    ### :clap: Example: A typical coverage report + ![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report")
    ### :clap: Doing It Right Example: Setting up coverage per component (using Jest) -![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg - "Using 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)") @@ -1508,7 +1552,7 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :thumbsdown: Anti-Pattern Example: What’s wrong with this coverage report? +### :thumbsdown: Anti-Pattern Example: 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) @@ -1520,7 +1564,7 @@ Based on a real-world scenario where we tracked our application usage in QA and ## ⚪ ️ 4.3 Measure logical coverage using mutation testing -:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: @@ -1541,22 +1585,22 @@ Knowing that all or most of the mutations were killed gives much higher confiden ### :thumbsdown: Anti Pattern Example: 100% coverage, 0% testing -![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg - "Using Stryker") +![](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}`); + logger.log(`Adding new order ${newOrder}`); + DB.save(newOrder); + Mailer.sendMail(newOrder.assignee, `A new order was places ${newOrder}`); - return {approved: true}; + return { approved: true }; } it("Test addNewOrder, don't use such test names", () => { - addNewOrder({asignee: "John@mailer.com",price: 120}); -});//Triggers 100% code coverage, but it doesn't check anything - + addNewOrder({ asignee: "John@mailer.com", price: 120 }); +}); //Triggers 100% code coverage, but it doesn't check anything ``` +
    ### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) @@ -1569,7 +1613,7 @@ it("Test addNewOrder, don't use such test names", () => { ## ⚪ ️4.4 Preventing test code issues with Test linters -:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything)
    @@ -1599,14 +1643,14 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test


    - + # Section 5️⃣: CI and Other Quality Measures

    ## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues -:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash._map(…) +:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…)
    ❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day @@ -1618,6 +1662,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    ### :thumbsdown: Anti Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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")
    @@ -1626,7 +1671,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI -:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky))
    @@ -1639,7 +1684,8 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com
    -### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code + ```javascript "scripts": { "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", @@ -1665,7 +1711,7 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com ## ⚪ ️5.3 Perform e2e testing over a true production-mirror -:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. +:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes.
    @@ -1678,7 +1724,7 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    -### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly
    ([Credit: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) +### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([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
    @@ -1687,7 +1733,8 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo

    ## ⚪ ️5.4 Parallelize test execution -:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes + +:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes ❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant @@ -1698,6 +1745,7 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    ### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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)")
    @@ -1705,7 +1753,8 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo

    ## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights + +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights ❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues @@ -1716,6 +1765,7 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    ### :clap: Doing It Right Example: + ```javascript //install license-checker in your CI environment or also locally npm install -g license-checker @@ -1734,7 +1784,8 @@ license-checker --summary --failOn BSD

    ## ⚪ ️5.6 Constantly inspect for vulnerable dependencies -:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build + +:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build ❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious @@ -1745,6 +1796,7 @@ license-checker --summary --failOn BSD
    ### :clap: Example: NPM Audit result + ![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result")
    @@ -1752,7 +1804,8 @@ license-checker --summary --failOn BSD

    ## ⚪ ️5.7 Automate dependency updates -:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: + +:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: (1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. @@ -1769,7 +1822,8 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions +### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest 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")
    @@ -1777,7 +1831,8 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    ## ⚪ ️ 5.8 Other, non-Node related, CI tips -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known
    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

    @@ -1787,6 +1842,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    ## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions + :white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that
    @@ -1798,7 +1854,8 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    @@ -1822,9 +1879,9 @@ An efficient update policy may allow some ‘vesting period’ — let the c **Follow:** -* [🐦 Twitter](https://twitter.com/goldbergyoni/) -* [📞 Contact](https://testjavascript.com/contact-2/) -* [✉️ Newsletter](https://testjavascript.com/newsletter//) +- [🐦 Twitter](https://twitter.com/goldbergyoni/) +- [📞 Contact](https://testjavascript.com/contact-2/) +- [✉️ Newsletter](https://testjavascript.com/newsletter//)

    @@ -1834,9 +1891,10 @@ An efficient update policy may allow some ‘vesting period’ — let the c **Role:** Tech reviewer and advisor -Took care to revise, improve, lint and polish all the texts +Took care to revise, improve, lint and polish all the texts **About:** full-stack web engineer, Node.js & GraphQL enthusiast +

    @@ -1894,4 +1952,5 @@ Thanks goes to these wonderful people who have contributed to this repository! + From 32d08b12a6e1d5c533035b6b86c95f9e54243cd1 Mon Sep 17 00:00:00 2001 From: Yoni Date: Sun, 29 Mar 2020 14:25:12 +0300 Subject: [PATCH 191/502] Changed image name to hierarchical-report.png --- .DS_Store | Bin 10244 -> 10244 bytes ...cal-report.png => hierarchical-report.png} | Bin readme.md | 17 +++++------------ 3 files changed, 5 insertions(+), 12 deletions(-) rename assets/{hirearchical-report.png => hierarchical-report.png} (100%) diff --git a/.DS_Store b/.DS_Store index dffbf785c4002bb11d627ca4f64393b582d5ada7..d47f21efd3899ffa9945e311f1e52256ca15e0d4 100644 GIT binary patch literal 10244 zcmeHMTWl0n82Sb z#S-;N0*M;Y7^5*HUO-<|9*j@=049)VY$Ez#OuRi96JLEm{m-1)va@X^@j(rlvzhas z^Izti^PO+z%sC4H*p<~<0U`h((8Z*@fU+49v$JbjD)BYdB$48w&$N>X(@s!bp5`4Y zLIgqtLIgqtLIgqt{tpPyn$40>#$}j?2!sfP2uvZs-VYJFn2h>zf=ln!L7BG&AX!Rk zFHzb1^d}i%3X)M@PH;&YO2|V=@=)}*80haDkcX2!>ibV{$-@D?mgzoz%joY=(BF7q z#B&FXavA0!0wDs^5ny}wQfP)@Fu*O^zi%R2e;8OQ(u)rzo z+AJ=Cj>24>Ix>rAVYk0Kf96-H^13~%_sv1v+B+~L{?eOA199l5({P-kxp!G@-j%aH-Y((KVLJ8NlVZ4MQ@K9#>jrN9vL^U4AOhQMqd zJv1=Q?&P{THYKO>*j&$JBT8k;w(UElxV$JwL*A2i4cl@K=oyD(T&{2kroNu>*)J*3NUhd z4~020iWe3vt-Y(han1ehn>#1V7gorUw0Oxt&Tx!`sUK9Gq;6@3HN1b+(A<$jgtFxt zM-4qAij}3U{g#^2C*_KT4-)xJ!ESvN)zM{ zMb8pZ>XgIG(w)&M*ZhEclm@Baq0S1j${klqtEK+OXu3t&;hJWtMcHqv3EdQBjknV# zty2ba95GSe$(0+V4rP#D1}W;YBUWM)S8q|aDvz?3cd3rMC#_qoS69eJqv)zrl!MIH zscEcFLVdK6(;+a5`e+N+iuA!yL+^kn?0_Tm$fV&ooQ5-S7G8n#a1k!S$8Z_0z-Mq3 zzJPDwd-xH4f}i0xxB-74F%KoI!fI4-IX2)5T#2i&4L9IM+=QF47kA<=+>Lv15Qk93 zBx;z#9FF1%9K)yad3*t1#B=yMzJnL=BEE~4@Ke0XyH=8C^@F^g&+!_Sg5-5_{)1a` zbS84v_HNt$ACdFx1&ooEixw|gdT0HL)h+8fwoc7x(HsUQsE}D6oj6}S9_4w$xl6pQ zsg;)1&Bpls#610&!002-pY*(Im)|WAV`RarO0^B5NVN8C4Xemj;#->UBa14};Wnvl ztw4;G=W?sGVWUW#mP>u>mIuhXQ=T_jQ>|1*r7lSZ_K1o9oP%$`1^9^g_a$71Z;5-q z5bu5`&Xr-M$GI9@hIbO{8i{wSaSg7=cH&+Kc4H55Z#(YAhjAbF;{lBO{L|4w8`GFU z7sv4=K8dIBDLjp5@GL&(@$psS;`>Dm?8|eHOZ=difrU%lwHrS?`zBxF0xV70Hy5_q zd;-gvF~ph++#?suSf*_alb$^t^I;eCK{xcmZiqo|aIr)7QBt_riL}P+v^Z*zBH2v2 zRuu6}Q3O@P6e18J5F!vF5F!vFFcSiExH7`d|9fZu|9_^?P!}Q)B5<1^fTcaLo-P_p ze&1R=E_T))p=*dPmYClJmtF{E-f=uh?>L?+>^R=rzmv}2v3qe*(zEypE~!N6U;i^8 S-2bPnTO3Y>`~TZ}|Nj?UYx;fw literal 10244 zcmeHMTWl0n820%<|IC?|?gBpefP~E1%=yp! zmvi~fclPW#3jo-h*OvjP03guAq+CJS4HC1n=ekrDYGjf~@z8EL*^K36=$+}J6d@2H z5FrpD5FrpDaQh)Zdp1kL1TLdILLfpQLSO^|_WhvJ!(<|qgIoq*9h3z}0FvdT4ic4v zpZ+9OrXZOJ#h=JkB0eLvt6QTbgmpmLWXqoB>TgLE&g5kjf)xaGv z!DW<32t){6j{sXP@tmkv^=%Nv31siu9$R9iP-03r~zV`YD z7lRnU@EZ}T`1?qHiAqIq6iwT~IkD^PUZI7BV}v-hFDcTPaLYQnLbgOq0AgMamQaOyA-flykMDELuzfTFRqd^D3nJyG2p< zQuh-Pc&OCSD2TGY)6z1A6{=RPCe_xi0!K4cuD@3htFW${R$4!e)8lHTxkV7#dxdbNm-XC;y0Ny6(&qn%y5CgarFcvpOO)l-25Gq@mlLPCb(ORxCXXZb3PVcG^lC}k zkPz8|lObqHjLnw>@xL?i6*vnQ;A8lV82AnR2tUK`@Heqf#7e9o?#;j$&cX$_2p3}= zF2xnNig>sd*I^rO!p*paxR}CS*oC`s4`$K89_&LiU}7Ez@Gw4%$M85Y@-ci8pCV2^ zL!5jQ&z3N-y@Y{pmojklzIXYpQ@=s=CARMsv9#a0w)fr4r)dAn2ES&D>L?^#+j8t) zs++Bj@z4S7&;o66A0(lzcz47a@)*54ir~^Z*FcAUlJ%79B@rh|BIq?r5dskc5dskc z5dskccMt+&_*hhS|KE1=|Nrlx5l2Ib5Qq>cMF7iNlda999X@WCI ❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause @@ -690,7 +689,7 @@ describe("Transfer service", () => { }); ``` -![alt text](assets/hirearchical-report.png) +![alt text](assets/hierarchical-report.png)
    @@ -699,17 +698,11 @@ describe("Transfer service", () => { ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Mocha") ```javascript - test('Then the response status should decline', () => { - - }); +test("Then the response status should decline", () => {}); - test('Then it should send email', () => { +test("Then it should send email", () => {}); - }); - - test('Then there should not be a new transfer record', () => { - - }); +test("Then there should not be a new transfer record", () => {}); ``` ![alt text](assets/flat-report.png) @@ -735,7 +728,7 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr ## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid -:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit _all_ types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white*check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit \_all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. From a1a6234067aa454ad16cb69addf8e3356a0cbf6a Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 6 Apr 2020 23:36:45 +0200 Subject: [PATCH 192/502] create readme-pl.md initial --- readme-pl.md | 1949 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1949 insertions(+) create mode 100644 readme-pl.md diff --git a/readme-pl.md b/readme-pl.md new file mode 100644 index 00000000..386c481a --- /dev/null +++ b/readme-pl.md @@ -0,0 +1,1949 @@ + + +
    + +# 👇 Why this guide can take your testing skills to the next level + +
    + +## 📗 45+ best practices: Super-comprehensive and exhaustive + +This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and curates for you dozens of the best blog posts, books and tools the market has to offer + +## 🚢 Advanced: Goes 10,000 miles beyond the basics + +Hop into a journey that travels way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average + +## 🌐 Full-stack: front, backend, CI, anything + +Start by understanding the ubiquitous testing practices that are the foundation for any application tier. Then, delve into your area of choice: frontend/UI, backend, CI or maybe all of them? + +
    + +### Written By 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/) + +
    + +### Translations - read in your own language + +- 🇨🇳[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) +- Want to translate to your own language? please open an issue 💜 + +

    + +## `Table of Contents` + +#### [`Section 0: The Golden Rule`](#section-0️⃣-the-golden-rule) + +A single advice that inspires all the others (1 special bullet) + +#### [`Section 1: The Test Anatomy`](#section-1-the-test-anatomy-1) + +The foundation - structuring clean tests (12 bullets) + +#### [`Section 2: Backend`](#section-2️⃣-backend-testing) + +Writing backend and Microservices tests efficiently (8 bullets) + +#### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) + +Writing tests for web UI including component and E2E tests (11 bullets) + +#### [`Section 4: Measuring Tests Effectiveness`](#section-4️⃣-measuring-test-effectiveness) + +Watching the watchman - measuring test quality (4 bullets) + +#### [`Section 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) + +Guidelines for CI in the JS world (9 bullets) + +

    + +# Section 0️⃣: The Golden Rule + +
    + +## ⚪️ 0 The Golden Rule: Design for lean testing + +:white_check_mark: **Do:** +Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. + +Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. + +The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). + +This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. + +![alt text](/assets/headspace.png "We have no head room for additional complexity") + +Most of the advice below are derivatives of this principle. + +### Ready to start? + +

    + +# Section 1: The Test Anatomy + +
    + +## ⚪ ️ 1.1 Include 3 parts in each test name + +:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: + +(1) What is being tested? For example, the ProductsService.addNewProduct method + +(2) Under what circumstances and scenario? For example, no price is passed to the method + +(3) What is the expected result? For example, the new product is not approved + +
    + +❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? + +
    + +**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand + +
    Code Examples + +
    + +### :clap: Doing It Right Example: A test name that constitutes 3 parts + +![](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: Doing It Right Example: A test name that constitutes 3 parts + +![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") + +
    + +

    + +## ⚪ ️ 1.2 Structure tests by the AAA pattern + +:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: + +1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code + +2nd A - Act: Execute the unit under test. Usually 1 line of code + +3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code + +
    + +❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: A test structured with the AAA pattern + +![](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: Anti Pattern Example: No separation, one bulk, harder to interpret + +```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 Describe expectations in a product language: use BDD-style assertions + +:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +
    + +❌ **Otherwise:** The team will write less tests and decorate the annoying ones with .skip() + +
    + +
    Code Examples
    + +![](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: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story + +```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: Doing It Right Example: Skimming through the following declarative test is a breeze + +```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 Stick to black-box testing: Test only public methods + +:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden +
    + +❌ **Otherwise:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored… + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A test case is testing the internals for no good reason + +![](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 Choose the right test doubles: Avoid mocks in favor of stubs and spies + +:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). + +Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. + +For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently +
    + +❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-pattern example: Mocks focus on the internals + +![](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:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals + +```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; +}); +``` + +
    + +

    + +## 📗 Want to learn all these practices with live video? + +### Visit my online course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +

    + +## ⚪ ️1.6 Don’t “foo”, use realistic input data + +:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +
    + +❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data + +![](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:Doing It Right Example: Randomizing realistic input + +```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 Test many input combinations using Property-based testing + +:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +
    + +❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Testing many input permutations with “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 If needed, use only short & inline snapshots + +:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. + +On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much + +It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +
    + +❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code + +![](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: Doing It Right Example: Expectations are visible and focused + +```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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with 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 :[ +}); + +``` + +
    + +### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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 Don’t catch errors, expect them + +:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations + +A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +
    + +❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with 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: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical 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 Tag your tests + +:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +
    + +❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) + +![](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 Categorize tests under at least 2 levels + +:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and prtscn below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) + +
    + +❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below + +![](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: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests + +![](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 Other generic good testing hygiene + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +
    + +❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades + +

    + +# Section 2️⃣: Backend Testing + +## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid + +:white*check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit \_all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? + +Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. + +It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks + +A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] + +
    + +❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘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 Component testing might be your best affair + +:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. + +Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +
    + +❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) + +![](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 Ensure new releases don’t break the API using + +:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +
    + +❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: + +![](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 Test your middlewares in isolation + +:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +
    + +❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests + +
    + +
    Code Examples + +
    + +### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine + +![](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 Measure and refactor using static analysis tools + +:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) + +Credit: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + +❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: + +![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg "Examples with CodeClimate") + +![alt text](assets/bp-16-yoni-goldberg-quality.png "CodeClimat, a commercial tool that can identify complex methods:") + +
    + +

    + +## ⚪ ️ 2.6 Check your readiness for Node-related chaos + +:white_check_mark: **Do:** Weirdly, most software testings are about logic & data only, but some of the worst things that happen (and are really hard to mitigate) are infrastructural issues. For example, did you ever test what happens when your process memory is overloaded, or when the server/process dies, or does your monitoring system realizes when the API becomes 50% slower?. To test and mitigate these type of bad things — [Chaos engineering](https://principlesofchaos.org/) was born by Netflix. It aims to provide awareness, frameworks and tools for testing our app resiliency for chaotic issues. For example, one of its famous tools, [the chaos monkey](https://github.com/Netflix/chaosmonkey), randomly kills servers to ensure that our service can still serve users and not relying on a single server (there is also a Kubernetes version, [kube-monkey](https://github.com/asobti/kube-monkey), that kills pods). All these tools work on the hosting/platform level, but what if you wish to test and generate pure Node chaos like check how your Node process copes with uncaught errors, unhandled promise rejection, v8 memory overloaded with the max allowed of 1.7GB or whether your UX stays satisfactory when the event loop gets blocked often? to address this I’ve written, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) which provides all sort of Node-related chaotic acts +
    + +❌ **Otherwise:** No escape here, Murphy’s law will hit your production without mercy + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: : Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to 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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with 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 :[ +}); + +``` + +
    + +### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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️⃣: Frontend Testing + +## ⚪ ️ 3.1 Separate UI from functionality + +:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI + +
    + +❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Separating out the UI details + +![](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: Anti Pattern Example: Assertion mix UI details and data + +```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 HTML elements based on attributes that are unlikely to change + +:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed + +
    + +❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing + +![](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: Anti-Pattern Example: Relying on 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 Whenever possible, test with a realistic and fully rendered component + +:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake + +With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children + +
    + +❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Working realstically with a fully rendered component + +![](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: Anti-Pattern Example: Mocking the reality with 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 Don't sleep, use frameworks built-in support for async events. Also try to speed things up + +:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +
    + +❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (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: Doing It Right Example: Testing library that waits for DOM elements + +```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: Anti-Pattern Example: custom sleep code + +```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 Watch how the content is served over the network + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") + +✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 + +
    + +❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration + +
    + +
    Code Examples + +### :clap: Doing It Right Example: Lighthouse page load inspection report + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + +
    + +
    + +## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs + +:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests + +
    + +❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower + +
    + +
    Code Examples + +
    + +### :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 +// 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 Have very few end-to-end tests that spans the whole system + +:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment + +
    + +❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected + +
    + +## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials + +:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). + +
    + +❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Logging-in before-all and not 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 Have one E2E smoke test that just travels across the site map + +:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector + +
    + +❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Smoke travelling across all 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 Expose the tests as a live collaborative document + +:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. + +❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Describing tests in human-language using 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: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook + +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 Detect visual issues with automated tools + +:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue + +
    + +❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A typical visual regression - right content that is served badly + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + +### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots + +![](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: Doing It Right Example: Using Applitools to get snapshot comaprison and other advanced features + +![](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️⃣: Measuring Test Effectiveness + +

    + +## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number + +:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. + +Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets + +
    + +❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down + +
    + +
    Code Examples + +
    + +### :clap: Example: A typical coverage report + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: Doing It Right Example: Setting up coverage per component (using 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 Inspect coverage reports to detect untested areas and other oddities + +:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +
    + +❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: 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?") + +
    + +

    + +## ⚪ ️ 4.3 Measure logical coverage using mutation testing + +:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. + +Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: + +(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations + +(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. + +Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +
    + +❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: 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({ asignee: "John@mailer.com", price: 120 }); +}); //Triggers 100% code coverage, but it doesn't check anything +``` + +
    + +### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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 Preventing test code issues with Test linters + +:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) + +
    + +❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A test case full of errors, luckily all are caught by 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 and Other Quality Measures + +

    + +## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues + +:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +
    + +❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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 Shorten the feedback loop with local developer-CI + +:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. + +Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +
    + +❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new 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 Perform e2e testing over a true production-mirror + +:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. + +The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +
    + +❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated + +
    + +
    Code Examples + +
    + +### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([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 Parallelize test execution + +:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes + +❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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 Stay away from legal issues using license and plagiarism check + +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights + +❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: + +```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 Constantly inspect for vulnerable dependencies + +:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build + +❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious + +
    + +
    Code Examples + +
    + +### :clap: Example: NPM Audit result + +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + +

    + +## ⚪ ️5.7 Automate dependency updates + +:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: + +(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. + +(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). + +An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +
    + +❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky + +
    + +
    Code Examples + +
    + +### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest 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 Other, non-Node related, CI tips + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +
    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
    +
    + +❌ **Otherwise:** You‘ll miss years of wisdom + +

    + +## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions + +:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +
    + +❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? + +
    + +
    Code Examples + +
    + +### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions + +
    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 500 fortune corporates 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. + +## 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

    🖋
    + + + + + From b8862a0e919da6b9e67f892409f1d3dacf477cf4 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 7 Apr 2020 00:44:06 +0200 Subject: [PATCH 193/502] update readme-pl.md updated until 1.2 --- readme-pl.md | 375 ++++++++++++++++++++++++++------------------------- 1 file changed, 188 insertions(+), 187 deletions(-) diff --git a/readme-pl.md b/readme-pl.md index 386c481a..617ff1e0 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -2,118 +2,119 @@
    -# 👇 Why this guide can take your testing skills to the next level +# 👇 Powody dla których ten przewodnik może przenieść twoje umiejętności testowania na wyższy poziom
    -## 📗 45+ best practices: Super-comprehensive and exhaustive +## 📗 45+ najlepszych praktyk: super kompleksowe i wyczerpujące -This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and curates for you dozens of the best blog posts, books and tools the market has to offer +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 -## 🚢 Advanced: Goes 10,000 miles beyond the basics +## 🚢 Zaawansowane: przekracza 10 000 mil poza zwykłe podstawy -Hop into a journey that travels way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average +Wskocz w podróż, która wykracza poza podstawy, podróż do zaawansowanych tematów, takich jak testowanie na produkcji, testowanie mutacji, testowanie na podstawie właściwości i wiele innych strategicznych i profesjonalnych narzędzi. Jeśli przeczytasz każde słowo w tym przewodniku, Twoje umiejętności testowania prawdopodobnie przekroczą średnią -## 🌐 Full-stack: front, backend, CI, anything +## 🌐 Full-stack: front, backend, CI, wszystko -Start by understanding the ubiquitous testing practices that are the foundation for any application tier. Then, delve into your area of choice: frontend/UI, backend, CI or maybe all of them? +Zacznij od zrozumienia wszechobecnych praktyk testowania, które są podstawą każdej warstwy aplikacji. Następnie zagłęb się w wybrany obszar: frontend/UI, backend, CI, a może wszystkie?
    -### Written By Yoni Goldberg +### Napisane przez 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/) +- Konsultant JavaScript & Node.js +- 📗 [Testowanie Node.js i JavaScript od A do Z](https://www.testjavascript.com) - Mój kompleksowy kurs online z ponad [10 godzinami wideo](https://www.testjavascript.com), 14 typów testów i ponad 40 najlepszych praktyk +- [Obserwuj mnie na Twitter](https://twitter.com/goldbergyoni/)
    -### Translations - read in your own language +### Tłumaczenia - czytaj w swoim własnym języku -- 🇨🇳[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) -- Want to translate to your own language? please open an issue 💜 +- 🇨🇳[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) +- Chcesz przetłumaczyć na swój język? Proszę skorzystaj z issue 💜

    -## `Table of Contents` +## `Spis treści` -#### [`Section 0: The Golden Rule`](#section-0️⃣-the-golden-rule) +#### [`Sekcja 0: Złota zasada`](#section-0️⃣-the-golden-rule) -A single advice that inspires all the others (1 special bullet) +Jedna rada, która inspiruje wszystkich innych (1 specjalny punkt) -#### [`Section 1: The Test Anatomy`](#section-1-the-test-anatomy-1) +#### [`Sekcja 1: Anatomia testu`](#section-1-the-test-anatomy-1) -The foundation - structuring clean tests (12 bullets) +Podstawa - konstruowanie czystych testów (12 wypunktowań) -#### [`Section 2: Backend`](#section-2️⃣-backend-testing) +#### [`Sekcja 2: Backend`](#section-2️⃣-backend-testing) -Writing backend and Microservices tests efficiently (8 bullets) +Pisanie backendu i wydajne testy Mikroserwisów (8 wypunktowań) -#### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) +#### [`Sekcja 3: Frontend`](#section-3️⃣-frontend-testing) -Writing tests for web UI including component and E2E tests (11 bullets) +Pisanie testów dla webowego interfejsu użytkownika, w tym testy komponentów i testy E2E (11 wypunktowań) -#### [`Section 4: Measuring Tests Effectiveness`](#section-4️⃣-measuring-test-effectiveness) +#### [`Sekcja 4: Pomiary skuteczności testów`](#section-4️⃣-measuring-test-effectiveness) -Watching the watchman - measuring test quality (4 bullets) +Watching the watchman - pomiar jakości testu (4 wypunktowania) -#### [`Section 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) +#### [`Sekcja 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) -Guidelines for CI in the JS world (9 bullets) +Wytyczne dla CI w świecie JS (9 wypunktowań)

    -# Section 0️⃣: The Golden Rule +# Sekcja 0️⃣: Złota zasada
    -## ⚪️ 0 The Golden Rule: Design for lean testing +## ⚪️ 0 Złota zasada: Projektowanie dla lean testing -:white_check_mark: **Do:** -Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. +: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. -Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. +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. -The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). +Testy są okazją do czegoś innego - przyjaznego i uśmiechniętego asystenta, z którym przyjemnie się pracuje i zapewnia wielką wartość za tak małą inwestycję. Nauka mówi nam, że mamy dwa systemy mózgowe: system 1 służy do łatwych czynności, takich jak prowadzenie samochodu po pustej drodze, i system 2, który jest przeznaczony do złożonych i świadomych operacji, takich jak rozwiązywanie równania matematycznego. Zaprojektuj swój test dla systemu 1, gdy patrzysz na kod testowy, powinien on czuć się tak łatwo, jak modyfikacja dokumentu HTML, a nie jak rozwiązywanie 2X(17 × 24). -This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. +Można to osiągnąć poprzez selektywne wybieranie technik, narzędzi i celów testowych, które są opłacalne i zapewniają duży zwrot z inwestycji. Testuj tylko tyle, ile potrzeba, staraj się, aby był zwinny, czasem warto porzucić niektóre testy i wymienić niezawodność na zwinność i prostotę. ![alt text](/assets/headspace.png "We have no head room for additional complexity") -Most of the advice below are derivatives of this principle. +Większość poniższych porad to pochodne tej zasady. -### Ready to start? +### Gotów by rozpocząć?

    -# Section 1: The Test Anatomy +# Sekcja 1: Anatomia testu
    -## ⚪ ️ 1.1 Include 3 parts in each test name +## ⚪ ️ 1.1 Dołącz 3 części do każdej nazwy testu -:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: +:white_check_mark: **Opis:** Raport z testu powinien informować, czy bieżąca wersja aplikacji spełnia wymagania osób, które niekoniecznie znają kod: testera, wdrażającego inżyniera DevOps i przyszłego ciebie za dwa lata. Można to najlepiej osiągnąć, jeśli testy są na poziomie wymagań i obejmują 3 części: -(1) What is being tested? For example, the ProductsService.addNewProduct method +(1) Co jest testowane? Na przykład, metoda ProductsService.addNewProduct -(2) Under what circumstances and scenario? For example, no price is passed to the method +(2) W jakich okolicznościach i scenariuszu? Na przykład żadna cena nie jest przekazywana do metody -(3) What is the expected result? For example, the new product is not approved +(3) Jaki jest oczekiwany wynik? Na przykład nowy produkt nie został zatwierdzony
    -❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? +❌ **W przeciwnym razie:** Wdrożenie właśnie nie powiodło się, test o nazwie "Dodaj produkt" nie powiódł się. Czy to mówi ci, co dokładnie działa nieprawidłowo?
    -**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +**👇 Uwaga:** Każdy pocisk ma przykłady kodu, a czasem także ilustrację. Kliknij aby rozszerzyć -
    Code Examples +
    Przykłady kodu
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :clap: Przykład robienia tego dobrze: nazwa testu, która składa się z 3 części ![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Using Mocha to illustrate the idea") @@ -133,7 +134,7 @@ describe('Products Service', function() {
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :clap: Przykład robienia tego dobrze: nazwa testu, która składa się z 3 części ![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") @@ -141,9 +142,9 @@ describe('Products Service', function() {

    -## ⚪ ️ 1.2 Structure tests by the AAA pattern +## ⚪ ️ 1.2 Struktura testów według wzorca AAA -:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: +:white_check_mark: **Opis:** Ustrukturyzuj swoje testy za pomocą 3 dobrze oddzielonych sekcji: Arrange, Act & Assert (AAA). Przestrzeganie tej struktury gwarantuje, że czytelnik nie poświęci procesora mózgu na zrozumienie planu testu: 1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code @@ -153,11 +154,11 @@ describe('Products Service', function() {
    -❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain +❌ **W przeciwnym razie:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain
    -
    Code Examples +
    Przykłady kodu
    @@ -200,14 +201,14 @@ test("Should be classified as premium", () => { ## ⚪ ️1.3 Describe expectations in a product language: use BDD-style assertions -:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +:white_check_mark: **Opis:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/)
    -❌ **Otherwise:** The team will write less tests and decorate the annoying ones with .skip() +❌ **W przeciwnym razie:** The team will write less tests and decorate the annoying ones with .skip()
    -
    Code Examples
    +
    Przykłady kodu
    ![](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") @@ -260,13 +261,13 @@ it("When asking for an admin, ensure only ordered admins in results", () => { ## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods -:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden +:white_check_mark: **Opis:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden
    -❌ **Otherwise:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored… +❌ **W przeciwnym razie:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored…
    -
    Code Examples +
    Przykłady kodu
    @@ -302,18 +303,18 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response", ## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies -:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **Opis:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently
    -❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend +❌ **W przeciwnym razie:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend
    -
    Code Examples +
    Przykłady kodu
    @@ -361,14 +362,14 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 Don’t “foo”, use realistic input data -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +:white_check_mark: **Opis:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing).
    -❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **W przeciwnym razie:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA”
    -
    Code Examples +
    Przykłady kodu
    @@ -415,14 +416,14 @@ it("Better: When adding new valid product, get successful confirmation", async ( ## ⚪ ️ 1.7 Test many input combinations using Property-based testing -:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +:white_check_mark: **Opis:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained
    -❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs +❌ **W przeciwnym razie:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs
    -
    Code Examples +
    Przykłady kodu
    @@ -452,18 +453,18 @@ describe("Product service", () => { ## ⚪ ️ 1.8 If needed, use only short & inline snapshots -:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. +:white_check_mark: **Opis:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes
    -❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **W przeciwnym razie:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown...
    -
    Code Examples +
    Przykłady kodu
    @@ -522,11 +523,11 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => { :white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries)
    -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **W przeciwnym razie:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data
    -
    Code Examples +
    Przykłady kodu
    @@ -576,16 +577,16 @@ it("When updating site name, get successful confirmation", async () => { ## ⚪ ️ 1.10 Don’t catch errors, expect them -:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations +:white_check_mark: **Opis:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user
    -❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +❌ **W przeciwnym razie:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong
    -
    Code Examples +
    Przykłady kodu
    @@ -626,14 +627,14 @@ it("When no product name, it throws error 400", async () => { ## ⚪ ️ 1.11 Tag your tests -:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +:white_check_mark: **Opis:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’
    -❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests +❌ **W przeciwnym razie:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests
    -
    Code Examples +
    Przykłady kodu
    @@ -659,15 +660,15 @@ describe("Order service", function() { ## ⚪ ️ 1.12 Categorize tests under at least 2 levels -:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and prtscn below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **Opis:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and prtscn below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway)
    -❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause +❌ **W przeciwnym razie:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause
    -
    Code Examples +
    Przykłady kodu
    @@ -715,20 +716,20 @@ test("Then there should not be a new transfer record", () => {}); ## ⚪ ️1.13 Other generic good testing hygiene -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +:white_check_mark: **Opis:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc)
    -❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades +❌ **W przeciwnym razie:** You‘ll miss pearls of wisdom that were collected for decades

    -# Section 2️⃣: Backend Testing +# Sekcja 2️⃣: Backend Testing ## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid -:white*check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit \_all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white*check_mark: **Opis:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit \_all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. @@ -738,11 +739,11 @@ A word of caution: the TDD argument in the software world takes a typical false-
    -❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes +❌ **W przeciwnym razie:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes
    -
    Code Examples +
    Przykłady kodu
    @@ -762,16 +763,16 @@ A word of caution: the TDD argument in the software world takes a typical false- ## ⚪ ️2.2 Component testing might be your best affair -:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +:white_check_mark: **Opis:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time.
    -❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage +❌ **W przeciwnym razie:** You may spend long days on writing unit tests to find out that you got only 20% system coverage
    -
    Code Examples +
    Przykłady kodu
    @@ -787,14 +788,14 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ## ⚪ ️2.3 Ensure new releases don’t break the API using -:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +:white_check_mark: **Opis:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration
    -❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear +❌ **W przeciwnym razie:** The alternatives are exhausting manual testing or deployment fear
    -
    Code Examples +
    Przykłady kodu
    @@ -810,14 +811,14 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ## ⚪ ️ 2.4 Test your middlewares in isolation -:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +:white_check_mark: **Opis:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below)
    -❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests +❌ **W przeciwnym razie:** A bug in Express middleware === a bug in all or most requests
    -
    Code Examples +
    Przykłady kodu
    @@ -850,7 +851,7 @@ test("A request without authentication header, should return http status 403", ( ## ⚪ ️2.5 Measure and refactor using static analysis tools -:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) +:white_check_mark: **Opis:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) Credit: [Keith Holliday](https://github.com/TheHollidayInn) @@ -860,7 +861,7 @@ Credit: {

    -# Section 3️⃣: Frontend Testing +# Sekcja 3️⃣: Frontend Testing ## ⚪ ️ 3.1 Separate UI from functionality -:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI +:white_check_mark: **Opis:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI
    -❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation +❌ **W przeciwnym razie:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation
    -
    Code Examples +
    Przykłady kodu
    @@ -1008,15 +1009,15 @@ test("When flagging to show only VIP, should display only VIP members", () => { ## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change -:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +:white_check_mark: **Opis:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed
    -❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **W przeciwnym razie:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border'
    -
    Code Examples +
    Przykłady kodu
    @@ -1072,17 +1073,17 @@ test("Whenever no data is passed, error metric shows zero", () => { ## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component -:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +:white_check_mark: **Opis:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children
    -❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? +❌ **W przeciwnym razie:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance?
    -
    Code Examples +
    Przykłady kodu
    @@ -1147,11 +1148,11 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display :white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution
    -❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance +❌ **W przeciwnym razie:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance
    -
    Code Examples +
    Przykłady kodu
    @@ -1216,11 +1217,11 @@ test("movie title appears", async () => {
    -❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration +❌ **W przeciwnym razie:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration
    -
    Code Examples +
    Przykłady kodu ### :clap: Doing It Right Example: Lighthouse page load inspection report @@ -1232,15 +1233,15 @@ test("movie title appears", async () => { ## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs -:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +:white_check_mark: **Opis:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests
    -❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower +❌ **W przeciwnym razie:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower
    -
    Code Examples +
    Przykłady kodu
    @@ -1296,15 +1297,15 @@ test("When no products exist, show the appropriate message", () => { ## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials -:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). +:white_check_mark: **Opis:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6).
    -❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again +❌ **W przeciwnym razie:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again
    -
    Code Examples +
    Przykłady kodu
    @@ -1344,15 +1345,15 @@ beforeEach(setUser => () { ## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map -:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector +:white_check_mark: **Opis:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector
    -❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering +❌ **W przeciwnym razie:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering
    -
    Code Examples +
    Przykłady kodu
    @@ -1379,13 +1380,13 @@ it("When doing smoke testing over all page, should load them all successfully", ## ⚪ ️ 3.10 Expose the tests as a live collaborative document -:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. +:white_check_mark: **Opis:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. -❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value +❌ **W przeciwnym razie:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value
    -
    Code Examples +
    Przykłady kodu
    @@ -1422,15 +1423,15 @@ Feature: Twitter new tweet ## ⚪ ️ 3.11 Detect visual issues with automated tools -:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue +:white_check_mark: **Opis:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue
    -❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? +❌ **W przeciwnym razie:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden?
    -
    Code Examples +
    Przykłady kodu
    @@ -1496,23 +1497,23 @@ describe("visual validation", () => {

    -# Section 4️⃣: Measuring Test Effectiveness +# Sekcja 4️⃣: Measuring Test Effectiveness

    ## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number -:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. +:white_check_mark: **Opis:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets
    -❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down +❌ **W przeciwnym razie:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down
    -
    Code Examples +
    Przykłady kodu
    @@ -1534,14 +1535,14 @@ Implementation tips: You may want to configure your continuous integration (CI) ## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities -:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +:white_check_mark: **Opis:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas
    -❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from +❌ **W przeciwnym razie:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from
    -
    Code Examples +
    Przykłady kodu
    @@ -1557,7 +1558,7 @@ Based on a real-world scenario where we tracked our application usage in QA and ## ⚪ ️ 4.3 Measure logical coverage using mutation testing -:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +:white_check_mark: **Opis:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: @@ -1568,11 +1569,11 @@ Mutation-based testing is here to help by measuring the amount of code that was Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar
    -❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code +❌ **W przeciwnym razie:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code
    -
    Code Examples +
    Przykłady kodu
    @@ -1606,14 +1607,14 @@ it("Test addNewOrder, don't use such test names", () => { ## ⚪ ️4.4 Preventing test code issues with Test linters -:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +:white_check_mark: **Opis:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything)
    -❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation +❌ **W przeciwnym razie:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation
    -
    Code Examples +
    Przykłady kodu
    @@ -1637,20 +1638,20 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# Section 5️⃣: CI and Other Quality Measures +# Sekcja 5️⃣: CI and Other Quality Measures

    ## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues -:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +:white_check_mark: **Opis:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…)
    -❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day +❌ **W przeciwnym razie:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day
    -
    Code Examples +
    Przykłady kodu
    @@ -1664,16 +1665,16 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI -:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +:white_check_mark: **Opis:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky))
    -❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact +❌ **W przeciwnym razie:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact
    -
    Code Examples +
    Przykłady kodu
    @@ -1704,16 +1705,16 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com ## ⚪ ️5.3 Perform e2e testing over a true production-mirror -:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. +:white_check_mark: **Opis:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes.
    -❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated +❌ **W przeciwnym razie:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated
    -
    Code Examples +
    Przykłady kodu
    @@ -1727,13 +1728,13 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo ## ⚪ ️5.4 Parallelize test execution -:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes +:white_check_mark: **Opis:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes -❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant +❌ **W przeciwnym razie:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant
    -
    Code Examples +
    Przykłady kodu
    @@ -1747,13 +1748,13 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo ## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights +:white_check_mark: **Opis:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights -❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues +❌ **W przeciwnym razie:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues
    -
    Code Examples +
    Przykłady kodu
    @@ -1778,13 +1779,13 @@ license-checker --summary --failOn BSD ## ⚪ ️5.6 Constantly inspect for vulnerable dependencies -:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +:white_check_mark: **Opis:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build ❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious
    -
    Code Examples +
    Przykłady kodu
    @@ -1798,7 +1799,7 @@ license-checker --summary --failOn BSD ## ⚪ ️5.7 Automate dependency updates -:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: +:white_check_mark: **Opis:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: (1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. @@ -1807,11 +1808,11 @@ license-checker --summary --failOn BSD An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8)
    -❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky +❌ **W przeciwnym razie:** Your production will run packages that have been explicitly tagged by their author as risky
    -
    Code Examples +
    Przykłady kodu
    @@ -1825,25 +1826,25 @@ An efficient update policy may allow some ‘vesting period’ — let the c ## ⚪ ️ 5.8 Other, non-Node related, CI tips -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +:white_check_mark: **Opis:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known
    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

    -❌ **Otherwise:** You‘ll miss years of wisdom +❌ **W przeciwnym razie:** You‘ll miss years of wisdom

    ## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions -:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +:white_check_mark: **Opis:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that
    -❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? +❌ **W przeciwnym razie:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues?
    -
    Code Examples +
    Przykłady kodu
    @@ -1854,7 +1855,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    -# Team +# Zespół ## Yoni Goldberg @@ -1862,18 +1863,18 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -**Role:** Writer +**Rola:** Writer -**About:** I'm an independent consultant who works with 500 fortune corporates 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) +**Opis:** Jestem niezależnym konsultantem, który współpracuje z firmami Fortune 500 i garażowymi startupami przy dopracowywaniu aplikacji JS i Node.js. Bardziej niż jakikolwiek inny temat fascynuje mnie, i mam na celu, opanowanie sztuki testowania. Jestem także autorem [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) +**📗 Kurs online:** Podobał Ci się ten przewodnik i chcesz maksymalnie wykorzystać swoje umiejętności testowania? Rozważ skorzystanie z mojego kompleksowego kursu [Testowanie Node.js i JavaScript od A do Z](https://www.testjavascript.com)
    -**Follow:** +**Obserwuj:** - [🐦 Twitter](https://twitter.com/goldbergyoni/) -- [📞 Contact](https://testjavascript.com/contact-2/) +- [📞 Kontakt](https://testjavascript.com/contact-2/) - [✉️ Newsletter](https://testjavascript.com/newsletter//)
    @@ -1882,30 +1883,30 @@ An efficient update policy may allow some ‘vesting period’ — let the c ## [Bruno Scheufler](https://github.com/BrunoScheufler) -**Role:** Tech reviewer and advisor +**Rola:** Recenzent i doradca techniczny -Took care to revise, improve, lint and polish all the texts +Zadbał o poprawienie, ulepszenie, usunięcie i dopracowanie wszystkich tekstów -**About:** full-stack web engineer, Node.js & GraphQL enthusiast +**Opis:** full-stack web engineer, entuzjasta Node.js & GraphQL

    ## [Ido Richter](https://github.com/idori) -**Role:** Concept, design and great advice +**Rola:** Koncepcja, projekt i świetna rada -**About:** A savvy frontend developer, CSS expert and emojis freak +**Opis:** Wytrawny frontend developer, ekspert CSS i emojis freak ## [Kyle Martin](https://github.com/js-kyle) -**Role:** Helps keep this project running, and reviews security related practices +**Rola:** Pomaga utrzymać ten projekt w ruchu i weryfikuje praktyki związane z bezpieczeństwem -**About:** Loves working on Node.js projects and web application security. +**Opis:** Uwielbia pracę nad projektami Node.js i bezpieczeństwem aplikacji internetowych. -## Contributors ✨ +## Współtwórcy ✨ -Thanks goes to these wonderful people who have contributed to this repository! +Podziękowania dla tych wspaniałych ludzi, którzy przyczynili się do tego repozytorium! From 2ef53ffef0edaaf5c3bc8f221fb28b4e86a15b38 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 7 Apr 2020 01:38:30 +0200 Subject: [PATCH 194/502] update readme-pl.md updated until 1.7 --- readme-pl.md | 148 +++++++++++++++++++++++++-------------------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/readme-pl.md b/readme-pl.md index 617ff1e0..4e6f1e34 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -39,27 +39,27 @@ Zacznij od zrozumienia wszechobecnych praktyk testowania, które są podstawą k ## `Spis treści` -#### [`Sekcja 0: Złota zasada`](#section-0️⃣-the-golden-rule) +#### [`Sekcja 0: Złota zasada`](#sekcja-0️⃣-złota-zasada) Jedna rada, która inspiruje wszystkich innych (1 specjalny punkt) -#### [`Sekcja 1: Anatomia testu`](#section-1-the-test-anatomy-1) +#### [`Sekcja 1: Anatomia testu`](#sekcja-1-anatomia-testu) Podstawa - konstruowanie czystych testów (12 wypunktowań) -#### [`Sekcja 2: Backend`](#section-2️⃣-backend-testing) +#### [`Sekcja 2: Backend`](#sekcja-2️⃣-backend-testing) Pisanie backendu i wydajne testy Mikroserwisów (8 wypunktowań) -#### [`Sekcja 3: Frontend`](#section-3️⃣-frontend-testing) +#### [`Sekcja 3: Frontend`](#sekcja-3️⃣-frontend-testing) Pisanie testów dla webowego interfejsu użytkownika, w tym testy komponentów i testy E2E (11 wypunktowań) -#### [`Sekcja 4: Pomiary skuteczności testów`](#section-4️⃣-measuring-test-effectiveness) +#### [`Sekcja 4: Pomiary skuteczności testów`](#sekcja-4️⃣-pomiary-skuteczności-testów) Watching the watchman - pomiar jakości testu (4 wypunktowania) -#### [`Sekcja 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) +#### [`Sekcja 5: Continuous Integration`](#sekcja-5️⃣-ci-and-other-quality-measures) Wytyczne dla CI w świecie JS (9 wypunktowań) @@ -146,15 +146,15 @@ describe('Products Service', function() { :white_check_mark: **Opis:** Ustrukturyzuj swoje testy za pomocą 3 dobrze oddzielonych sekcji: Arrange, Act & Assert (AAA). Przestrzeganie tej struktury gwarantuje, że czytelnik nie poświęci procesora mózgu na zrozumienie planu testu: -1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code +1st A - Arrange: Cały kod instalacyjny, aby wprowadzić system do scenariusza, którego test ma na celu symulację. Może to obejmować tworzenie instancji testowanego konstruktora, dodawanie rekordów DB, mockowanie/usuwanie obiektów i każdy inny kod przygotowawczy -2nd A - Act: Execute the unit under test. Usually 1 line of code +2nd A - Act: Wykonaj unit pod test. Zwykle 1 linia kodu -3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code +3rd A - Assert: Upewnij się, że otrzymana wartość spełnia oczekiwania. Zwykle 1 linia kodu
    -❌ **W przeciwnym razie:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain +❌ **W przeciwnym razie:** Nie tylko spędzasz godziny na zrozumieniu głównego kodu, ale to, co powinno być najprostszą częścią dnia (testowanie) obciąża Twój mózg
    @@ -162,7 +162,7 @@ describe('Products Service', function() {
    -### :clap: Doing It Right Example: A test structured with the AAA pattern +### :clap: Przykład robienia tego dobrze: test oparty na wzorcu 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") @@ -184,7 +184,7 @@ describe("Customer classifier", () => {
    -### :thumbsdown: Anti Pattern Example: No separation, one bulk, harder to interpret +### :thumbsdown: Przykład antywzorca: brak separacji, jedna masa, trudniejsza do interpretacji ```javascript test("Should be classified as premium", () => { @@ -199,12 +199,12 @@ test("Should be classified as premium", () => {

    -## ⚪ ️1.3 Describe expectations in a product language: use BDD-style assertions +## ⚪ ️1.3 Opisz oczekiwania w języku produktu: stosuj asercje w stylu BDD -:white_check_mark: **Opis:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +:white_check_mark: **Opis:** Kodowanie testów w stylu deklaratywnym pozwala czytelnikowi na natychmiastowe złapanie go bez wydawania nawet jednego cyklu mózg-procesor. Kiedy piszesz kod imperatywny wypełniony logiką warunkową, czytelnik jest zmuszony wywierać więcej cykli mózg-procesor. W takim przypadku zakoduj oczekiwanie w języku przypominającym język człowieka, deklaratywnym stylu BDD, używając `expect` lub `should` i nie używając niestandardowego kodu. Jeśli Chai & Jest nie zawiera żądanej asercji i jest wysoce powtarzalne, rozważ [rozszerzenie Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) lub napisanie [wtyczki niestandardowej Chai](https://www.chaijs.com/guide/plugins/)
    -❌ **W przeciwnym razie:** The team will write less tests and decorate the annoying ones with .skip() +❌ **W przeciwnym razie:** Zespół napisze mniej testów i ozdobi części irytujące z .skip()
    @@ -212,7 +212,7 @@ test("Should be classified as premium", () => { ![](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: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story +### :thumbsdown: Przykład antywzorca: Czytelnik musi przejrzeć niezbyt krótki i imperatywny kod, aby uzyskać historię testową ```javascript test("When asking for an admin, ensure only ordered admins in results", () => { @@ -242,7 +242,7 @@ test("When asking for an admin, ensure only ordered admins in results", () => {
    -### :clap: Doing It Right Example: Skimming through the following declarative test is a breeze +### :clap: Przykład robienia tego dobrze: Przejrzenie poniższego testu deklaratywnego to pestka ```javascript it("When asking for an admin, ensure only ordered admins in results", () => { @@ -259,19 +259,19 @@ it("When asking for an admin, ensure only ordered admins in results", () => {

    -## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods +## ⚪ ️ 1.4 Trzymaj się testów czarnej skrzynki: testuj tylko metody publiczne -:white_check_mark: **Opis:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden +:white_check_mark: **Opis:** Testowanie elementów wewnętrznych przynosi ogromne koszty prawie za nic. Jeśli Twój kod / interfejs API zapewnia prawidłowe wyniki, czy naprawdę warto zainwestować następne 3 godziny w testowanie JAK działało ono wewnętrznie, a następnie utrzymać te delikatne testy? Za każdym razem, gdy sprawdzane jest zachowanie publiczne, implementacja prywatna jest również domyślnie testowana, a testy zostaną przerwane tylko w przypadku wystąpienia określonego problemu (np. nieprawidłowego wyniku). Takie podejście jest również określane jako `behavioral testing`. Z drugiej strony, jeśli przetestujesz wewnętrzne elementy (podejście z białą ramką) - skupiasz się na planowaniu wyniku komponentu na drobiazgowe szczegóły, a twój test może się zepsuć z powodu drobnych refaktorów kodu, chociaż wyniki są w porządku - to dramatycznie zwiększa konserwację, obciąża
    -❌ **W przeciwnym razie:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored… +❌ **W przeciwnym razie:** Twoje testy zachowują się jak [chłopiec, który wołał wilka](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): krzycząc false-positive (np. test kończy się niepowodzeniem, ponieważ zmieniono nazwę zmiennej prywatnej). Nic dziwnego, że ludzie wkrótce zaczną ignorować powiadomienia CI, aż pewnego dnia prawdziwy błąd zostanie zignorowany…
    Przykłady kodu
    -### :thumbsdown: Anti Pattern Example: A test case is testing the internals for no good reason +### :thumbsdown: Przykład antywzorca: Przypadek testowy testuje elementy wewnętrzne bez uzasadnionego powodu ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") @@ -301,16 +301,16 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response",

    -## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies +## ⚪ ️ ️1.5 Wybierz odpowiedni test doubles: Unikaj mockowania na rzecz stubs i spies -:white_check_mark: **Opis:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **Opis:** Test doubles są złem koniecznym, ponieważ są sprzężone z wewnętrznymi elementami aplikacji, ale niektóre zapewniają ogromną wartość ([Przeczytaj tutaj przypomnienie o test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). -Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. +Przed użyciem test doubles zadaj bardzo proste pytanie: czy używam go do testowania funkcjonalności, która pojawia się lub może pojawić się w dokumencie wymagań? Jeśli nie, jest to white-box testing smell. -For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently + Na przykład jeśli chcesz przetestować, czy aplikacja zachowuje się rozsądnie, gdy usługa płatnicza jest wyłączona, możesz zlikwidować usługę płatniczą i uruchomić niektóre zwracając ‘Brak odpowiedzi’, aby upewnić się, że testowana jednostka zwraca prawidłową wartość. To sprawdza zachowanie / odpowiedź / wynik naszej aplikacji w określonych scenariuszach. Możesz także użyć spies, aby potwierdzić, że wiadomość e-mail została wysłana, gdy ta usługa nie działa - jest to ponownie kontrola behawioralna, która prawdopodobnie pojawi się w dokumencie wymagań („Wyślij wiadomość e-mail, jeśli nie można zapisać płatności”). Z drugiej strony, jeśli mockujesz usługę płatności i upewniasz się, że została ona wywołana za pomocą odpowiednich typów JavaScript - wtedy twój test koncentruje się na wewnętrznych rzeczach, które nie mają nic z funkcjonalnością aplikacji i prawdopodobnie często się zmieniają
    -❌ **W przeciwnym razie:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend +❌ **W przeciwnym razie:** Wszelka refaktoryzacja kodu nakazuje wyszukiwanie wszystkich próbnych elementów w kodzie i odpowiednią aktualizację. Testy stają się ciężarem, a nie pomocnym przyjacielem
    @@ -318,7 +318,7 @@ For example, if you want to test that your app behaves reasonably when the payme
    -### :thumbsdown: Anti-pattern example: Mocks focus on the internals +### :thumbsdown: Przykład antywzorca: Mocks skupiające się na elementach wewnętrznych ![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg "Examples with Sinon") @@ -338,7 +338,7 @@ it("When a valid product is about to be deleted, ensure data access DAL was call
    -### :clap:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals +### :clap:Przykład robienia tego dobrze: spies koncentrują się na testowaniu wymagań, ale jako efekt uboczny nieuchronnie dotykają elementów wewnętrznych ```javascript it("When a valid product is about to be deleted, ensure an email is sent", async () => { @@ -354,18 +354,18 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

    -## 📗 Want to learn all these practices with live video? +## 📗 Chcesz poznać wszystkie te praktyki z wideo na żywo? -### Visit my online course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) +### Odwiedź mój kurs online [Testowanie Node.js i JavaScript od A do Z](https://www.testjavascript.com)

    -## ⚪ ️1.6 Don’t “foo”, use realistic input data +## ⚪ ️1.6 Nie "foo", używaj realistycznych danych wejściowych -:white_check_mark: **Opis:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +:white_check_mark: **Opis:** Często błędy produkcyjne są ujawniane pod bardzo konkretnymi i zaskakującymi danymi wejściowymi - im bardziej realistyczny jest wkład testowy, tym większe są szanse na wczesne wykrycie błędów. Użyj dedykowanych bibliotek takich jak [Faker](https://www.npmjs.com/package/faker) do generowania pseudo-rzeczywistych danych, które przypominają różnorodność i formę danych produkcyjnych. Na przykład takie biblioteki mogą generować realistyczne numery telefonów, nazwy użytkowników, karty kredytowe, nazwy firm, a nawet tekst „lorem ipsum”. Możesz także utworzyć niektóre testy (oprócz testów jednostkowych, a nie zamienników), które losowo dodają fałszywych danych, aby rozciągnąć testowaną jednostkę lub nawet zaimportować prawdziwe dane ze środowiska produkcyjnego. Chcesz przenieść go na wyższy poziom? Zobacz następny punkt (testy oparte na właściwościach).
    -❌ **W przeciwnym razie:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **W przeciwnym razie:** Wszystkie testy programistyczne będą fałszywie pokazywać kolor zielony, gdy użyjesz syntetycznych danych wejściowych, takich jak „Foo”, ale wtedy produkcja może zmienić kolor na czerwony, gdy haker przejdzie tak nieprzyjemny ciąg znaków, jak “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA”
    @@ -373,7 +373,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async
    -### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data +### :thumbsdown: Przykład antywzorca: Zestaw testowy, który przechodzi z powodu nierealistycznych danych ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -398,7 +398,7 @@ test("Wrong: When adding new product with valid properties, get successful confi
    -### :clap:Doing It Right Example: Randomizing realistic input +### :clap:Przykład robienia tego dobrze: Randomizing realistic input ```javascript it("Better: When adding new valid product, get successful confirmation", async () => { @@ -414,7 +414,7 @@ it("Better: When adding new valid product, get successful confirmation", async (

    -## ⚪ ️ 1.7 Test many input combinations using Property-based testing +## ⚪ ️ 1.7 Testowanie wielu kombinacji danych wejściowych za pomocą testów opartych na właściwościach :white_check_mark: **Opis:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained
    @@ -427,7 +427,7 @@ it("Better: When adding new valid product, get successful confirmation", async (
    -### :clap: Doing It Right Example: Testing many input permutations with “fast-check” +### :clap: Przykład robienia tego dobrze: Testing many input permutations with “fast-check” ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -468,7 +468,7 @@ It’s worth noting that there are few cases where long & external snapshots are
    -### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code +### :thumbsdown: Przykład antywzorca: Coupling our test to unseen 2000 lines of code ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -490,7 +490,7 @@ it("TestJavaScript.com is renderd correctly", () => {
    -### :clap: Doing It Right Example: Expectations are visible and focused +### :clap: Przykład robienia tego dobrze: Expectations are visible and focused ```javascript it("When visiting TestJavaScript.com home page, a menu is displayed", () => { @@ -556,7 +556,7 @@ it("When querying by site name, get the right site", async () => {
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: Przykład robienia tego dobrze: We can stay within the test, each test acts on its own set of data ```javascript it("When updating site name, get successful confirmation", async () => { @@ -590,7 +590,7 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e
    -### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch +### :thumbsdown: Przykład antywzorca: A long test case that tries to assert the existence of error with try-catch ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") @@ -611,7 +611,7 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM +### :clap: Przykład robienia tego dobrze: A human-readable expectation that could be understood easily, maybe even by QA or technical PM ```javascript it("When no product name, it throws error 400", async () => { @@ -638,7 +638,7 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) +### :clap: Przykład robienia tego dobrze: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -672,7 +672,7 @@ describe("Order service", function() {
    -### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below +### :clap: Przykład robienia tego dobrze: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -694,7 +694,7 @@ describe("Transfer service", () => {
    -### :thumbsdown: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests +### :thumbsdown: Przykład antywzorca: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Mocha") @@ -747,7 +747,7 @@ A word of caution: the TDD argument in the software world takes a typical false-
    -### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the sane way’ +### :clap: Przykład robienia tego dobrze: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘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’") @@ -776,7 +776,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) +### :clap: Przykład robienia tego dobrze: Supertest allows approaching Express API in-process (fast and cover many layers) ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") @@ -799,7 +799,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap: Doing It Right Example: +### :clap: Przykład robienia tego dobrze: ![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") @@ -822,7 +822,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :clap:Przykład robienia tego dobrze: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -865,7 +865,7 @@ Credit: {
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: Przykład robienia tego dobrze: We can stay within the test, each test acts on its own set of data ```javascript it("When updating site name, get successful confirmation", async () => { @@ -967,7 +967,7 @@ it("When updating site name, get successful confirmation", async () => {
    -### :clap: Doing It Right Example: Separating out the UI details +### :clap: Przykład robienia tego dobrze: Separating out the UI details ![](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") @@ -1021,7 +1021,7 @@ test("When flagging to show only VIP, should display only VIP members", () => {
    -### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing +### :clap: Przykład robienia tego dobrze: Querying an element using a dedicated attrbiute for testing ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") @@ -1050,7 +1050,7 @@ test("Whenever no data is passed to metric, show 0 as default", () => {
    -### :thumbsdown: Anti-Pattern Example: Relying on CSS attributes +### :thumbsdown: Przykład antywzorca: Relying on CSS attributes ```html @@ -1087,7 +1087,7 @@ With all that said, a word of caution is in order: this technique works for smal
    -### :clap: Doing It Right Example: Working realstically with a fully rendered component +### :clap: Przykład robienia tego dobrze: Working realstically with a fully rendered component ![](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") @@ -1119,7 +1119,7 @@ test("Realistic approach: When clicked to show filters, filters are displayed", }); ``` -### :thumbsdown: Anti-Pattern Example: Mocking the reality with shallow rendering +### :thumbsdown: Przykład antywzorca: Mocking the reality with shallow rendering ```javascript test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { @@ -1156,7 +1156,7 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) +### :clap: Przykład robienia tego dobrze: E2E API that resolves only when the async operations is done (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") @@ -1168,7 +1168,7 @@ cy.wait("@products"); // wait for route to appear // this line will get executed only when the route is ready ``` -### :clap: Doing It Right Example: Testing library that waits for DOM elements +### :clap: Przykład robienia tego dobrze: Testing library that waits for DOM elements ```javascript // @testing-library/dom @@ -1185,7 +1185,7 @@ test("movie title appears", async () => { }); ``` -### :thumbsdown: Anti-Pattern Example: custom sleep code +### :thumbsdown: Przykład antywzorca: custom sleep code ```javascript test("movie title appears", async () => { @@ -1223,7 +1223,7 @@ test("movie title appears", async () => {
    Przykłady kodu -### :clap: Doing It Right Example: Lighthouse page load inspection report +### :clap: Przykład robienia tego dobrze: Lighthouse page load inspection report ![](/assets/lighthouse2.png "Lighthouse page load inspection report") @@ -1245,7 +1245,7 @@ test("movie title appears", async () => {
    -### :clap: Doing It Right Example: Stubbing or intercepting API calls +### :clap: Przykład robienia tego dobrze: 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") @@ -1309,7 +1309,7 @@ test("When no products exist, show the appropriate message", () => {
    -### :clap: Doing It Right Example: Logging-in before-all and not before-each +### :clap: Przykład robienia tego dobrze: Logging-in before-all and not before-each ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") @@ -1357,7 +1357,7 @@ beforeEach(setUser => () {
    -### :clap: Doing It Right Example: Smoke travelling across all pages +### :clap: Przykład robienia tego dobrze: Smoke travelling across all pages ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") @@ -1390,7 +1390,7 @@ it("When doing smoke testing over all page, should load them all successfully",
    -### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js +### :clap: Przykład robienia tego dobrze: Describing tests in human-language using cucumber-js ![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") @@ -1411,7 +1411,7 @@ Feature: Twitter new tweet ``` -### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook +### :clap: Przykład robienia tego dobrze: Visualizing our components, their various states and inputs using Storybook ![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") @@ -1441,7 +1441,7 @@ Feature: Twitter new tweet
    -### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots +### :clap: Przykład robienia tego dobrze: Configuring wraith to capture and compare UI snapshots ![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") @@ -1470,7 +1470,7 @@ paths: path: /subscribe ``` -### :clap: Doing It Right Example: Using Applitools to get snapshot comaprison and other advanced features +### :clap: Przykład robienia tego dobrze: Using Applitools to get snapshot comaprison and other advanced features ![](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") @@ -1523,7 +1523,7 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :clap: Doing It Right Example: Setting up coverage per component (using Jest) +### :clap: Przykład robienia tego dobrze: Setting up coverage per component (using Jest) ![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") @@ -1546,7 +1546,7 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :thumbsdown: Anti-Pattern Example: What’s wrong with this coverage report? +### :thumbsdown: Przykład antywzorca: 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) @@ -1597,7 +1597,7 @@ it("Test addNewOrder, don't use such test names", () => {
    -### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :clap: Przykład robienia tego dobrze: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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)") @@ -1678,7 +1678,7 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com
    -### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: Przykład robienia tego dobrze: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code ```javascript "scripts": { @@ -1738,7 +1738,7 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    -### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :clap: Przykład robienia tego dobrze: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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)") @@ -1758,7 +1758,7 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    -### :clap: Doing It Right Example: +### :clap: Przykład robienia tego dobrze: ```javascript //install license-checker in your CI environment or also locally From 4dc922fa253aa914f8105b1da9d0f8953be59b4c Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 7 Apr 2020 10:58:37 +0200 Subject: [PATCH 195/502] update readme-pl.md updated until 2.4 --- readme-pl.md | 101 ++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/readme-pl.md b/readme-pl.md index 4e6f1e34..a9d8e074 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -416,10 +416,10 @@ it("Better: When adding new valid product, get successful confirmation", async ( ## ⚪ ️ 1.7 Testowanie wielu kombinacji danych wejściowych za pomocą testów opartych na właściwościach -:white_check_mark: **Opis:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained + :white_check_mark: **Opis:** Zazwyczaj wybieramy kilka próbek wejściowych dla każdego testu. Nawet jeśli format wejściowy przypomina dane rzeczywiste (zobacz punkt ‘Nie foo’), obejmujemy tylko kilka kombinacji danych wejściowych (method(‘’, true, 1), method(“string” , false” , 0)). Jednak w produkcji interfejs API, który jest wywoływany z 5 parametrami, może być wywoływany z tysiącami różnych kombinacji, jeden z nich może spowolnić nasz proces ([zobacz Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). Co jeśli mógłbyś napisać pojedynczy test, który automatycznie wysyła 1000 permutacji różnych danych wejściowych i wyłapuje, dla których danych wejściowych nasz kod nie zwraca poprawnej odpowiedzi? Testowanie oparte na właściwościach jest techniką, która robi dokładnie to: wysyłając wszystkie możliwe kombinacje danych wejściowych do testowanego urządzenia, zwiększa to prawdopodobieństwo znalezienia błędu. Na przykład, biorąc pod uwagę metodę - addNewProduct (identyfikator, nazwa, isDiscount) - biblioteki obsługujące będą wywoływać tę metodę z wieloma kombinacjami (liczba, łańcuch, wartość logiczna), takich jak (1, „iPhone”, false), (2, „Galaxy ", prawdziwe). Możesz uruchomić testy oparte na właściwościach, używając swojego ulubionego test runnera (Mocha, Jest, etc) za pomocą bibliotek takich jak [js-verify](https://github.com/jsverify/jsverify) lub [testcheck](https://github.com/leebyron/testcheck-js) (znacznie lepsza dokumentacja). Aktualizacja: Nicolas Dubien sugeruje w komentarzach poniżej [sprawdzenie fast-check](https://github.com/dubzzz/fast-check#readme) który wydaje się oferować dodatkowe funkcje, a także jest aktywnie utrzymywany
    -❌ **W przeciwnym razie:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs +❌ **W przeciwnym razie:** Nieświadomie wybierasz wejścia testowe, które obejmują tylko dobrze działające ścieżki kodu. Niestety obniża to efektywność testowania jako pojazdu do wykrywania błędów
    @@ -451,16 +451,16 @@ describe("Product service", () => {

    -## ⚪ ️ 1.8 If needed, use only short & inline snapshots +## ⚪ ️ 1.8 Jeśli potrzeba, użyj tylko short & inline snapshots -:white_check_mark: **Opis:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. +:white_check_mark: **Opis:** Gdzie jest potrzeba na [testy snapshot](https://jestjs.io/docs/en/snapshot-testing), używaj tylko krótkich i skoncentrowanych snapshotów (np. 3-7 linie), które są uwzględnione w ramach testu ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) i nie w plikach zewnętrznych. Przestrzeganie tych wytycznych sprawi, że testy będą zrozumiałe i mniej kruche. -On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much +Z drugiej strony, poradniki ‘klasycznych snapshotów’ i narzędzia zachęcają do przechowywania dużych plików (np. znaczników renderowania komponentu, wyniku API JSON) na jakimś zewnętrznym nośniku i zapewniają za każdym razem, gdy uruchamiany jest test w celu porównania otrzymanego wyniku z zapisaną wersją. To, na przykład, może pośrednio powiązać nasz test z 1000 liniami z 3000 wartościami danych, o których twórca testów nigdy nie czytał i nie uzasadniał. Dlaczego to źle? W ten sposób istnieje 1000 powodów niepowodzenia testu - wystarczy zmienić jedną linię, aby migawka stała się nieważna, i prawdopodobnie zdarzy się to często. Jak często? Dla każdej przestrzeni, komentarza lub drobnej zmiany CSS / HTML. Nie tylko to, nazwa testu nie dałaby informacji o niepowodzeniu, ponieważ po prostu sprawdza, czy 1000 wierszy się nie zmieniło, zachęca także twórcy testu do zaakceptowania jako pożądanego prawdziwego długiego dokumentu, którego nie mógł sprawdzić i zweryfikować. Wszystko to są objawy niejasnego i niecierpliwego testu, który nie jest skoncentrowany i ma na celu osiągnięcie zbyt wiele -It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +Warto zauważyć, że istnieje kilka przypadków, w których dopuszczalne są długie i zewnętrzne migawki - podczas potwierdzania schematu, a nie danych (wyodrębnianie wartości i skupianie się na polach) lub gdy otrzymany dokument rzadko się zmienia
    -❌ **W przeciwnym razie:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **W przeciwnym razie:** Test interfejsu użytkownika kończy się niepowodzeniem. Kod wydaje się prawidłowy, ekran wyświetla idealne piksele, co się stało? Twoje testowanie migawek właśnie znalazło różnicę między dokumentem źródłowym, a aktualnie otrzymanym - do znacznika została dodana pojedyncza spacja...
    @@ -468,7 +468,7 @@ It’s worth noting that there are few cases where long & external snapshots are
    -### :thumbsdown: Przykład antywzorca: Coupling our test to unseen 2000 lines of code +### :thumbsdown: Przykład antywzorca: Łączymy nasz test z niewidzialnymi 2000 liniami kodu ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -490,7 +490,7 @@ it("TestJavaScript.com is renderd correctly", () => {
    -### :clap: Przykład robienia tego dobrze: Expectations are visible and focused +### :clap: Przykład robienia tego dobrze: Oczekiwania są widoczne i skoncentrowane ```javascript it("When visiting TestJavaScript.com home page, a menu is displayed", () => { @@ -518,12 +518,12 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {

    -## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test +## ⚪ ️1.9 Unikaj globalnych test fixture i seeds, dodawaj dane na test -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +: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)
    -❌ **W przeciwnym razie:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **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
    @@ -531,7 +531,7 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {
    -### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :thumbsdown: Przykład antywzorca: testy nie są niezależne i polegają na pewnym globalnym hook do zasilania globalnych danych BD ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") @@ -556,7 +556,7 @@ it("When querying by site name, get the right site", async () => {
    -### :clap: Przykład robienia tego dobrze: We can stay within the test, each test acts on its own set of data +### :clap: Przykład robienia tego dobrze: Możemy pozostać w teście, każdy test działa na własny zestaw danych ```javascript it("When updating site name, get successful confirmation", async () => { @@ -575,14 +575,14 @@ it("When updating site name, get successful confirmation", async () => {
    -## ⚪ ️ 1.10 Don’t catch errors, expect them +## ⚪ ️ 1.10 Nie wychwytuj błędów, oczekuj ich -:white_check_mark: **Opis:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations +:white_check_mark: **Opis:** Podczas próby stwierdzenia, że niektóre dane wejściowe powodują błąd, może być właściwe użycie try-catch-finally i zapewnienie, że wprowadzono klauzulę catch. Wynikiem jest niezręczny i pełny przypadek testowy (przykład poniżej), który ukrywa prosty cel testu i oczekiwania na wynik -A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +Bardziej elegancką alternatywą jest użycie dedykowanego asercji Chai w jednym wierszu: expect (method).to.throw (lub w Jest: expect(method).toThrow()). Absolutnie obowiązkowe jest również upewnienie się, że wyjątek zawiera właściwość określającą typ błędu, w przeciwnym razie biorąc pod uwagę tylko ogólny błąd, aplikacja nie będzie w stanie zrobić wiele, zamiast wyświetlać rozczarowujący komunikat użytkownikowi
    -❌ **W przeciwnym razie:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +❌ **W przeciwnym razie:** Trudno będzie wnioskować z raportów testów (np. raportów CI), co poszło nie tak
    @@ -590,7 +590,7 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e
    -### :thumbsdown: Przykład antywzorca: A long test case that tries to assert the existence of error with try-catch +### :thumbsdown: Przykład antywzorca: Długi przypadek testowy, który próbuje potwierdzić istnienie błędu z try-catch ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") @@ -611,7 +611,7 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Przykład robienia tego dobrze: A human-readable expectation that could be understood easily, maybe even by QA or technical PM +### :clap: Przykład robienia tego dobrze: Oczekiwanie czytelne dla człowieka, które może być łatwo zrozumiane, może nawet przez QA lub technicznego PM ```javascript it("When no product name, it throws error 400", async () => { @@ -625,12 +625,12 @@ it("When no product name, it throws error 400", async () => {

    -## ⚪ ️ 1.11 Tag your tests +## ⚪ ️ 1.11 Taguj twoje testy -:white_check_mark: **Opis:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +:white_check_mark: **Opis:** Różne testy muszą być uruchamiane w różnych scenariuszach: quick smoke, IO-less, testy powinny być uruchamiane, gdy programista zapisuje lub commituje plik, pełne kompleksowe testy zwykle uruchamiane są po przesłaniu nowego pull requesta itp. Można to osiągnąć poprzez oznaczenie testów słowami kluczowymi takimi jak #cold #api #sanity, aby można było grepować za pomocą uprzęży testującej i wywołać pożądany podzbiór. Na przykład w ten sposób można wywołać tylko grupę sanity test z Mocha: mocha — grep ‘sanity’
    -❌ **W przeciwnym razie:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests +❌ **W przeciwnym razie:** Uruchamianie wszystkich testów, w tym testów, które wykonują dziesiątki zapytań BD, za każdym razem, gdy programista wprowadzi małą zmianę, może być bardzo powolny i powstrzymuje programistów przed uruchomieniem testów
    @@ -638,7 +638,7 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Przykład robienia tego dobrze: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) +### :clap: Przykład robienia tego dobrze: Tagowanie testów jako ‘#cold-test’ umożliwia test runnerowi wykonywanie tylko szybkich testów (testy cold===quick które nie wykonują operacji wejścia/wyjścia i mogą być wykonywane często, nawet gdy programista pisze) ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -658,13 +658,13 @@ describe("Order service", function() {

    -## ⚪ ️ 1.12 Categorize tests under at least 2 levels +## ⚪ ️ 1.12 Kategoryzuj testy na co najmniej 2 poziomach -:white_check_mark: **Opis:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and prtscn below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **Opis:** Zastosuj pewną strukturę do swojego zestawu testów, aby od czasu do czasu odwiedzający mógł łatwo zrozumieć wymagania (testy to najlepsza dokumentacja) i różne testowane scenariusze. Powszechną metodą jest umieszczanie co najmniej 2 bloków 'opisz' nad testami: pierwszy to nazwa testowanej jednostki, a drugi to dodatkowy poziom kategoryzacji, taki jak scenariusz lub kategorie niestandardowe (patrz przykłady kodu i prtscn poniżej). Takie postępowanie znacznie poprawi również raporty z testów: Czytelnik łatwo wywnioskuje kategorie testów, zagłębi się w żądaną sekcję i skoreluje testy zakończone niepowodzeniem. Ponadto programistom łatwiej będzie poruszać się po kodzie pakietu z wieloma testami. Istnieje wiele alternatywnych struktur dla zestawu testów, które możesz rozważyć, jak [given-when-then](https://github.com/searls/jasmine-given) oraz [RITE](https://github.com/ericelliott/riteway)
    -❌ **W przeciwnym razie:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause +❌ **W przeciwnym razie:** Patrząc na raport z płaską i długą listą testów, czytelnik musi przejrzeć długie teksty, aby zakończyć główne scenariusze i skorelować powszechność nieudanych testów. Rozważ następujący przypadek: gdy testy 7/100 zakończą się niepowodzeniem, przeglądanie płaskiej listy będzie wymagało przeczytania tekstu testów zakończonych niepowodzeniem, aby zobaczyć, jak się ze sobą wiążą. Jednak w hierarchicznym raporcie wszystkie z nich mogą podlegać temu samemu przepływowi lub kategorii, a czytelnik szybko zorientuje się, co lub gdzie jest źródło przyczyny awarii
    @@ -672,7 +672,7 @@ describe("Order service", function() {
    -### :clap: Przykład robienia tego dobrze: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below +### :clap: Przykład robienia tego dobrze: Strukturyzacja pakietu z nazwą jednostki pod test i scenariuszy doprowadzi do wygodnego raportu pokazanego poniżej ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -694,7 +694,7 @@ describe("Transfer service", () => {
    -### :thumbsdown: Przykład antywzorca: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests +### :thumbsdown: Przykład antywzorca: Płaska lista testów utrudni czytelnikowi identyfikację historii użytkowników i skorelowanie testów zakończonych niepowodzeniem ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Mocha") @@ -714,32 +714,33 @@ test("Then there should not be a new transfer record", () => {});

    -## ⚪ ️1.13 Other generic good testing hygiene +## ⚪ ️1.13 Inne ogólne dobre zasady higieny testowania + +:white_check_mark: **Opis:** Ten post skupia się na poradach dotyczących testowania, które są związane lub przynajmniej mogą być zilustrowane przykładem Node JS. Ten punkt zawiera jednak kilka dobrze znanych wskazówek niezwiązanych z Node -:white_check_mark: **Opis:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +Uczyć się i ćwiczyć [zasady TDD](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — dla wielu są niezwykle cenne, ale nie przestrasz się, jeśli nie pasują do Twojego stylu, nie tylko tobie. Rozważ napisanie testów przed kodem w [style red-green-refactor](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), upewnij się, że każdy test sprawdza dokładnie jedną rzecz, gdy znajdziesz błąd - przed naprawą napisz test, który wykryje ten błąd w przyszłości, pozwól każdemu testowi zawieść co najmniej raz, zanim zmieni kolor na zielony, uruchom moduł, pisząc szybki i uproszczony kod, który satysfakcjonuje test - następnie stopniowo refaktoryzuj i przenieś go do poziomu klasy produkcyjnej, unikaj jakiejkolwiek zależności od środowiska (ścieżki, systemu operacyjnego itp.)
    -❌ **W przeciwnym razie:** You‘ll miss pearls of wisdom that were collected for decades +❌ **W przeciwnym razie:** Będziesz tęsknić za perłami mądrości zbieranymi przez dziesięciolecia

    # Sekcja 2️⃣: Backend Testing -## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid +## ⚪ ️2.1 Wzbogać swoje portfolio testowe: wyjdź poza testy jednostkowe i piramidę -:white*check_mark: **Opis:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit \_all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white*check_mark: **Opis:** [Piramida testowania](https://martinfowler.com/bliki/TestPyramid.html), pomimo że 10> lat starsza, to świetny i odpowiedni model, który sugeruje trzy typy testowania i wpływa na strategię testowania większości programistów. Jednocześnie pojawiła się ponad garstka nowych, błyszczących technik testowania, które ukrywają się w cieniu piramidy testowania. Biorąc pod uwagę wszystkie dramatyczne zmiany, które widzieliśmy w ciągu ostatnich 10 lat (Microservices, cloud, serverless), czy jest możliwe, że jeden dość stary model będzie odpowiedni \_wszystkim* typom aplikacji? Czy świat testowania nie powinien rozważyć przyjęcia nowych technik testowania? -Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. +Nie zrozumcie mnie źle, w 2019 roku piramida testowania, TDD i testy jednostkowe są nadal potężną techniką i prawdopodobnie najlepiej pasują do wielu aplikacji. Tylko jak każdy inny model, pomimo swojej przydatności, [czasem musi się mylić](https://en.wikipedia.org/wiki/All_models_are_wrong). Rozważmy na przykład aplikację IOT, która pobiera wiele zdarzeń do magistrali komunikatów, takiej jak Kafka/RabbitMQ, która następnie przepływa do jakiejś hurtowni danych i jest w końcu odpytywana przez interfejs analityczny. Czy naprawdę powinniśmy wydać 50% naszego budżetu z testów na pisanie testów jednostkowych dla aplikacji, która jest zorientowana na integrację i prawie nie ma logiki? Wraz ze wzrostem różnorodności typów aplikacji (boty, krypto, Alexa-skills) rośnie szansa na znalezienie scenariuszy, w których piramida testowania nie jest najlepszym rozwiązaniem. -It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks +Nadszedł czas, aby wzbogacić swoje portfolio testowania i zapoznać się z większą liczbą typów testów (następne punkty sugerują kilka pomysłów), modelami umysłu, takimi jak piramida testowania, ale także dopasować typy testowania do rzeczywistych problemów, z którymi się borykasz (‘Hej, nasz interfejs API jest zepsuty, napiszmy testowanie umów konsumenckich!’), zdywersyfikuj swoje testy jak inwestor, który buduje portfel na podstawie analizy ryzyka - oceń, gdzie mogą pojawić się problemy i dopasuj niektóre środki zapobiegawcze, aby zmniejszyć potencjalne ryzyko -A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] +Słowo ostrzeżenia: argument TDD w świecie oprogramowania ma typową fałszywą dychotomię, niektórzy głoszą, że można go używać wszędzie, inni uważają, że to diabeł. Każdy, kto mówi w absolutach, jest w błędzie :]
    -❌ **W przeciwnym razie:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes +❌ **W przeciwnym razie:** Będziesz tęsknić za niektórymi narzędziami z niesamowitym ROI, takimi jak Fuzz, lint, i mutacją która może zapewnić wartość w 10 minut
    @@ -747,11 +748,11 @@ A word of caution: the TDD argument in the software world takes a typical false-
    -### :clap: Przykład robienia tego dobrze: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the sane way’ +### :clap: Przykład robienia tego dobrze: Cindy Sridharan sugeruje wzbogacić portfolio testowania w swoim niesamowitym poście 'Testowanie mikrousług - w ten sam sposób' ![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) +☺️Przykład: [YouTube: “Beyond Unit Tests: 5 Shiny Node.JS Test Types (2018)” (Yoni Goldberg)](https://www.youtube.com/watch?v=-2zP494wdUY&feature=youtu.be)
    @@ -761,14 +762,14 @@ A word of caution: the TDD argument in the software world takes a typical false-

    -## ⚪ ️2.2 Component testing might be your best affair +## ⚪ ️2.2 Testowanie komponentów może być Twoją najlepszą kwestią -:white_check_mark: **Opis:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +: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. -Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +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.
    -❌ **W przeciwnym razie:** You may spend long days on writing unit tests to find out that you got only 20% system coverage +❌ **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
    @@ -776,7 +777,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap: Przykład robienia tego dobrze: Supertest allows approaching Express API in-process (fast and cover many layers) +### :clap: Przykład robienia tego dobrze: Supertest pozwala zbliżyć się do Express API w trakcie procesu (szybki i obejmuje wiele warstw) ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") @@ -786,12 +787,12 @@ Component tests focus on the Microservice ‘unit’, they work against the API,

    -## ⚪ ️2.3 Ensure new releases don’t break the API using +## ⚪ ️2.3 Upewnij się, że nowe wersje nie psują interfejsu API -:white_check_mark: **Opis:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +:white_check_mark: **Opis:** Tak więc twój mikroserwis ma wielu klientów i uruchamiasz wiele wersji usługi ze względu na kompatybilność (aby wszyscy byli zadowoleni). Potem zmieniasz jakieś pole i 'buum!'. Jakiś ważny klient, który działa na tym, jest zły. Oto Catch-22 w świecie integracji: po stronie serwera bardzo trudne jest uwzględnienie wszystkich oczekiwań wielu klientów - z drugiej strony klienci nie mogą przeprowadzać żadnych testów, ponieważ serwer kontroluje daty wydania. [Umowy konsumenckie i framework PACT](https://docs.pact.io/) stworzone zostały, aby sformalizować ten proces z bardzo destrukcyjnym podejściem - nie serwer sam określa plan testów, a klient określa testy… serwera! PACT może rejestrować oczekiwania klienta i umieszczać je we wspólnej lokalizacji, „brokerze”, dzięki czemu serwer może wyciągać oczekiwania i uruchamiać każdą kompilację za pomocą biblioteki PACT, aby wykrywać zerwane umowy - oczekiwanie klienta, które nie jest spełnione. W ten sposób wszystkie niedopasowania API serwer-klient zostaną wykryte wcześnie podczas kompilacji / CI i mogą zaoszczędzić sporo frustracji.
    -❌ **W przeciwnym razie:** The alternatives are exhausting manual testing or deployment fear +❌ **W przeciwnym razie:** Alternatywami są wyczerpujące testy ręczne lub strach przed wdrożeniem
    @@ -809,7 +810,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,

    -## ⚪ ️ 2.4 Test your middlewares in isolation +## ⚪ ️ 2.4 Przetestuj swoje middleware w izolacji :white_check_mark: **Opis:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below)
    From e67870e730b86e7a7cbefd5676d5eae42088aca0 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 7 Apr 2020 11:47:18 +0200 Subject: [PATCH 196/502] update readme-pl updated until 3.6 --- readme-pl.md | 90 +++++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/readme-pl.md b/readme-pl.md index a9d8e074..c7c5e143 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -730,7 +730,7 @@ Uczyć się i ćwiczyć [zasady TDD](https://www.sm-cloud.com/book-review-test-d ## ⚪ ️2.1 Wzbogać swoje portfolio testowe: wyjdź poza testy jednostkowe i piramidę -:white*check_mark: **Opis:** [Piramida testowania](https://martinfowler.com/bliki/TestPyramid.html), pomimo że 10> lat starsza, to świetny i odpowiedni model, który sugeruje trzy typy testowania i wpływa na strategię testowania większości programistów. Jednocześnie pojawiła się ponad garstka nowych, błyszczących technik testowania, które ukrywają się w cieniu piramidy testowania. Biorąc pod uwagę wszystkie dramatyczne zmiany, które widzieliśmy w ciągu ostatnich 10 lat (Microservices, cloud, serverless), czy jest możliwe, że jeden dość stary model będzie odpowiedni \_wszystkim* typom aplikacji? Czy świat testowania nie powinien rozważyć przyjęcia nowych technik testowania? +:white_check_mark: **Opis:** [Piramida testowania](https://martinfowler.com/bliki/TestPyramid.html), pomimo że 10> lat starsza, to świetny i odpowiedni model, który sugeruje trzy typy testowania i wpływa na strategię testowania większości programistów. Jednocześnie pojawiła się ponad garstka nowych, błyszczących technik testowania, które ukrywają się w cieniu piramidy testowania. Biorąc pod uwagę wszystkie dramatyczne zmiany, które widzieliśmy w ciągu ostatnich 10 lat (Microservices, cloud, serverless), czy jest możliwe, że jeden dość stary model będzie odpowiedni _wszystkim_ typom aplikacji? Czy świat testowania nie powinien rozważyć przyjęcia nowych technik testowania? Nie zrozumcie mnie źle, w 2019 roku piramida testowania, TDD i testy jednostkowe są nadal potężną techniką i prawdopodobnie najlepiej pasują do wielu aplikacji. Tylko jak każdy inny model, pomimo swojej przydatności, [czasem musi się mylić](https://en.wikipedia.org/wiki/All_models_are_wrong). Rozważmy na przykład aplikację IOT, która pobiera wiele zdarzeń do magistrali komunikatów, takiej jak Kafka/RabbitMQ, która następnie przepływa do jakiejś hurtowni danych i jest w końcu odpytywana przez interfejs analityczny. Czy naprawdę powinniśmy wydać 50% naszego budżetu z testów na pisanie testów jednostkowych dla aplikacji, która jest zorientowana na integrację i prawie nie ma logiki? Wraz ze wzrostem różnorodności typów aplikacji (boty, krypto, Alexa-skills) rośnie szansa na znalezienie scenariuszy, w których piramida testowania nie jest najlepszym rozwiązaniem. @@ -812,10 +812,11 @@ Testy komponentów koncentrują się na mikroserwisowej ‘jednostce’, działa ## ⚪ ️ 2.4 Przetestuj swoje middleware w izolacji -:white_check_mark: **Opis:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +:white_check_mark: **Opis:** Wiele osób unika testowania oprogramowania pośredniego, ponieważ stanowią one niewielką część systemu i wymagają aktywnego serwera Express. Oba powody są błędne - oprogramowanie pośrednie jest małe, ale wpływa na wszystkie lub większość żądań i można je łatwo przetestować jako dostępne funkcje {req,res} obiekty JS. Aby przetestować funkcję oprogramowania pośredniego, należy ją po prostu wywołać i szpiegować ([używając na przykład Sinon](https://www.npmjs.com/package/sinon)) w połączeniu z obiektami {req,res} aby upewnić się, że funkcja wykonała właściwą akcję. +Biblioteka [node-mock-http](https://www.npmjs.com/package/node-mocks-http) posuwa się jeszcze dalej i uwzględnia obiekty {req, res} wraz ze szpiegowaniem ich zachowania. Na przykład można sprawdzić, czy status HTTP ustawiony w obiekcie res jest zgodny z oczekiwaniami (patrz przykład poniżej)
    -❌ **W przeciwnym razie:** A bug in Express middleware === a bug in all or most requests +❌ **W przeciwnym razie:** Błąd w middleware Express === błąd we wszystkich lub większości żądań
    @@ -823,7 +824,7 @@ Testy komponentów koncentrują się na mikroserwisowej ‘jednostce’, działa
    -### :clap:Przykład robienia tego dobrze: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :clap:Przykład robienia tego dobrze: Tesowanie middleware w izolacji, bez wykonywania połączeń sieciowych i budzenia całej maszyny Express ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -850,15 +851,15 @@ test("A request without authentication header, should return http status 403", (

    -## ⚪ ️2.5 Measure and refactor using static analysis tools +## ⚪ ️2.5 Pomiar i refaktoryzacja za pomocą narzędzi do analizy statycznej -:white_check_mark: **Opis:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) +:white_check_mark: **Opis:** Korzystanie z narzędzi do analizy statycznej pomaga, zapewniając obiektywne sposoby poprawy jakości kodu i utrzymania kodu w stanie możliwym do utrzymania. Możesz dodać narzędzia analizy statycznej do kompilacji CI, aby przerwać, gdy wykryje code smells. Jego głównymi zaletami w stosunku do zwykłego lintowania jest możliwość kontroli jakości w kontekście wielu plików (np. wykrywanie duplikacji), przeprowadzania zaawansowanej analizy (np. złożoności kodu) oraz śledzenia historii i postępu problemów z kodem. Są dwa przykłady narzędzi, których możesz użyć [Sonarqube](https://www.sonarqube.org/) (2,600+ [gwiazdek](https://github.com/SonarSource/sonarqube)) oraz [Code Climate](https://codeclimate.com/) (1,500+ [gwiazdek](https://github.com/codeclimate/codeclimate)) -Credit: [Keith Holliday](https://github.com/TheHollidayInn) +Źródło:
    [Keith Holliday](https://github.com/TheHollidayInn)
    -❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix +❌ **W przeciwnym razie:** Przy złej jakości kodu błędy i wydajność zawsze będą stanowić problem, którego nie będzie w stanie naprawić żadna nowa błyszcząca biblioteka ani najnowocześniejsze funkcje
    @@ -866,7 +867,7 @@ Credit: {
    -### :clap: Przykład robienia tego dobrze: We can stay within the test, each test acts on its own set of data +### :clap: Przykład robienia tego dobrze: Możemy pozostać w teście, każdy test działa na własny zestaw danych ```javascript it("When updating site name, get successful confirmation", async () => { @@ -954,13 +955,13 @@ it("When updating site name, get successful confirmation", async () => { # Sekcja 3️⃣: Frontend Testing -## ⚪ ️ 3.1 Separate UI from functionality +## ⚪ ️ 3.1 Oddziel interfejs użytkownika od funkcjonalności -:white_check_mark: **Opis:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI +:white_check_mark: **Opis:** Podczas koncentrowania się na testowaniu logiki komponentu szczegóły interfejsu użytkownika stają się szumem, który należy wyodrębnić, aby testy mogły koncentrować się na czystych danych. Praktycznie wyodrębnij pożądane dane ze znaczników w abstrakcyjny sposób, który nie jest zbyt sprzężony z implementacją graficzną, potwierdzaj tylko na czystych danych (vs szczegóły graficzne HTML / CSS) i wyłącz spowalniające animacje. Możesz ulec pokusie unikania renderowania i testowania tylko tylnej części interfejsu użytkownika (np. usług, akcji, store), ale spowoduje to testy fikcyjne, które nie przypominają rzeczywistości i nie ujawnią przypadków, w których właściwe dane nie są nawet przybyć do interfejsu użytkownika
    -❌ **W przeciwnym razie:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation +❌ **W przeciwnym razie:** Czysto obliczone dane z testu mogą być gotowe za 10 ms, ale wtedy cały test potrwa 500 ms (100 testów = 1 minuta) z powodu jakiejś wymyślnej i nieistotnej animacji
    @@ -968,7 +969,7 @@ it("When updating site name, get successful confirmation", async () => {
    -### :clap: Przykład robienia tego dobrze: Separating out the UI details +### :clap: Przykład robienia tego dobrze: Oddzielanie szczegółów interfejsu użytkownika ![](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") @@ -989,7 +990,7 @@ test("When users-list is flagged to show only VIP, should display only VIP membe
    -### :thumbsdown: Anti Pattern Example: Assertion mix UI details and data +### :thumbsdown: Przykład antywzorca: asercja miesza szczegóły interfejsu użytkownika i dane ```javascript test("When flagging to show only VIP, should display only VIP members", () => { @@ -1008,13 +1009,13 @@ test("When flagging to show only VIP, should display only VIP members", () => {

    -## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change +## ⚪ ️ 3.2 Zapytaj elementy HTML na podstawie atrybutów, których zmiana jest mało prawdopodobna -:white_check_mark: **Opis:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +:white_check_mark: **Opis:** Zapytaj elementy HTML na podstawie atrybutów, które prawdopodobnie przetrwają zmiany graficzne, w przeciwieństwie do selektorów CSS i podobnych etykiet formularzy. Jeśli wyznaczony element nie ma takich atrybutów, utwórz dedykowany atrybut testowy, taki jak 'test-id-submit-button'. Podążanie tą drogą nie tylko gwarantuje, że testy funkcjonalne / logiczne nigdy nie psują się z powodu zmian wyglądu i odczuć, ale także staje się jasne dla całego zespołu, że ten element i atrybut są wykorzystywane przez testy i nie należy ich usuwać
    -❌ **W przeciwnym razie:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **W przeciwnym razie:** Chcesz przetestować funkcjonalność logowania obejmującą wiele komponentów, logikę i usługi, wszystko jest skonfigurowane idealnie - stubs, spies, połączenia Ajax są izolowane. Wszystko wydaje się idealne. Następnie test kończy się niepowodzeniem, ponieważ projektant zmienił klasę CSS div 'thick-border' do 'thin-border'
    @@ -1022,7 +1023,7 @@ test("When flagging to show only VIP, should display only VIP members", () => {
    -### :clap: Przykład robienia tego dobrze: Querying an element using a dedicated attrbiute for testing +### :clap: Przykład robienia tego dobrze: Zapytanie o element przy użyciu dedykowanego atrybutu do testowania ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") @@ -1051,7 +1052,7 @@ test("Whenever no data is passed to metric, show 0 as default", () => {
    -### :thumbsdown: Przykład antywzorca: Relying on CSS attributes +### :thumbsdown: Przykład antywzorca: Poleganie na atrybutach CSS ```html @@ -1072,15 +1073,15 @@ test("Whenever no data is passed, error metric shows zero", () => {
    -## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component +## ⚪ ️ 3.3 O ile to możliwe, testuj z realistycznym i w pełni renderowanym komponentem -:white_check_mark: **Opis:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +:white_check_mark: **Opis:** Kiedy tylko rozsądny rozmiar, przetestuj komponent z zewnątrz, tak jak robią to użytkownicy, w pełni renderuj interfejs użytkownika, działaj na nim i upewnij się, że renderowany interfejs zachowuje się zgodnie z oczekiwaniami. Unikaj wszelkiego rodzaju mockowania, częściowego i płytkiego renderowania - takie podejście może skutkować niezakłóconymi błędami z powodu braku szczegółów i utrudniać konserwację, gdy testy brudzą się z elementów wewnętrznych (patrz punkt 'Preferuj testowanie czarnej skrzynki'). Jeśli jeden z elementów potomnych znacznie spowalnia (np. animacja) lub komplikuje konfigurację - zastanów się nad wyraźnym zastąpieniem go fake'm -With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children +Biorąc to wszystko pod uwagę, należy zachować ostrożność: ta technika działa w przypadku małych / średnich komponentów, które pakują rozsądne rozmiary komponentów potomnych. Pełne renderowanie komponentu ze zbyt dużą liczbą potomnych utrudni rozumowanie na temat błędów testów (analiza przyczyn) i może być zbyt wolne. W takich przypadkach napisz tylko kilka testów z tym głównym składnikiem macierzystym i więcej testów z jego potomnymi.
    -❌ **W przeciwnym razie:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? +❌ **W przeciwnym razie:** Podczas wbijania się w wewnętrzne komponenty przez wywoływanie ich prywatnych metod i sprawdzania stanu wewnętrznego - podczas refaktoryzacji implementacji komponentów musiałbyś przefakturować wszystkie testy. Czy naprawdę masz możliwości takiego poziomu konserwacji?
    @@ -1088,7 +1089,7 @@ With all that said, a word of caution is in order: this technique works for smal
    -### :clap: Przykład robienia tego dobrze: Working realstically with a fully rendered component +### :clap: Przykład robienia tego dobrze: Praca w trybie rzeczywistym z całkowicie renderowanym komponentem ![](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") @@ -1120,7 +1121,7 @@ test("Realistic approach: When clicked to show filters, filters are displayed", }); ``` -### :thumbsdown: Przykład antywzorca: Mocking the reality with shallow rendering +### :thumbsdown: Przykład antywzorca: Mockowanie rzeczywistości z płytkim renderowaniem ```javascript test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { @@ -1144,12 +1145,12 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up +## ⚪ ️ 3.4 Nie śpij, użyj wbudowanej obsługi frameworków dla zdarzeń asynchronicznych. Spróbuj także przyspieszyć -:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +:white_check_mark: **Opis:** W wielu przypadkach czas zakończenia testu jest po prostu nieznany (np. animacja wstrzymuje wygląd elementu) - w takim przypadku unikaj spania (np. SetTimeOut) i preferuj bardziej deterministyczne metody, które zapewnia większość platform. Niektóre biblioteki pozwalają na oczekiwanie na operacje (np. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), inne zapewniają API do czekania jak [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Czasami bardziej eleganckim sposobem jest zlikwidowanie wolnego zasobu, na przykład API, a następnie, gdy moment odpowiedzi staje się deterministyczny, komponent można jawnie ponownie renderować. Gdy zależy od jakiegoś zewnętrznego komponentu, który śpi, może się przydać [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Spanie to schemat, którego należy unikać, ponieważ wymusza powolny lub ryzykowny test (podczas oczekiwania na zbyt krótki okres). Ilekroć spanie i odpytywanie jest nieuniknione i nie ma wsparcia ze strony środowiska testowego, niektóre biblioteki npm jak [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) mogą pomóc w rozwiązaniu pół-deterministycznym
    -❌ **W przeciwnym razie:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance +❌ **W przeciwnym razie:** Podczas snu przez długi czas testy będą o rząd wielkości wolniejsze. Podczas próby spania dla małych liczb test nie powiedzie się, gdy testowana jednostka nie zareagowała w odpowiednim czasie. Sprowadza się to zatem do kompromisu między flakiness, a złą wydajnością
    @@ -1157,7 +1158,7 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -### :clap: Przykład robienia tego dobrze: E2E API that resolves only when the async operations is done (Cypress) +### :clap: Przykład robienia tego dobrze: E2E API rozwiązuje to dopiero po zakończeniu operacji asynchronicznych (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") @@ -1169,7 +1170,8 @@ cy.wait("@products"); // wait for route to appear // this line will get executed only when the route is ready ``` -### :clap: Przykład robienia tego dobrze: Testing library that waits for DOM elements +### :clap: Przykład robienia tego dobrze: Biblioteka testująca, która czeka na elementy DOM + ```javascript // @testing-library/dom @@ -1186,7 +1188,7 @@ test("movie title appears", async () => { }); ``` -### :thumbsdown: Przykład antywzorca: custom sleep code +### :thumbsdown: Przykład antywzorca: niestandardowy sleep code ```javascript test("movie title appears", async () => { @@ -1210,15 +1212,15 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.5 Watch how the content is served over the network +## ⚪ ️ 3.5 Zobacz, jak treść jest udostępniana przez sieć ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") -✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 +✅ **Opis:** Zastosuj aktywny monitor, który zapewnia optymalizację ładowania strony w rzeczywistej sieci - obejmuje to wszelkie problemy związane z UX, takie jak powolne ładowanie strony lub niezminimalizowany pakiet. Rynek narzędzi inspekcyjnych nie jest krótki: podstawowe narzędzia, takie jak [pingdom](https://www.pingdom.com/), AWS CloudWatch, [gcp StackDriver](https://cloud.google.com/monitoring/uptime-checks/) można łatwo skonfigurować, aby sprawdzał, czy serwer żyje i reagował na podstawie rozsądnej SLA. To tylko rysuje powierzchnię tego, co może się nie udać, dlatego lepiej wybrać narzędzia specjalizujące się we frontendzie (np. [lighthouse](https://developers.google.com/web/tools/lighthouse/), [pagespeed](https://developers.google.com/speed/pagespeed/insights/)) i wykonać bogatszą analizę. Należy skoncentrować się na objawach, wskaźnikach, które bezpośrednio wpływają na UX, takich jak czas ładowania strony, [meaningful paint](https://scotch.io/courses/10-web-performance-audit-tips-for-your-next-billion-users-in-2018/fmp-first-meaningful-paint), [czas, aż strona stanie się interaktywna (TTI)](https://calibreapp.com/blog/time-to-interactive/). Ponadto można również zwrócić uwagę na przyczyny techniczne, takie jak zapewnienie kompresji zawartości, czas do pierwszego bajtu, optymalizacja obrazów, zapewnienie rozsądnego rozmiaru DOM, SSL i wiele innych. Wskazane jest, aby mieć te bogate monitory zarówno podczas projektowania, jako część CI, a co najważniejsze - 24x7 przez serwery produkcji / CDN
    -❌ **W przeciwnym razie:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration +❌ **W przeciwnym razie:** Musi być rozczarowujące, gdy zda się sobie sprawę, że po tak wielkiej dbałości o interfejs użytkownika, 100% testy funkcjonalne zdały i wyrafinowane pakowanie - UX jest straszny i powolny z powodu błędnej konfiguracji CDN
    @@ -1232,7 +1234,7 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs +## ⚪ ️ 3.6 Stub niestabilne i wolne zasoby, takie jak interfejsy API zaplecza :white_check_mark: **Opis:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests From 78541e499a717f65ffb7bc7384081e92237deae6 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 7 Apr 2020 14:26:36 +0200 Subject: [PATCH 197/502] update readme-pl updated until 5.4 --- readme-pl.md | 134 ++++++++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/readme-pl.md b/readme-pl.md index c7c5e143..62557fd0 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -1234,13 +1234,14 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.6 Stub niestabilne i wolne zasoby, takie jak interfejsy API zaplecza +## ⚪ ️ 3.6 Stub dla niestabilnych i wolnych zasobów, takich jak interfejsy zaplecza API -:white_check_mark: **Opis:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +:white_check_mark: **Opis:** Kodując swoje główne testy (nie testy E2E), unikaj angażowania zasobów, które są poza twoją odpowiedzialnością i kontroluj, takie jak backend API i zamiast tego używaj stubs (np. test double). Praktycznie, zamiast prawdziwych wywołań sieciowych interfejsów API, użyj biblioteki test double (np [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) dla stubbingu odpowiedzi API. Główną zaletą jest zapobieganie niestabilności - testowanie lub przemieszczanie interfejsów API z definicji nie jest wysoce stabilne i od czasu do czasu zawiedzie testy, chociaż TWÓJ komponent zachowuje się dobrze (środowisko env nie było przeznaczone do testowania i zwykle ogranicza żądania). Pozwoli to na symulację różnych zachowań API, które powinny kierować zachowaniem twojego komponentu, tak jak w przypadku braku danych lub w przypadku, gdy API zgłasza błąd. Wreszcie połączenia sieciowe znacznie spowolnią testy
    -❌ **W przeciwnym razie:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower +❌ **W przeciwnym razie:** Średni test trwa nie dłużej niż kilka ms, typowe wywołanie API trwa 100 ms>, co powoduje, że każdy test ~20x wolniej +
    @@ -1248,7 +1249,7 @@ test("movie title appears", async () => {
    -### :clap: Przykład robienia tego dobrze: Stubbing or intercepting API calls +### :clap: Przykład robienia tego dobrze: Stubbing lub przechwytywanie wywołań 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") @@ -1288,23 +1289,23 @@ test("When no products exist, show the appropriate message", () => {
    -## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system +## ⚪ ️ 3.7 Mieć bardzo mało kompleksowych testów obejmujących cały system -:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +:white_check_mark: **Opis:** Chociaż E2E (end-to-end) zwykle oznacza testowanie tylko interfejsu użytkownika z prawdziwą przeglądarką (patrz punkt 3.6), dla innych oznacza testy rozciągające cały system, w tym prawdziwy backend. Ten ostatni rodzaj testów jest bardzo cenny, ponieważ obejmuje błędy integracyjne między frontendem a backendem, które mogą się zdarzyć z powodu niewłaściwego zrozumienia schematu wymiany. Są również skuteczną metodą wykrywania problemów z integracją backend-to-backend (np. Microservice A wysyła niewłaściwy komunikat do Microservice B), a nawet wykrywania błędów wdrażania - nie ma struktur zaplecza dla testów E2E, które byłyby tak przyjazne i dojrzałe jak frameworki UI takie jak [Cypress](https://www.cypress.io/) oraz [Pupeteer](https://github.com/GoogleChrome/puppeteer). Minusem takich testów jest wysoki koszt konfiguracji środowiska z tyloma komponentami, a przede wszystkim ich kruchość - biorąc pod uwagę 50 mikrousług, nawet jeśli jeden się nie powiedzie, cały E2E po prostu zawiódł. Z tego powodu powinniśmy stosować tę technikę oszczędnie i prawdopodobnie mieć 1-10 z nich i nie więcej. To powiedziawszy, nawet niewielka liczba testów E2E może wychwycić rodzaj problemów, do których są skierowane - błędy wdrażania i integracji. Wskazane jest, aby uruchamiać je w środowisku produkcyjnym podobnym do produkcyjnego.
    -❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected +❌ **W przeciwnym razie:** Interfejs użytkownika może dużo zainwestować w testowanie jego funkcjonalności, aby zdać sobie sprawę bardzo późno, że backend zwrócił payload (schemat danych, z którym musi pracować interfejs użytkownika) jest bardzo różny od oczekiwanego
    -## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials +## ⚪ ️ 3.8 Przyspiesz testy E2E poprzez ponowne użycie danych logowania -:white_check_mark: **Opis:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). +:white_check_mark: **Opis:** W testach E2E, które obejmują prawdziwy backend i opierają się na prawidłowym tokenie użytkownika dla wywołań API, izolacja testu do poziomu, na którym użytkownik jest tworzony i logowany w każdym żądaniu, nie opłaca się. Zamiast tego zaloguj się tylko raz, zanim rozpocznie się wykonywanie testu (np. before-all hook), zapisz token w lokalnej pamięci i użyj go ponownie w żądaniach. Wydaje się to naruszać jedną z podstawowych zasad testowania - zachowaj autonomię testu bez łączenia zasobów. Chociaż jest to uzasadnione zmartwienie, w testach E2E kluczowe znaczenie ma wydajność, a utworzenie 1-3 zapytań API przed rozpoczęciem każdego indywidualnego testu może prowadzić do okropnego czasu wykonania. Ponowne użycie poświadczeń nie oznacza, że testy muszą działać na tych samych rekordach użytkownika - jeśli polegasz na rekordach użytkownika (np. historii płatności użytkownika testowego), to upewnij się, że wygenerujesz te rekordy w ramach testu i unikniesz dzielenia się ich istnieniem z innymi testami. Pamiętaj również, że backend może być sfałszowany - jeśli twoje testy koncentrują się na frontendzie, może lepiej być go wyodrębnić i zablokować API backendu (patrz punkt 3.6).
    -❌ **W przeciwnym razie:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again +❌ **W przeciwnym razie:** Biorąc pod uwagę 200 przypadków testowych i zakładając login = 100ms = 20 sekund tylko samego ponownego logowania
    @@ -1312,7 +1313,7 @@ test("When no products exist, show the appropriate message", () => {
    -### :clap: Przykład robienia tego dobrze: Logging-in before-all and not before-each +### :clap: Przykład robienia tego dobrze: Logging-in before-all i nie before-each ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") @@ -1346,13 +1347,14 @@ beforeEach(setUser => () {
    -## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map +## ⚪ ️ 3.9 Zrób jeden smoke test E2E, który podróżuje po mapie witryny + -:white_check_mark: **Opis:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector +:white_check_mark: **Opis:** W celu monitorowania produkcji i kontroli poprawności w czasie programowania uruchom pojedynczy test E2E, który odwiedzi wszystkie / większość stron witryny i zapewni, że nic się nie zepsuje. Ten rodzaj testu zapewnia duży zwrot z inwestycji, ponieważ jest bardzo łatwy do napisania i utrzymania, ale może wykryć wszelkiego rodzaju awarie, w tym problemy z funkcjonowaniem, siecią i wdrażaniem. Inne style sprawdzania smoke i sanity nie są tak niezawodne i wyczerpujące - niektóre zespoły ops po prostu pingują stronę główną (produkcję) lub programistów, którzy przeprowadzają wiele testów integracyjnych, które nie wykrywają problemów z pakowaniem i przeglądarką. Oczywiste jest, że smoke test nie zastępuje testów funkcjonalnych, a jedynie służy jako quick smoke detector
    -❌ **W przeciwnym razie:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering +❌ **W przeciwnym razie:** Wszystko może wydawać się idealne, wszystkie testy przeszły pomyślnie, kontrola kondycji produkcji również jest pozytywna, ale komponent płatności miał problem z pakowaniem i tylko trasa płatności się nie wyświetla
    @@ -1360,7 +1362,7 @@ beforeEach(setUser => () {
    -### :clap: Przykład robienia tego dobrze: Smoke travelling across all pages +### :clap: Przykład robienia tego dobrze: Smoke podróżujący po wszystkich stronach ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") @@ -1381,11 +1383,11 @@ it("When doing smoke testing over all page, should load them all successfully",
    -## ⚪ ️ 3.10 Expose the tests as a live collaborative document +## ⚪ ️ 3.10 Ujawnij testy jako dokument współpracy na żywo -:white_check_mark: **Opis:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. +:white_check_mark: **Opis:** Oprócz zwiększenia niezawodności aplikacji testy przynoszą kolejną atrakcyjną okazję na stół - służą jako dokumentacja aplikacji na żywo. Ponieważ testy z natury mówią w mniej technicznym i produktowym języku UX, przy użyciu odpowiednich narzędzi mogą służyć jako artefakt komunikacyjny, który w znacznym stopniu dopasowuje wszystkich współpracowników - programistów i ich klientów. Na przykład niektóre platformy umożliwiają wyrażanie przepływu i oczekiwań (np. plan testów) przy użyciu języka czytelnego dla człowieka, aby każdy interesariusz, w tym PM, mógł czytać, zatwierdzać i współpracować przy testach, które właśnie stały się dokumentem wymagań na żywo. Technikę tę określa się również mianem „testu akceptacji”, ponieważ pozwala klientowi zdefiniować kryteria akceptacji w prostym języku. To jest [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) w swojej najczystszej formie. Jednym z popularnych frameworków które to umożliwiają jest [Cucumber który ma aromat JavaScript](https://github.com/cucumber/cucumber-js), zobacz przykład poniżej. Kolejna podobna, ale inna możliwość, [StoryBook](https://storybook.js.org/), umożliwia eksponowanie komponentów interfejsu użytkownika jako katalogu graficznego, w którym można przechodzić przez różne stany każdego komponentu (np. renderować siatkę bez filtrów, renderować tę siatkę z wieloma wierszami lub bez, itp.), zobaczyć, jak to wygląda i jak aby wywołać ten stan - może to spodobać się także ludziom od produktu, ale służy głównie jako dokumentacja na żywo dla programistów, którzy używają tych składników. -❌ **W przeciwnym razie:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value +❌ **W przeciwnym razie:** Po zainwestowaniu najlepszych zasobów w testowanie, szkoda tylko nie wykorzystać tej inwestycji i zyskać świetną wartość
    @@ -1393,7 +1395,7 @@ it("When doing smoke testing over all page, should load them all successfully",
    -### :clap: Przykład robienia tego dobrze: Describing tests in human-language using cucumber-js +### :clap: Przykład robienia tego dobrze: Opisywanie testów w języku zrozumiałym dla człowieka używając cucumber-js ![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") @@ -1414,7 +1416,7 @@ Feature: Twitter new tweet ``` -### :clap: Przykład robienia tego dobrze: Visualizing our components, their various states and inputs using Storybook +### :clap: Przykład robienia tego dobrze: Wizualizacja naszych komponentów, ich różnych stanów i danych wejściowych używając Storybook ![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") @@ -1424,13 +1426,13 @@ Feature: Twitter new tweet

    -## ⚪ ️ 3.11 Detect visual issues with automated tools +## ⚪ ️ 3.11 Wykrywanie problemów wizualnych za pomocą zautomatyzowanych narzędzi -:white_check_mark: **Opis:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue +:white_check_mark: **Opis:** Skonfiguruj zautomatyzowane narzędzia do przechwytywania zrzutów ekranu interfejsu użytkownika podczas prezentacji zmian i wykrywania problemów wizualnych, takich jak nakładanie się lub łamanie zawartości. Zapewnia to, że nie tylko odpowiednie dane są przygotowane, ale także użytkownik może je wygodnie zobaczyć. Ta technika nie jest powszechnie stosowana, nasze podejście do testowania skłania się ku testom funkcjonalnym, ale jest to wizualne doświadczenie użytkownika i przy tak wielu typach urządzeń bardzo łatwo jest przeoczyć jakiś paskudny błąd interfejsu użytkownika. Niektóre bezpłatne narzędzia mogą dostarczyć podstaw - generować i zapisywać zrzuty ekranu do kontroli ludzkich oczu. Chociaż takie podejście może być wystarczające w przypadku małych aplikacji, jest wadliwe, jak każde inne ręczne testowanie, które wymaga ludzkiej pracy za każdym razem, gdy coś się zmienia. Z drugiej strony automatyczne wykrywanie problemów z interfejsem jest dość trudne ze względu na brak jasnej definicji - w tym miejscu pojawia się pole „regresji wizualnej” i rozwiązuje tę zagadkę, porównując stary interfejs z najnowszymi zmianami i wykrywając różnice. Niektóre narzędzia OSS / darmowe mogą zapewniać niektóre z tych funkcji (np. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) ale mogą wymagać znacznego czasu instalacji. Linia narzędzi komercyjnych (np. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) idzie o krok dalej, usprawniając instalację i pakując zaawansowane funkcje, takie jak interfejs zarządzania, alarmowanie, inteligentne przechwytywanie poprzez eliminację szumów wizualnych (np. reklamy, animacje), a nawet analizę pierwotnych przyczyn zmian DOM/CSS, które doprowadziły do problemu
    -❌ **W przeciwnym razie:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? +❌ **W przeciwnym razie:** Jak dobra jest strona z treściami, która wyświetla świetną treść (100% testów), ładuje się natychmiast, ale połowa obszaru zawartości jest ukryta?
    @@ -1438,13 +1440,13 @@ Feature: Twitter new tweet
    -### :thumbsdown: Anti Pattern Example: A typical visual regression - right content that is served badly +### :thumbsdown: Przykład antywzorca: Typowa regresja wizualna - właściwa treść, która jest źle wyświetlana ![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks")
    -### :clap: Przykład robienia tego dobrze: Configuring wraith to capture and compare UI snapshots +### :clap: Przykład robienia tego dobrze: Konfigurowanie Wraith do przechwytywania i porównywania migawek interfejsu użytkownika ![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") @@ -1473,7 +1475,7 @@ paths: path: /subscribe ``` -### :clap: Przykład robienia tego dobrze: Using Applitools to get snapshot comaprison and other advanced features +### :clap: Przykład robienia tego dobrze: Używanie Applitools aby uzyskać porównanie migawek i inne zaawansowane funkcje ![](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") @@ -1500,19 +1502,19 @@ describe("visual validation", () => {

    -# Sekcja 4️⃣: Measuring Test Effectiveness +# Sekcja 4️⃣: Pomiar skuteczności testu

    -## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number +## ⚪ ️ 4.1 Zdobądź wystarczające pokrycie, aby mieć pewność siebie, ~80% wydaje się być szczęśliwą liczbą -:white_check_mark: **Opis:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. +:white_check_mark: **Opis:** Celem testowania jest uzyskanie pewności siebie do szybkiego poruszania się, oczywiście im więcej kodu jest testowane, tym większa pewność zespołu. Pokrycie jest miarą tego, ile wierszy kodu (i gałęzi, instrukcji itp.) jest osiąganych przez testy. Ile wystarczy? 10–30% jest oczywiście zbyt niskie, aby mieć jakiekolwiek pojęcie o poprawności kompilacji, z drugiej strony 100% jest bardzo drogie i może przesunąć uwagę z krytycznych ścieżek na egzotyczne zakątki kodu. Długa odpowiedź jest taka, że zależy to od wielu czynników, takich jak rodzaj aplikacji - jeśli budujesz następną generację Airbusa A380, to 100% jest koniecznością, dla witryny ze zdjęciami z kreskówkami 50% może być za dużo. Chociaż większość entuzjastów testowania twierdzi, że odpowiedni próg pokrycia jest kontekstowy, większość z nich wspomina również o liczbie 80% ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) które prawdopodobnie powinny spełniać większość aplikacji. -Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets +Wskazówki dotyczące implementacji: możesz skonfigurować ciągłą integrację (CI) tak, aby mieć próg pokrycia ([link Jest](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) i zatrzymać kompilację, która nie jest zgodna z tym standardem (można również skonfigurować próg na komponent, patrz przykład kodu poniżej). Ponadto rozważ wykrycie zmniejszenia pokrycia kompilacji (gdy nowo zatwierdzony kod ma mniejsze pokrycie) - spowoduje to, że programiści zwiększą lub przynajmniej zachowają ilość testowanego kodu. To powiedziawszy, pokrycie jest tylko jedną miarą, opartą na danych ilościowych, która nie wystarczy, aby powiedzieć o solidności twoich testów. Można go również oszukać, jak pokazano w następnych punktach.
    -❌ **W przeciwnym razie:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down +❌ **W przeciwnym razie:** Pewność siebie i liczby idą ze sobą w parze, tak naprawdę nie wiedząc, że przetestowałeś większość systemu - będzie też trochę strachu, a strach cię spowolni
    @@ -1520,13 +1522,13 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :clap: Example: A typical coverage report +### :clap: Przykład: typowy raport pokrycia ![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report")
    -### :clap: Przykład robienia tego dobrze: Setting up coverage per component (using Jest) +### :clap: Przykład robienia tego dobrze: Konfigurowanie pokrycia dla każdego komponentu (za pomocą Jest) ![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") @@ -1536,12 +1538,12 @@ Implementation tips: You may want to configure your continuous integration (CI)

    -## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities +## ⚪ ️ 4.2 Sprawdź raporty pokrycia, aby wykryć nietestowane obszary i inne osobliwości -:white_check_mark: **Opis:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +:white_check_mark: **Opis:** Niektóre problemy wymykają się tuż pod radarem i naprawdę trudno je znaleźć przy użyciu tradycyjnych narzędzi. To nie są tak naprawdę błędy, ale bardziej zaskakujące zachowanie aplikacji, które może mieć poważny wpływ. Na przykład często niektóre obszary kodu nie są wywoływane lub rzadko są wywoływane - myślałeś, że klasa 'PriceCalculator' zawsze ustala cenę produktu, ale okazuje się, że tak naprawdę nigdy nie jest wywoływana, chociaż mamy 10000 produktów w BD i wiele sprzedaży… Raporty pokrycia kodu pomagają zrozumieć, czy aplikacja zachowuje się tak, jak myślisz. Poza tym może także podkreślić, które typy kodu nie są testowane - informacja, że 80% kodu jest testowane, nie mówi, czy kluczowe części są objęte testem. Generowanie raportów jest łatwe - po prostu uruchom aplikację na produkcji lub podczas testowania ze śledzeniem pokrycia, a następnie wyświetl kolorowe raporty, które podkreślają częstotliwość wywoływania każdego obszaru kodu. Jeśli nie spieszysz się, aby zajrzeć do tych danych - możesz znaleźć nieco
    -❌ **W przeciwnym razie:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from +❌ **W przeciwnym razie:** Jeśli nie wiesz, które części kodu nie zostały przetestowane, nie wiesz, skąd mogą wynikać problemy.
    @@ -1549,9 +1551,9 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :thumbsdown: Przykład antywzorca: What’s wrong with this coverage report? +### :thumbsdown: Przykład antywzorca: Co jest nie tak z tym raportem pokrycia? -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) +W oparciu o scenariusz z rzeczywistego świata, w którym śledziliśmy użycie naszej aplikacji w ramach kontroli jakości i znajdowaliśmy ciekawe wzorce logowania (wskazówka: liczba niepowodzeń logowania jest nieproporcjonalna, coś jest wyraźnie nie tak. W końcu okazało się, że jakiś błąd interfejsu użytkownika ciągle uderza w backend login API) ![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report?") @@ -1559,20 +1561,20 @@ Based on a real-world scenario where we tracked our application usage in QA and

    -## ⚪ ️ 4.3 Measure logical coverage using mutation testing +## ⚪ ️ 4.3 Zmierz pokrycie logiczne za pomocą testu mutacji -:white_check_mark: **Opis:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +:white_check_mark: **Opis:** Metryka tradycyjnego pokrycia często zakłamuje, tzn. może pokazywać 100% pokrycia kodu, ale żadna z twoich funkcji, nawet jedna, nie zwraca właściwej odpowiedzi. Dlaczego? Po prostu mierzy, które wiersze kodu odwiedził test, ale nie sprawdza, czy testy faktycznie coś testowały - stwierdzając, że ma właściwą odpowiedź. Jak ktoś, kto podróżuje w interesach i pokazuje swoje znaczki paszportowe - nie świadczy to o żadnej pracy, tylko że odwiedził kilka lotnisk i hoteli. -Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: +Testy oparte na mutacjach są tutaj pomocne, mierząc ilość kodu, który był TESTOWANY, a nie tylko ODWIEDZANY. [Stryker](https://stryker-mutator.io/) jest biblioteką JavaScript do testowania mutacji, a implementacja jest naprawdę fajna: -(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations +(1) celowo zmienia kod i "zasadza błędy". Na przykład kod newOrder.price===0 staje się newOrder.price!=0. Te "bugi" nazywane są mutacjami -(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. +(2) uruchamia testy, jeśli wszystko się powiedzie, wówczas mamy problem - testy nie służyły wykrywaniu błędów, mutacje są tzw. survived. Jeśli testy się nie powiodły, to świetnie, mutacje zostały zabite. -Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +Wiedza, że wszystkie lub większość mutacji została zabita, daje znacznie większą pewność niż tradycyjne pokrycie, a czas przygotowania jest podobny
    -❌ **W przeciwnym razie:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code +❌ **W przeciwnym razie:** Będziesz oszukiwany, że 85% pokrycia oznacza, że Twój test wykryje błędy w 85% twojego kodu
    @@ -1580,7 +1582,7 @@ Knowing that all or most of the mutations were killed gives much higher confiden
    -### :thumbsdown: Anti Pattern Example: 100% coverage, 0% testing +### :thumbsdown: Przykład antywzorca: 100% pokrycia, 0% testowania ![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") @@ -1600,7 +1602,7 @@ it("Test addNewOrder, don't use such test names", () => {
    -### :clap: Przykład robienia tego dobrze: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :clap: Przykład robienia tego dobrze: Stryker raportuje, narzędzie do testowania mutacji, wykrywa i zlicza ilość kodu, który nie jest testowany (mutacje) ![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)") @@ -1608,20 +1610,20 @@ it("Test addNewOrder, don't use such test names", () => {

    -## ⚪ ️4.4 Preventing test code issues with Test linters +## ⚪ ️4.4 Zapobieganie problemom z kodem testowym z Test linters -:white_check_mark: **Opis:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +:white_check_mark: **Opis:** Zestaw wtyczek ESLint został zbudowany specjalnie do sprawdzania wzorców kodów testów i wykrywania problemów. Na przykład, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) ostrzeże, gdy test zostanie napisany na poziomie globalnym (nie pochodna deklaracji describe()) lub gdy testy są [pomijane](https://mochajs.org/#inclusive-tests) co może prowadzić do fałszywego przekonania, że wszystkie testy przebiegają pomyślnie. Podobnie, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) może na przykład ostrzegać, gdy test nie ma żadnych asercji (niczego nie sprawdza)
    -❌ **W przeciwnym razie:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation +❌ **W przeciwnym razie:** Widok 90% pokrycia kodu i 100% zielonych testów sprawi, że twoja twarz będzie się uśmiechać tylko do momentu, gdy zdasz sobie sprawę, że wiele testów niczego nie potwierdza, a wiele pakietów testowych zostało właśnie pominiętych. Mamy nadzieję, że nie wdrożyłeś niczego w oparciu o tę fałszywą obserwację
    Przykłady kodu
    -### :thumbsdown: Anti Pattern Example: A test case full of errors, luckily all are caught by Linters +### :thumbsdown: Przykład antywzorca: Przypadek testowy pełen błędów, na szczęście wszystkie zostają złapane przez Linters ```javascript describe("Too short description", () => { @@ -1641,16 +1643,16 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# Sekcja 5️⃣: CI and Other Quality Measures +# Sekcja 5️⃣: CI oraz inne miary jakości

    -## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues +## ⚪ ️ 5.1 Wzbogać swoje linters i przerwij buildy, które mają problemy z linters -:white_check_mark: **Opis:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +:white_check_mark: **Opis:** Linters to bezpłatny lunch, z 5-minutową konfiguracją otrzymujesz za darmo auto-pilota pilnującego twojego kodu i wychwytującego poważny problem podczas pisania. Dawno minęły czasy, w których linting było związane z kosmetycznymi poprawkami (brak średników!). Obecnie Linters mogą wykrywać poważne problemy, takie jak błędy, które nie są poprawnie zgłaszane i gubią informacje. Oprócz podstawowego zestawu zasad (jak [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) lub [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), rozważ włączenie pewnych specjalizacji Linters jak [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect), które mogą wykryć testy bez asercji, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) może odkryć obietnice (promises) bez rozwiązania (twój kod nigdy nie będzie kontynuowany), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme), które mogą odkryć chciwe wyrażenia regularne, które mogą zostać wykorzystane do ataków DOS, oraz [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) może alarmować, gdy kod korzysta z metod utility biblioteki, które są częścią podstawowych metod V8, takich jak Lodash.\_map(…)
    -❌ **W przeciwnym razie:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day +❌ **W przeciwnym razie:** Weź pod uwagę deszczowy dzień, w którym Twoja produkcja ulega awarii, ale dzienniki nie wyświetlają śladu stosu błędów. Co się stało? Twój kod przez pomyłkę rzucił obiekt niebędący błędem, a ślad stosu został utracony, co jest dobrym powodem uderzenia głową o ścianę z cegły. 5-minutowa konfiguracja linijek może wykryć tę LITERÓWKĘ i uratować Twój dzień
    @@ -1658,7 +1660,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    -### :thumbsdown: Anti Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug +### :thumbsdown: Przykład antywzorca: Błędnie rzucony niewłaściwy obiekt Error, dla tego błędu nie pojawi się śledzenie stosu. Na szczęście ESLint łapie kolejny błąd produkcyjny ![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") @@ -1666,14 +1668,14 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -## ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI +## ⚪ ️ 5.2 Skróć pętlę sprzężenia zwrotnego z lokalnym developer-CI -:white_check_mark: **Opis:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +:white_check_mark: **Opis:** Używasz CI z błyszczącymi inspekcjami jakości, takimi jak testowanie, linting, sprawdzanie podatności itp.? Pomóż programistom uruchomić ten pipeline również lokalnie, aby uzyskać natychmiastowe informacje zwrotne i skrócić [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Czemu? Wydajny proces testowania składa się z wielu iteracyjnych pętli: (1) próby -> (2) informacje zwrotne -> (3) refaktor. Im szybsze jest sprzężenie zwrotne, tym więcej iteracji ulepszeń może wykonać programista na moduł i uzyskać doskonałe wyniki. Z drugiej strony, gdy informacje zwrotne są spóźnione, mniej ulepszeń można by spakować w jeden dzień, zespół może już przejść do innego tematu / zadania / modułu i może nie być gotowy na udoskonalenie tego modułu. -Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +Praktycznie, niektórzy dostawcy CI (przykład: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) zezwalają na lokalne uruchomienie pipeline'a. Niektóre komercyjne narzędzia jak [wallaby zapewniają cenne i spostrzeżenia testowania](https://wallabyjs.com/) jako developer prototype (bez przynależności). Alternatywnie, możesz po prostu dodać skrypt npm do package.json który uruchamia wszystkie polecenia jakości (np. test, lint, vulnerabilities) — użyj narzędzi jak [concurrently](https://www.npmjs.com/package/concurrently) do równoległości i niezerowego kodu wyjścia, jeśli jedno z narzędzi uległo awarii. Teraz programista powinien po prostu wywołać jedno polecenie - np. ‘npm run quality’ — aby uzyskać natychmiastową informację zwrotną. Rozważ także przerwanie commita, jeśli sprawdzenie jakości nie powiodło się przy użyciu githook ([husky może pomóc](https://github.com/typicode/husky))
    -❌ **W przeciwnym razie:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact +❌ **W przeciwnym razie:** Gdy wyniki jakości pojawiają się następnego dnia po kodzie, testowanie nie staje się płynną częścią rozwoju, a raczej formalnym artefaktem
    @@ -1681,7 +1683,7 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com
    -### :clap: Przykład robienia tego dobrze: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: Przykład robienia tego dobrze: skrypty npm, które przeprowadzają kontrolę jakości kodu, wszystkie są uruchamiane równolegle na żądanie lub gdy programista próbuje wypchnąć nowy kod ```javascript "scripts": { @@ -1706,14 +1708,14 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com

    -## ⚪ ️5.3 Perform e2e testing over a true production-mirror +## ⚪ ️5.3 Przeprowadź testy e2e na prawdziwym production-mirror -:white_check_mark: **Opis:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. +:white_check_mark: **Opis:** Testowanie end to end (e2e) są głównym wyzwaniem każdego CI pipeline — tworzenie w locie identycznego efemerycznego lustra produkcyjnego ze wszystkimi powiązanymi usługami chmurowymi może być uciążliwe i kosztowne. Znalezienie najlepszego kompromisu to Twoja gra: [Docker-compose](https://serverless.com/) umożliwia tworzenie izolowanego, zadokowanego środowiska z identycznymi kontenerami przy użyciu jednego zwykłego pliku tekstowego, ale technologia tworzenia kopii zapasowych (np. sieci, model wdrażania) różni się od rzeczywistych produkcji. Możesz to połączyć z [‘AWS Local’](https://github.com/localstack/localstack) do pracy ze stub prawdziwych usług AWS. Jeśli poszedłeś drogą [serverless](https://serverless.com/) wiele frameworków jak serverless i [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) umożliwia lokalne wywołanie kodu Faas. -The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +Ogromny ekosystem Kubernetes ma jeszcze sformalizować standardowe wygodne narzędzie do lokalnego i CI-mirroring, choć wiele nowych narzędzi jest często uruchamianych. Jednym z podejść jest uruchomienie ‘minimized-Kubernetes’ używając narzędzi jak [Minikube](https://kubernetes.io/docs/setup/minikube/) i [MicroK8s](https://microk8s.io/) które przypominają prawdziwe, pochodzą tylko z mniejszym nakładem. Innym podejściem jest testowanie za pomocą zdalnego ‘real-Kubernetes’, niektórzy CI dostawcy (np. [Codefresh](https://codefresh.io/)) mają natywną integrację ze środowiskiem Kubernetes i ułatwiają uruchamianie pipeline CI w rzeczywistości, inne pozwalają na niestandardowe skrypty na zdalnym Kubernetes.
    -❌ **W przeciwnym razie:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated +❌ **W przeciwnym razie:** Korzystanie z różnych technologii do produkcji i testowania wymaga utrzymania dwóch modeli wdrażania oraz oddzielenia programistów i zespołu Ops.
    @@ -1721,7 +1723,7 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    -### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly
    ([Credit: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) +### :clap: Przykład: pipeline CI który generuje klaster Kubernetes w locie ([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
    From 5c5c7b1163dbcb7e4d364a186982d617b77eb2d2 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 7 Apr 2020 18:29:56 +0200 Subject: [PATCH 198/502] update readme-pl updated --- readme-pl.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/readme-pl.md b/readme-pl.md index 62557fd0..43484bd7 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -1672,7 +1672,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test :white_check_mark: **Opis:** Używasz CI z błyszczącymi inspekcjami jakości, takimi jak testowanie, linting, sprawdzanie podatności itp.? Pomóż programistom uruchomić ten pipeline również lokalnie, aby uzyskać natychmiastowe informacje zwrotne i skrócić [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Czemu? Wydajny proces testowania składa się z wielu iteracyjnych pętli: (1) próby -> (2) informacje zwrotne -> (3) refaktor. Im szybsze jest sprzężenie zwrotne, tym więcej iteracji ulepszeń może wykonać programista na moduł i uzyskać doskonałe wyniki. Z drugiej strony, gdy informacje zwrotne są spóźnione, mniej ulepszeń można by spakować w jeden dzień, zespół może już przejść do innego tematu / zadania / modułu i może nie być gotowy na udoskonalenie tego modułu. -Praktycznie, niektórzy dostawcy CI (przykład: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) zezwalają na lokalne uruchomienie pipeline'a. Niektóre komercyjne narzędzia jak [wallaby zapewniają cenne i spostrzeżenia testowania](https://wallabyjs.com/) jako developer prototype (bez przynależności). Alternatywnie, możesz po prostu dodać skrypt npm do package.json który uruchamia wszystkie polecenia jakości (np. test, lint, vulnerabilities) — użyj narzędzi jak [concurrently](https://www.npmjs.com/package/concurrently) do równoległości i niezerowego kodu wyjścia, jeśli jedno z narzędzi uległo awarii. Teraz programista powinien po prostu wywołać jedno polecenie - np. ‘npm run quality’ — aby uzyskać natychmiastową informację zwrotną. Rozważ także przerwanie commita, jeśli sprawdzenie jakości nie powiodło się przy użyciu githook ([husky może pomóc](https://github.com/typicode/husky)) +W praktyce, niektórzy dostawcy CI (przykład: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) zezwalają na lokalne uruchomienie pipeline'a. Niektóre komercyjne narzędzia jak [wallaby zapewniają cenne i spostrzeżenia testowania](https://wallabyjs.com/) jako developer prototype (bez przynależności). Alternatywnie, możesz po prostu dodać skrypt npm do package.json który uruchamia wszystkie polecenia jakości (np. test, lint, vulnerabilities) — użyj narzędzi jak [concurrently](https://www.npmjs.com/package/concurrently) do równoległości i niezerowego kodu wyjścia, jeśli jedno z narzędzi uległo awarii. Teraz programista powinien po prostu wywołać jedno polecenie - np. ‘npm run quality’ — aby uzyskać natychmiastową informację zwrotną. Rozważ także przerwanie commita, jeśli sprawdzenie jakości nie powiodło się przy użyciu githook ([husky może pomóc](https://github.com/typicode/husky))
    ❌ **W przeciwnym razie:** Gdy wyniki jakości pojawiają się następnego dnia po kodzie, testowanie nie staje się płynną częścią rozwoju, a raczej formalnym artefaktem @@ -1731,11 +1731,11 @@ Ogromny ekosystem Kubernetes ma jeszcze sformalizować standardowe wygodne narz

    -## ⚪ ️5.4 Parallelize test execution +## ⚪ ️5.4 Równoległe wykonywanie testu -:white_check_mark: **Opis:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes +:white_check_mark: **Opis:** Po prawidłowym przeprowadzeniu, testowanie to twój przyjaciel 24/7, który zapewnia niemal natychmiastową informację zwrotną. W praktyce wykonanie 500 testów jednostkowych powiązanych z CPU na jednym wątku może potrwać zbyt długo. Na szczęście nowoczesne test runners i platformy CI (takie jak [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) oraz [rozszerzenia Mocha](https://github.com/yandex/mocha-parallel-tests)) potrafią równolegle przeprowadzić test w wiele procesów i osiągnąć znaczną poprawę czasu reakcji. Niektórzy dostawcy CI również przeprowadzają testy równoległe w kontenerach (!), co jeszcze bardziej skraca pętlę sprzężenia zwrotnego. Niezależnie od tego, czy lokalnie w wielu procesach, czy w niektórych interfejsach CLI w chmurze na wielu komputerach - równolegle do popytu, zachowując autonomię testów, ponieważ każdy może działać na różnych procesach -❌ **W przeciwnym razie:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant +❌ **W przeciwnym razie:** Uzyskanie wyników testu w 1 godzinę po opublikowaniu nowego kodu, gdy już kodujesz kolejne funkcje, to świetny przepis na uczynienie testowania mniej istotnym
    @@ -1743,7 +1743,7 @@ Ogromny ekosystem Kubernetes ma jeszcze sformalizować standardowe wygodne narz
    -### :clap: Przykład robienia tego dobrze: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :clap: Przykład robienia tego dobrze: Mocha parallel & Jest łatwo prześcigną tradycyjne Mocha dzięki testowaniu równoległości ([Źródło: 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)") @@ -1751,11 +1751,11 @@ Ogromny ekosystem Kubernetes ma jeszcze sformalizować standardowe wygodne narz

    -## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check +## ⚪ ️5.5 Unikaj problemów prawnych dzięki sprawdzeniu licencji i unikaniu plagiatu -:white_check_mark: **Opis:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights +:white_check_mark: **Opis:** Problemy z licencjonowaniem i plagiatem nie są teraz prawdopodobnie Twoim głównym problemem, ale dlaczego nie zaznaczyć tego pola również za 10 minut? Kilka pakietów npm jak [kontrola licencji](https://www.npmjs.com/package/license-checker) i [kontrola plagiatu](https://www.npmjs.com/package/plagiarism-checker) (komercyjny z bezpłatnym planem) można łatwo wypalić w pipeline CI i sprawdzić, czy nie ma smutków, takich jak zależności, z restrykcyjnymi licencjami lub kodem, który został skopiowany z Stackoverflow i najwyraźniej narusza niektóre prawa autorskie -❌ **W przeciwnym razie:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues +❌ **W przeciwnym razie:** Nieumyślnie programiści mogą używać pakietów z nieodpowiednimi licencjami lub kopiować wklejony kod komercyjny i napotykać problemy prawne
    @@ -1782,11 +1782,11 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.6 Constantly inspect for vulnerable dependencies +## ⚪ ️5.6 Nieustannie sprawdzaj wrażliwe zależności -:white_check_mark: **Opis:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +:white_check_mark: **Opis:** Nawet najbardziej renomowane zależności, takie jak Express, mają znane luki w zabezpieczeniach. Można to łatwo oswoić za pomocą narzędzi community, takich jak [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), lub komercyjnych narzędzi takich jak [snyk](https://snyk.io/) (oferuje również darmową wersję community). Oba mogą być wywoływane z twojego CI na każdej kompilacji -❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious +❌ **W przeciwnym razie:** Utrzymywanie kodu w czystości przed lukami bez dedykowanych narzędzi będzie wymagało ciągłego śledzenia publikacji online na temat nowych zagrożeń. Dość nudne
    @@ -1794,7 +1794,7 @@ license-checker --summary --failOn BSD
    -### :clap: Example: NPM Audit result +### :clap: Przykład: wynik NPM Audit ![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") @@ -1802,18 +1802,18 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.7 Automate dependency updates +## ⚪ ️5.7 Zautomatyzuj aktualizacje zależności -:white_check_mark: **Opis:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: +:white_check_mark: **Opis:** Yarn i npm ostatnie wprowadzenie Package-lock.json wprowadziło poważne wyzwanie (droga do piekła jest wybrukowana dobrymi intencjami) - domyślnie teraz pakiety nie otrzymują już aktualizacji. Nawet zespół prowadzący wiele nowych wdrożeń z ‘npm install’ & ‘npm update’ nie otrzyma nowych aktualizacji. Prowadzi to w najlepszym razie do obniżonych wersji pakietów lub w najgorszym przypadku do podatnego kodu. Zespoły polegają teraz na dobrej woli programistów i pamięci, aby ręcznie aktualizować package.json lub korzystać ręcznie z narzędzi [taki jak ncu](https://www.npmjs.com/package/npm-check-updates). Bardziej niezawodnym sposobem może być zautomatyzowanie procesu uzyskiwania najbardziej niezawodnych wersji zależności, chociaż nie ma jeszcze srebrnych rozwiązań, istnieją dwie możliwe drogi automatyzacji: -(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. +(1) CI może zawieść buildy, które mają przestarzałe zależności - przy użyciu narzędzi takich jak [‘npm outdated’](https://docs.npmjs.com/cli/outdated) lub ‘npm-check-updates (ncu)’. Takie postępowanie zmusi programistów do aktualizacji zależności. -(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). +(2) Użyj komercyjnych narzędzi, które skanują kod i automatycznie wysyłają pull requesty ze zaktualizowanymi zależnościami. Pozostaje jedno interesujące pytanie: jaka powinna być zasada aktualizacji zależności - aktualizacja każdej poprawki generuje zbyt wiele narzutów, aktualizowanie zaraz po wydaniu wersji głównej może wskazywać na niestabilną wersję (wiele pakietów było podatnych na atak już w pierwszych dniach po wydaniu, [zobacz](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). -An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +Skuteczne zasady aktualizacji mogą pozwolić na pewien 'okres nabywania uprawnień' - pozwól, aby kod pozostawał w tyle za @latest przez pewien czas i wersjami, zanim uzna lokalną kopię za przestarzałą (np. wersja lokalna to 1.3.1, a wersja repozytorium to 1.3.8)
    -❌ **W przeciwnym razie:** Your production will run packages that have been explicitly tagged by their author as risky +❌ **W przeciwnym razie:** Produkcja będzie uruchamiać pakiety, które zostały wyraźnie oznaczone przez autora jako ryzykowne
    @@ -1821,7 +1821,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions +### :clap: Przykład: [ncu](https://www.npmjs.com/package/npm-check-updates) może być używany ręcznie lub w pipeline CI, aby wykryć, w jakim stopniu kod opóźnia się w stosunku do najnowszych wersji ![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") @@ -1829,23 +1829,23 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    -## ⚪ ️ 5.8 Other, non-Node related, CI tips +## ⚪ ️ 5.8 Inne, niezwiązane z Node'm, porady CI -:white_check_mark: **Opis:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +:white_check_mark: **Opis:** Ten post skupia się na poradach dotyczących testowania, które są związane lub przynajmniej mogą być zilustrowane przykładem Node JS. Ten punkt zawiera jednak kilka dobrze znanych wskazówek niezwiązanych z 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

    -❌ **W przeciwnym razie:** You‘ll miss years of wisdom +❌ **W przeciwnym razie:** Będziesz tęsknić za latami mądrości

    -## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions +## ⚪ ️ 5.9 Build matrix: Uruchom te same kroki CI, używając wielu wersji Node -:white_check_mark: **Opis:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +:white_check_mark: **Opis:** W kontroli jakości chodzi o przypadkowość, im więcej masz miejsca, tym więcej masz szczęścia we wczesnym wykrywaniu problemów. Podczas opracowywania pakietów wielokrotnego użytku lub uruchamiania produkcji dla wielu klientów z różnymi wersjami konfiguracji i wersji Node'a, CI musi uruchomić pipeline testów na wszystkich kombinacjach konfiguracji. Na przykład, zakładając, że używamy MySQL dla niektórych klientów i Postgres dla innych - niektórzy dostawcy CI obsługują funkcję o nazwie 'Matrix', która pozwala na uruchomienie zestawu testów dla wszystkich permutacji MySQL, Postgres i wielu wersji Node, takich jak 8, 9 i 10. Odbywa się to przy użyciu konfiguracji tylko bez dodatkowego wysiłku (zakładając, że masz testy lub inne kontrole jakości). Inne CI, które nie obsługują Matrix, mogą mieć rozszerzenia lub poprawki, aby to umożliwić
    -❌ **W przeciwnym razie:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? +❌ **W przeciwnym razie:** Czy po tak ciężkiej pracy związanej z pisaniem testów pozwolimy, aby błędy wkradły się tylko z powodu problemów z konfiguracją?
    @@ -1853,7 +1853,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +### :clap: Przykład: użycie definicji kompilacji Travis (dostawca CI) do uruchomienia tego samego testu w wielu wersjach Node
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    From 6a8f91161a996f1c663d638fe67f703eb39c062e Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 7 Apr 2020 18:32:40 +0200 Subject: [PATCH 199/502] update readme added Polish ver. info --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 386c481a..3293556c 100644 --- a/readme.md +++ b/readme.md @@ -32,6 +32,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇨🇳[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) - Want to translate to your own language? please open an issue 💜

    From 8c7568b9b0cae59f6b4e372c2af0f8d675a05df8 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 7 Apr 2020 18:34:13 +0200 Subject: [PATCH 200/502] update readme small fix, internal link --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 3293556c..a46a8768 100644 --- a/readme.md +++ b/readme.md @@ -32,7 +32,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇨🇳[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) +- 🇵🇱[Polish](readme-pl.md) - courtesy of [Michal Biesiada](https://github.com/mbiesiad) - Want to translate to your own language? please open an issue 💜

    From 3e972951024f4dd24a75ea16017b7511b457afb5 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 7 Apr 2020 18:43:24 +0200 Subject: [PATCH 201/502] update readme-pl.md fixed internal links --- readme-pl.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme-pl.md b/readme-pl.md index 43484bd7..65b72f77 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -43,7 +43,7 @@ Zacznij od zrozumienia wszechobecnych praktyk testowania, które są podstawą k Jedna rada, która inspiruje wszystkich innych (1 specjalny punkt) -#### [`Sekcja 1: Anatomia testu`](#sekcja-1-anatomia-testu) +#### [`Sekcja 1: Anatomia testu`](#sekcja-1-anatomia-testu-1) Podstawa - konstruowanie czystych testów (12 wypunktowań) @@ -55,11 +55,11 @@ Pisanie backendu i wydajne testy Mikroserwisów (8 wypunktowań) Pisanie testów dla webowego interfejsu użytkownika, w tym testy komponentów i testy E2E (11 wypunktowań) -#### [`Sekcja 4: Pomiary skuteczności testów`](#sekcja-4️⃣-pomiary-skuteczności-testów) +#### [`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) -#### [`Sekcja 5: Continuous Integration`](#sekcja-5️⃣-ci-and-other-quality-measures) +#### [`Sekcja 5: Continuous Integration`](#sekcja-5️⃣-ci-oraz-inne-miary-jakości) Wytyczne dla CI w świecie JS (9 wypunktowań) From 1fe3a7b99c2de46393daa838627fdcf4749b7035 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 7 Apr 2020 19:49:09 +0200 Subject: [PATCH 202/502] update readme-pl.md small fix, typo --- readme-pl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-pl.md b/readme-pl.md index 65b72f77..3d64312e 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -1081,7 +1081,7 @@ Biorąc to wszystko pod uwagę, należy zachować ostrożność: ta technika dzi
    -❌ **W przeciwnym razie:** Podczas wbijania się w wewnętrzne komponenty przez wywoływanie ich prywatnych metod i sprawdzania stanu wewnętrznego - podczas refaktoryzacji implementacji komponentów musiałbyś przefakturować wszystkie testy. Czy naprawdę masz możliwości takiego poziomu konserwacji? +❌ **W przeciwnym razie:** Podczas wbijania się w wewnętrzne komponenty przez wywoływanie ich prywatnych metod i sprawdzania stanu wewnętrznego - podczas refaktoryzacji implementacji komponentów musiałbyś przerefakturować wszystkie testy. Czy naprawdę masz możliwości takiego poziomu konserwacji?
    From a2b4bcde2727088bc6a4437e9fd0358701e84539 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2020 15:18:38 +0000 Subject: [PATCH 203/502] docs: update readme.md [skip ci] --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 386c481a..771ba383 100644 --- a/readme.md +++ b/readme.md @@ -1940,10 +1940,10 @@ Thanks goes to these wonderful people who have contributed to this repository!
    koooge

    🖋 +
    Michal

    🖋 - From 7b1846ebe313cb2a6979ec2f8ee14ee9aa98a9ec Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2020 15:18:39 +0000 Subject: [PATCH 204/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 431fa410..2dcb1b6a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -204,6 +204,15 @@ "contributions": [ "content" ] + }, + { + "login": "mbiesiad", + "name": "Michal", + "avatar_url": "https://avatars0.githubusercontent.com/u/18367606?v=4", + "profile": "https://twitter.com/michalbiesiada", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 01e497df8e960a0e1e3fffb7e84d92ccbb45cd0d Mon Sep 17 00:00:00 2001 From: Ruy Maraventano Date: Fri, 24 Apr 2020 21:05:13 +0200 Subject: [PATCH 205/502] Fix missing checkmark --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 007b29f2..292fb9a9 100644 --- a/readme.md +++ b/readme.md @@ -729,7 +729,7 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr ## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid -:white*check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit \_all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit \_all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. From 2c9bb058d95c93b94b6e6e6cc79ef9c2a2bcbd8a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 10 May 2020 08:08:41 +0000 Subject: [PATCH 206/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 007b29f2..006a85ed 100644 --- a/readme.md +++ b/readme.md @@ -1942,6 +1942,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    koooge

    🖋
    Michal

    🖋 +
    roywalker

    🖋 From c66d7c0cb36864aa577a16c26861a484a33e2d6f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 10 May 2020 08:08:42 +0000 Subject: [PATCH 207/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 2dcb1b6a..1bdb101e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -213,6 +213,15 @@ "contributions": [ "content" ] + }, + { + "login": "roywalker", + "name": "roywalker", + "avatar_url": "https://avatars0.githubusercontent.com/u/611846?v=4", + "profile": "http://roywalker.me", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From e29bc94b3254fb76d5d5c2587e04ff72cdb4f82c Mon Sep 17 00:00:00 2001 From: biesiadamich <60202305+biesiadamich@users.noreply.github.com> Date: Sun, 10 May 2020 14:15:26 +0200 Subject: [PATCH 208/502] update readme small fixes typo --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index dd852169..b6171a3c 100644 --- a/readme.md +++ b/readme.md @@ -729,7 +729,7 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr ## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid -:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit \_all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. @@ -747,7 +747,7 @@ A word of caution: the TDD argument in the software world takes a typical false-
    -### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the sane way’ +### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing 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’") @@ -1509,7 +1509,7 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear. and fear will slow you down +❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down
    From c8367f00560afa9a6fd835adad2be52478ce8fb7 Mon Sep 17 00:00:00 2001 From: dangen <23185799+dangen-effy@users.noreply.github.com> Date: Thu, 14 May 2020 10:06:05 +0900 Subject: [PATCH 209/502] Update readme.md Missing async keyword --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index dd852169..b195b291 100644 --- a/readme.md +++ b/readme.md @@ -536,7 +536,7 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => { ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") ```javascript -before(() => { +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'); }); @@ -914,7 +914,7 @@ Credit: { +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'); }); From 63f4682b2c28bc90477d656c14001bb6212cbfd7 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 31 May 2020 11:03:23 +0300 Subject: [PATCH 210/502] Update readme.md --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index dd852169..d0b8987c 100644 --- a/readme.md +++ b/readme.md @@ -140,6 +140,11 @@ describe('Products Service', function() {
    +
    +
    ©Credits + [Roy Osherove - The Art of Unit Testing with examples in JavaScript](https://www.manning.com/books/the-art-of-unit-testing-third-edition) +
    +

    ## ⚪ ️ 1.2 Structure tests by the AAA pattern From 63c8429a911a7440a04c645d7da508a87b04f8d7 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 31 May 2020 11:10:39 +0300 Subject: [PATCH 211/502] Update readme.md --- readme.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index d0b8987c..0e0c3837 100644 --- a/readme.md +++ b/readme.md @@ -109,6 +109,7 @@ Most of the advice below are derivatives of this principle.
    **👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +
    Code Examples @@ -141,9 +142,9 @@ describe('Products Service', function() {

    -
    ©Credits - [Roy Osherove - The Art of Unit Testing with examples in JavaScript](https://www.manning.com/books/the-art-of-unit-testing-third-edition) -
    +
    © Credits + 1. Roy Osherove - The Art of Unit Testing with examples in JavaScript +


    From bc0880f282855f88770d9fb09674eb3e0bb90178 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 31 May 2020 11:41:53 +0300 Subject: [PATCH 212/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0e0c3837..59a82dcc 100644 --- a/readme.md +++ b/readme.md @@ -143,7 +143,7 @@ describe('Products Service', function() {
    © Credits - 1. Roy Osherove - The Art of Unit Testing with examples in JavaScript + 1. Roy Osherove - The Art of Unit Testing with examples in JavaScript


    From 831569f2c7d0a938d1718d03421e8d6a10f8c4bd Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 31 May 2020 12:01:22 +0300 Subject: [PATCH 213/502] Update readme.md --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 59a82dcc..de837b35 100644 --- a/readme.md +++ b/readme.md @@ -142,8 +142,8 @@ describe('Products Service', function() {

    -
    © Credits - 1. Roy Osherove - The Art of Unit Testing with examples in JavaScript +
    © Credits & read-more + 1. Roy Osherove - Naming standards for unit tests


    From e9599cbe325fae380033d9fa96f61aa63de5b321 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 7 Jun 2020 08:02:25 +0000 Subject: [PATCH 214/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index de837b35..411a884a 100644 --- a/readme.md +++ b/readme.md @@ -1949,6 +1949,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    koooge

    🖋
    Michal

    🖋
    roywalker

    🖋 +
    dangen

    🖋 From 73b01e52a6d637daa8e622931b4954edcdfb1aba Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 7 Jun 2020 08:02:26 +0000 Subject: [PATCH 215/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 1bdb101e..626f423f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -222,6 +222,15 @@ "contributions": [ "content" ] + }, + { + "login": "dangen-effy", + "name": "dangen", + "avatar_url": "https://avatars3.githubusercontent.com/u/23185799?v=4", + "profile": "https://dangen-effy.github.io/", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 5bacd237d55230368de37de140861a6b6ec0199b Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 7 Jun 2020 08:04:34 +0000 Subject: [PATCH 216/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index b136cc89..48824a19 100644 --- a/readme.md +++ b/readme.md @@ -1950,6 +1950,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    Michal

    🖋
    roywalker

    🖋
    dangen

    🖋 +
    biesiadamich

    🖋 From 24c3e575c83e136d1db14be8adfd21a554b9383d Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 7 Jun 2020 08:04:35 +0000 Subject: [PATCH 217/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 626f423f..49b901d0 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -231,6 +231,15 @@ "contributions": [ "content" ] + }, + { + "login": "biesiadamich", + "name": "biesiadamich", + "avatar_url": "https://avatars1.githubusercontent.com/u/60202305?v=4", + "profile": "https://dev.to/mbiesiad", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 6a795e6e4320a0099090ad75dac6b89324176601 Mon Sep 17 00:00:00 2001 From: Yoni Date: Sat, 20 Jun 2020 21:31:13 +0300 Subject: [PATCH 218/502] Package json --- index.js | 1 + package.json | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 index.js create mode 100644 package.json diff --git a/index.js b/index.js new file mode 100644 index 00000000..e6be5312 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +// This is a book for now, but code examples are coming soon \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..dde3fe4b --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "javascript-testing-best-practices", + "version": "1.0.0", + "description": "📗🌐 🚢 Comprehensive and exhaustive JavaScript & Node.js testing best practices (April 2020) https://testjavascript.com/", + "main": "index.js", + "scripts": { + "test": "t" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/i0natan/javascript-testing-best-practices.git" + }, + "keywords": [ + "test", + "tdd", + "javascript", + "mocha", + "jest", + "aaa", + "coverage", + "confidence", + "unit", + "test", + "integration", + "e2e" + ], + "author": "Yoni Goldberg", + "license": "ISC", + "bugs": { + "url": "https://github.com/i0natan/javascript-testing-best-practices/issues" + }, + "homepage": "https://github.com/i0natan/javascript-testing-best-practices#readme" +} From 37b9ef36d03e78bc9d588a27610c60e57e99420d Mon Sep 17 00:00:00 2001 From: Lukas Bischof Date: Fri, 26 Jun 2020 13:35:00 +0200 Subject: [PATCH 219/502] Correct two spelling issues --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index b1b6b5f2..7b829d33 100644 --- a/readme.md +++ b/readme.md @@ -670,7 +670,7 @@ describe("Order service", function() {
    -❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause +❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in a hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause
    @@ -724,7 +724,7 @@ test("Then there should not be a new transfer record", () => {}); :white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satisfies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc)
    ❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades From c7812f001bab66e05a878f8a015d246c8555f4b6 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Wed, 15 Jul 2020 23:59:00 +0200 Subject: [PATCH 220/502] introduction done --- readme-es.md | 1959 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1959 insertions(+) create mode 100644 readme-es.md diff --git a/readme-es.md b/readme-es.md new file mode 100644 index 00000000..34ec19e7 --- /dev/null +++ b/readme-es.md @@ -0,0 +1,1959 @@ + + +
    + +# 👇 Por qué esta guia puede hacerte llevar tus habilidades de testing al siguiente nivel + +
    + +## 📗 45+ buenas practicas: Super comprensiva y exhaustiva + +Esta es una guia completa para Javascript y Node de la A a la Z. Resumen y selecciona docenas de los mejores post de blogs, libros, y herramientas ofrecidas por el mercado + +## 🚢 Advanzado: Va 10.000 kilometros más allá de lo basico + +Súbete a un viaje que va más allá de lo básico, llegando a temas avanzados como pruebas en producción, mutation testing, property-based testing y muchas otras herramientas estratégicas y profesionales. Si lees esta guía completamente es probable que tus habilidades de testing este por encima de la media + +## 🌐 Full-stack: front, backend, CI, cualquier cosa + +Empieza por comprender las tecnicas de testing ubicuas que son la base de cualquier nivel de aplicación. Luego, profundice en su área de elección: frontend/UI, backend, CI o tal vez todos. + +
    + +### Escrita por Yoni Goldberg + +- Consultor JavaScript & Node.js +- 📗 [Testing Node.js & JavaScript de la A a la Z](https://www.testjavascript.com) - Mi curso completamente online con más de [10 horas de video](https://www.testjavascript.com), 14 tipos de test y más de 40 buenas practicas +- [Sigueme en Twitter ](https://twitter.com/goldbergyoni/) + +
    + +### Traducciones - leelas en tu propio idioma + +- 🇨🇳[Chino](readme-zh-CN.md) - cortesia de [Yves yao](https://github.com/yvesyao) +- 🇰🇷[Coreano](readme.kr.md) - cortesia de [Rain Byun](https://github.com/ragubyun) +- 🇵🇱[Polaco](readme-pl.md) - cortesia de [Michal Biesiada](https://github.com/mbiesiad) +- Quieres traducir a tu propio lenguaje? porfavor abre una issue 💜 + +

    + +## `Tabla de Contenidos` + +#### [`Section 0: The Golden Rule`](#section-0️⃣-the-golden-rule) + +A single advice that inspires all the others (1 special bullet) + +#### [`Section 1: The Test Anatomy`](#section-1-the-test-anatomy-1) + +The foundation - structuring clean tests (12 bullets) + +#### [`Section 2: Backend`](#section-2️⃣-backend-testing) + +Writing backend and Microservices tests efficiently (8 bullets) + +#### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) + +Writing tests for web UI including component and E2E tests (11 bullets) + +#### [`Section 4: Measuring Tests Effectiveness`](#section-4️⃣-measuring-test-effectiveness) + +Watching the watchman - measuring test quality (4 bullets) + +#### [`Section 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) + +Guidelines for CI in the JS world (9 bullets) + +

    + +# Section 0️⃣: The Golden Rule + +
    + +## ⚪️ 0 The Golden Rule: Design for lean testing + +:white_check_mark: **Do:** +Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. + +Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. + +The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). + +This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. + +![alt text](/assets/headspace.png "We have no head room for additional complexity") + +Most of the advice below are derivatives of this principle. + +### Ready to start? + +

    + +# Section 1: The Test Anatomy + +
    + +## ⚪ ️ 1.1 Include 3 parts in each test name + +:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: + +(1) What is being tested? For example, the ProductsService.addNewProduct method + +(2) Under what circumstances and scenario? For example, no price is passed to the method + +(3) What is the expected result? For example, the new product is not approved + +
    + +❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? + +
    + +**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: A test name that constitutes 3 parts + +![](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: Doing It Right Example: A test name that constitutes 3 parts + +![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 Structure tests by the AAA pattern + +:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: + +1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code + +2nd A - Act: Execute the unit under test. Usually 1 line of code + +3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code + +
    + +❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: A test structured with the AAA pattern + +![](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: Anti Pattern Example: No separation, one bulk, harder to interpret + +```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 Describe expectations in a product language: use BDD-style assertions + +:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +
    + +❌ **Otherwise:** The team will write less tests and decorate the annoying ones with .skip() + +
    + +
    Code Examples
    + +![](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: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story + +```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: Doing It Right Example: Skimming through the following declarative test is a breeze + +```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 Stick to black-box testing: Test only public methods + +:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden +
    + +❌ **Otherwise:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored… + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A test case is testing the internals for no good reason + +![](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 Choose the right test doubles: Avoid mocks in favor of stubs and spies + +:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). + +Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. + +For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently +
    + +❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-pattern example: Mocks focus on the internals + +![](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:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals + +```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; +}); +``` + +
    + +

    + +## 📗 Want to learn all these practices with live video? + +### Visit my online course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +

    + +## ⚪ ️1.6 Don’t “foo”, use realistic input data + +:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +
    + +❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data + +![](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:Doing It Right Example: Randomizing realistic input + +```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 Test many input combinations using Property-based testing + +:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +
    + +❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Testing many input permutations with “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 If needed, use only short & inline snapshots + +:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. + +On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much + +It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +
    + +❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code + +![](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: Doing It Right Example: Expectations are visible and focused + +```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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](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: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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 Don’t catch errors, expect them + +:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations + +A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +
    + +❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with 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: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical 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 Tag your tests + +:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +
    + +❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) + +![](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 Categorize tests under at least 2 levels + +:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and prtscn below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) + +
    + +❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below + +![](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: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests + +![](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 Other generic good testing hygiene + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +
    + +❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades + +

    + +# Section 2️⃣: Backend Testing + +## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid + +:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? + +Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. + +It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks + +A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] + +
    + +❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing 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 Component testing might be your best affair + +:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. + +Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +
    + +❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) + +![](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 Ensure new releases don’t break the API using + +:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +
    + +❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: + +![](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 Test your middlewares in isolation + +:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +
    + +❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests + +
    + +
    Code Examples + +
    + +### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine + +![](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 Measure and refactor using static analysis tools + +:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) + +Credit: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + +❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: + +![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg "Examples with CodeClimate") + +![alt text](assets/bp-16-yoni-goldberg-quality.png "CodeClimat, a commercial tool that can identify complex methods:") + +
    + +

    + +## ⚪ ️ 2.6 Check your readiness for Node-related chaos + +:white_check_mark: **Do:** Weirdly, most software testings are about logic & data only, but some of the worst things that happen (and are really hard to mitigate) are infrastructural issues. For example, did you ever test what happens when your process memory is overloaded, or when the server/process dies, or does your monitoring system realizes when the API becomes 50% slower?. To test and mitigate these type of bad things — [Chaos engineering](https://principlesofchaos.org/) was born by Netflix. It aims to provide awareness, frameworks and tools for testing our app resiliency for chaotic issues. For example, one of its famous tools, [the chaos monkey](https://github.com/Netflix/chaosmonkey), randomly kills servers to ensure that our service can still serve users and not relying on a single server (there is also a Kubernetes version, [kube-monkey](https://github.com/asobti/kube-monkey), that kills pods). All these tools work on the hosting/platform level, but what if you wish to test and generate pure Node chaos like check how your Node process copes with uncaught errors, unhandled promise rejection, v8 memory overloaded with the max allowed of 1.7GB or whether your UX stays satisfactory when the event loop gets blocked often? to address this I’ve written, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) which provides all sort of Node-related chaotic acts +
    + +❌ **Otherwise:** No escape here, Murphy’s law will hit your production without mercy + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: : Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to 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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](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: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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️⃣: Frontend Testing + +## ⚪ ️ 3.1 Separate UI from functionality + +:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI + +
    + +❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Separating out the UI details + +![](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: Anti Pattern Example: Assertion mix UI details and data + +```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 HTML elements based on attributes that are unlikely to change + +:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed + +
    + +❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing + +![](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: Anti-Pattern Example: Relying on 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 Whenever possible, test with a realistic and fully rendered component + +:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake + +With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children + +
    + +❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Working realstically with a fully rendered component + +![](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: Anti-Pattern Example: Mocking the reality with 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 Don't sleep, use frameworks built-in support for async events. Also try to speed things up + +:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +
    + +❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (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: Doing It Right Example: Testing library that waits for DOM elements + +```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: Anti-Pattern Example: custom sleep code + +```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 Watch how the content is served over the network + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") + +✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 + +
    + +❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration + +
    + +
    Code Examples + +### :clap: Doing It Right Example: Lighthouse page load inspection report + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + +
    + +
    + +## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs + +:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests + +
    + +❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower + +
    + +
    Code Examples + +
    + +### :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 +// 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 Have very few end-to-end tests that spans the whole system + +:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment + +
    + +❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected + +
    + +## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials + +:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). + +
    + +❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Logging-in before-all and not 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 Have one E2E smoke test that just travels across the site map + +:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector + +
    + +❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Smoke travelling across all 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 Expose the tests as a live collaborative document + +:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. + +❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Describing tests in human-language using 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: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook + +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 Detect visual issues with automated tools + +:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue + +
    + +❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A typical visual regression - right content that is served badly + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + +### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots + +![](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: Doing It Right Example: Using Applitools to get snapshot comaprison and other advanced features + +![](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️⃣: Measuring Test Effectiveness + +

    + +## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number + +:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. + +Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets + +
    + +❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down + +
    + +
    Code Examples + +
    + +### :clap: Example: A typical coverage report + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: Doing It Right Example: Setting up coverage per component (using 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 Inspect coverage reports to detect untested areas and other oddities + +:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +
    + +❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: 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?") + +
    + +

    + +## ⚪ ️ 4.3 Measure logical coverage using mutation testing + +:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. + +Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: + +(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations + +(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. + +Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +
    + +❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: 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({ asignee: "John@mailer.com", price: 120 }); +}); //Triggers 100% code coverage, but it doesn't check anything +``` + +
    + +### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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 Preventing test code issues with Test linters + +:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) + +
    + +❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: A test case full of errors, luckily all are caught by 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 and Other Quality Measures + +

    + +## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues + +:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +
    + +❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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 Shorten the feedback loop with local developer-CI + +:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. + +Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +
    + +❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new 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 Perform e2e testing over a true production-mirror + +:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. + +The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +
    + +❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated + +
    + +
    Code Examples + +
    + +### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([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 Parallelize test execution + +:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes + +❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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 Stay away from legal issues using license and plagiarism check + +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights + +❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: + +```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 Constantly inspect for vulnerable dependencies + +:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build + +❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious + +
    + +
    Code Examples + +
    + +### :clap: Example: NPM Audit result + +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + +

    + +## ⚪ ️5.7 Automate dependency updates + +:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: + +(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. + +(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). + +An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +
    + +❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky + +
    + +
    Code Examples + +
    + +### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest 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 Other, non-Node related, CI tips + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +
    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
    +
    + +❌ **Otherwise:** You‘ll miss years of wisdom + +

    + +## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions + +:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +
    + +❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? + +
    + +
    Code Examples + +
    + +### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions + +
    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 500 fortune corporates 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. + +## 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

    🖋
    + + + + From 767b3365155abc8a1deb343b379df3348d2e4a4b Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Thu, 16 Jul 2020 00:32:23 +0200 Subject: [PATCH 221/502] section1 done --- readme-es.md | 306 +++++++++++++++++++++++++-------------------------- 1 file changed, 153 insertions(+), 153 deletions(-) diff --git a/readme-es.md b/readme-es.md index 34ec19e7..2ac92414 100644 --- a/readme-es.md +++ b/readme-es.md @@ -39,62 +39,62 @@ Empieza por comprender las tecnicas de testing ubicuas que son la base de cualqu ## `Tabla de Contenidos` -#### [`Section 0: The Golden Rule`](#section-0️⃣-the-golden-rule) +#### [`Sección 0: La Regla de Oro`](#section-0️⃣-the-golden-rule) -A single advice that inspires all the others (1 special bullet) +Un solo consejo que inspira a todos los demás (1 apartado especial) -#### [`Section 1: The Test Anatomy`](#section-1-the-test-anatomy-1) +#### [`Sección 1: La Anatomía de un Test`](#section-1-the-test-anatomy-1) -The foundation - structuring clean tests (12 bullets) +La base - estructurando tests claros (12 apartados) -#### [`Section 2: Backend`](#section-2️⃣-backend-testing) +#### [`Sección 2: Backend`](#section-2️⃣-backend-testing) -Writing backend and Microservices tests efficiently (8 bullets) +Escribiendo test de backend y Microservicios eficientemente (8 apartados) -#### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) +#### [`Sección 3: Frontend`](#section-3️⃣-frontend-testing) -Writing tests for web UI including component and E2E tests (11 bullets) +Escribiendo test para web UI incluyendo test de componente y E2E (11 apartados) -#### [`Section 4: Measuring Tests Effectiveness`](#section-4️⃣-measuring-test-effectiveness) +#### [`Sección 4: Midiendo la Efectividad de los Tests`](#section-4️⃣-measuring-test-effectiveness) -Watching the watchman - measuring test quality (4 bullets) +Vigilando al vigilante - midiendo la calidad de los test (4 apartados) -#### [`Section 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) +#### [`Sección 5: Integración Continua`](#section-5️⃣-ci-and-other-quality-measures) -Guidelines for CI in the JS world (9 bullets) +Pautas para Integracion Continua en el mundo de JS (9 apartados)

    -# Section 0️⃣: The Golden Rule +# Sección 0️⃣: La Regla de Oro
    -## ⚪️ 0 The Golden Rule: Design for lean testing +## ⚪️ 0 La Regla de Oro: Diseñando testing ligero -:white_check_mark: **Do:** -Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. +:white_check_mark: **Haz:** +El codigo de los test no es como el código de producción - diséñelo para que sea simple, corto, sin abstracciones, plano, agradable de trabajar, ligero. Uno debe mirar un test y entender la intención al instante. -Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. +Nuestras mentes están llenas por el código de producción, no tenemos espacio de cabeza para añadir más cosas complejas. Si intentamos introducir otro código desafiante en nuestro cerebro, ralentizará al todo el equipo, lo que va en contra de la razón por la que hacemos testing. Prácticamente aquí es donde muchos equipos simplemente abandonan el testing. -The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). +Los test son una oportunidad de tener algo más: un asistente amable y sonriente, con el que es un placer trabajar y da mucho valor a cambio de una inversión tan pequeña. La ciencia nos dice que tenemos dos sistemas cerebrales: el sistema 1 se usa para actividades sin esfuerzo como conducir un automóvil en una carretera vacía y el sistema 2, que está destinado a operaciones complejas y conscientes como resolver una ecuación matemática. Diseña tus test para el sistema 1, cuando observes el código de un test, debería parecer tan fácil como modificar un documento HTML y no como resolver 2X(17 × 24). -This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. +Esto se puede lograr mediante la seleccion de técnicas cuidadosamente, herramientas y objetivos de test que son rentables y proporcionan un gran retorno de la inversión. Testee solo lo que sea necesario, esfuércese por mantenerlo ágil, a veces incluso vale la pena abandonar algunos test y cambiar la confiabilidad por agilidad y simplicidad. -![alt text](/assets/headspace.png "We have no head room for additional complexity") +![alt text](/assets/headspace.png "No tenemos espacio para la más complejidad") -Most of the advice below are derivatives of this principle. +La mayoría de los siguientes consejos son derivados de este principio. -### Ready to start? +### ¿Listo para empezar?

    -# Section 1: The Test Anatomy +# Sección 1: La Anatomía de un Test
    ## ⚪ ️ 1.1 Include 3 parts in each test name -:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: +:white_check_mark: **Haz:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: (1) What is being tested? For example, the ProductsService.addNewProduct method @@ -104,14 +104,14 @@ Most of the advice below are derivatives of this principle.
    -❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? +❌ **De lo contrario:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning?
    -**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +**👇 Nota:** Each bullet has code examples and sometime also an image illustration. Click to expand
    -
    Code Examples +
    Código de Ejemplo
    @@ -150,7 +150,7 @@ describe('Products Service', function() { ## ⚪ ️ 1.2 Structure tests by the AAA pattern -:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: +:white_check_mark: **Haz:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: 1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code @@ -160,11 +160,11 @@ describe('Products Service', function() {
    -❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain +❌ **De lo contrario:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain
    -
    Code Examples +
    Código de Ejemplo
    @@ -207,14 +207,14 @@ test("Should be classified as premium", () => { ## ⚪ ️1.3 Describe expectations in a product language: use BDD-style assertions -:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +:white_check_mark: **Haz:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/)
    -❌ **Otherwise:** The team will write less tests and decorate the annoying ones with .skip() +❌ **De lo contrario:** The team will write less tests and decorate the annoying ones with .skip()
    -
    Code Examples
    +
    Código de Ejemplo
    ![](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") @@ -267,13 +267,13 @@ it("When asking for an admin, ensure only ordered admins in results", () => { ## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods -:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden +:white_check_mark: **Haz:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden
    -❌ **Otherwise:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored… +❌ **De lo contrario:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored…
    -
    Code Examples +
    Código de Ejemplo
    @@ -309,18 +309,18 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response", ## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies -:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **Haz:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently
    -❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend +❌ **De lo contrario:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend
    -
    Code Examples +
    Código de Ejemplo
    @@ -368,14 +368,14 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 Don’t “foo”, use realistic input data -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +:white_check_mark: **Haz:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing).
    -❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **De lo contrario:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA”
    -
    Code Examples +
    Código de Ejemplo
    @@ -422,14 +422,14 @@ it("Better: When adding new valid product, get successful confirmation", async ( ## ⚪ ️ 1.7 Test many input combinations using Property-based testing -:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +:white_check_mark: **Haz:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained
    -❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs +❌ **De lo contrario:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs
    -
    Code Examples +
    Código de Ejemplo
    @@ -459,18 +459,18 @@ describe("Product service", () => { ## ⚪ ️ 1.8 If needed, use only short & inline snapshots -:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. +:white_check_mark: **Haz:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes
    -❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **De lo contrario:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown...
    -
    Code Examples +
    Código de Ejemplo
    @@ -526,14 +526,14 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => { ## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +:white_check_mark: **Haz:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries)
    -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **De lo contrario:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data
    -
    Code Examples +
    Código de Ejemplo
    @@ -583,16 +583,16 @@ it("When updating site name, get successful confirmation", async () => { ## ⚪ ️ 1.10 Don’t catch errors, expect them -:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations +:white_check_mark: **Haz:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user
    -❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +❌ **De lo contrario:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong
    -
    Code Examples +
    Código de Ejemplo
    @@ -633,14 +633,14 @@ it("When no product name, it throws error 400", async () => { ## ⚪ ️ 1.11 Tag your tests -:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +:white_check_mark: **Haz:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’
    -❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests +❌ **De lo contrario:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests
    -
    Code Examples +
    Código de Ejemplo
    @@ -666,15 +666,15 @@ describe("Order service", function() { ## ⚪ ️ 1.12 Categorize tests under at least 2 levels -:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and prtscn below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **Haz:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and prtscn below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway)
    -❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause +❌ **De lo contrario:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause
    -
    Code Examples +
    Código de Ejemplo
    @@ -722,12 +722,12 @@ test("Then there should not be a new transfer record", () => {}); ## ⚪ ️1.13 Other generic good testing hygiene -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +:white_check_mark: **Haz:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc)
    -❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades +❌ **De lo contrario:** You‘ll miss pearls of wisdom that were collected for decades

    @@ -735,7 +735,7 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr ## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid -:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **Haz:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. @@ -745,11 +745,11 @@ A word of caution: the TDD argument in the software world takes a typical false-
    -❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes +❌ **De lo contrario:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes
    -
    Code Examples +
    Código de Ejemplo
    @@ -769,16 +769,16 @@ A word of caution: the TDD argument in the software world takes a typical false- ## ⚪ ️2.2 Component testing might be your best affair -:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +:white_check_mark: **Haz:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time.
    -❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage +❌ **De lo contrario:** You may spend long days on writing unit tests to find out that you got only 20% system coverage
    -
    Code Examples +
    Código de Ejemplo
    @@ -794,14 +794,14 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ## ⚪ ️2.3 Ensure new releases don’t break the API using -:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +:white_check_mark: **Haz:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration
    -❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear +❌ **De lo contrario:** The alternatives are exhausting manual testing or deployment fear
    -
    Code Examples +
    Código de Ejemplo
    @@ -817,14 +817,14 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ## ⚪ ️ 2.4 Test your middlewares in isolation -:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +:white_check_mark: **Haz:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below)
    -❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests +❌ **De lo contrario:** A bug in Express middleware === a bug in all or most requests
    -
    Code Examples +
    Código de Ejemplo
    @@ -857,17 +857,17 @@ test("A request without authentication header, should return http status 403", ( ## ⚪ ️2.5 Measure and refactor using static analysis tools -:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) +:white_check_mark: **Haz:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) Credit: [Keith Holliday](https://github.com/TheHollidayInn)
    -❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix +❌ **De lo contrario:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix
    -
    Code Examples +
    Código de Ejemplo
    @@ -883,14 +883,14 @@ Credit: { ## ⚪ ️ 3.1 Separate UI from functionality -:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI +:white_check_mark: **Haz:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI
    -❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation +❌ **De lo contrario:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation
    -
    Code Examples +
    Código de Ejemplo
    @@ -1015,15 +1015,15 @@ test("When flagging to show only VIP, should display only VIP members", () => { ## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change -:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +:white_check_mark: **Haz:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed
    -❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **De lo contrario:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border'
    -
    Code Examples +
    Código de Ejemplo
    @@ -1079,17 +1079,17 @@ test("Whenever no data is passed, error metric shows zero", () => { ## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component -:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +:white_check_mark: **Haz:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children
    -❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? +❌ **De lo contrario:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance?
    -
    Code Examples +
    Código de Ejemplo
    @@ -1151,14 +1151,14 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display ## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up -:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +:white_check_mark: **Haz:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution
    -❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance +❌ **De lo contrario:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance
    -
    Code Examples +
    Código de Ejemplo
    @@ -1219,15 +1219,15 @@ test("movie title appears", async () => { ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") -✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 +✅ **Haz:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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
    -❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration +❌ **De lo contrario:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration
    -
    Code Examples +
    Código de Ejemplo ### :clap: Doing It Right Example: Lighthouse page load inspection report @@ -1239,15 +1239,15 @@ test("movie title appears", async () => { ## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs -:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +:white_check_mark: **Haz:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests
    -❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower +❌ **De lo contrario:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower
    -
    Code Examples +
    Código de Ejemplo
    @@ -1293,25 +1293,25 @@ test("When no products exist, show the appropriate message", () => { ## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system -:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +:white_check_mark: **Haz:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment
    -❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected +❌ **De lo contrario:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected
    ## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials -:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). +:white_check_mark: **Haz:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6).
    -❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again +❌ **De lo contrario:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again
    -
    Code Examples +
    Código de Ejemplo
    @@ -1351,15 +1351,15 @@ beforeEach(setUser => () { ## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map -:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector +:white_check_mark: **Haz:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector
    -❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering +❌ **De lo contrario:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering
    -
    Code Examples +
    Código de Ejemplo
    @@ -1386,13 +1386,13 @@ it("When doing smoke testing over all page, should load them all successfully", ## ⚪ ️ 3.10 Expose the tests as a live collaborative document -:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. +:white_check_mark: **Haz:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. -❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value +❌ **De lo contrario:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value
    -
    Code Examples +
    Código de Ejemplo
    @@ -1429,15 +1429,15 @@ Feature: Twitter new tweet ## ⚪ ️ 3.11 Detect visual issues with automated tools -:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue +:white_check_mark: **Haz:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue
    -❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? +❌ **De lo contrario:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden?
    -
    Code Examples +
    Código de Ejemplo
    @@ -1509,17 +1509,17 @@ describe("visual validation", () => { ## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number -:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. +:white_check_mark: **Haz:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets
    -❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down +❌ **De lo contrario:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down
    -
    Code Examples +
    Código de Ejemplo
    @@ -1541,14 +1541,14 @@ Implementation tips: You may want to configure your continuous integration (CI) ## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities -:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +:white_check_mark: **Haz:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas
    -❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from +❌ **De lo contrario:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from
    -
    Code Examples +
    Código de Ejemplo
    @@ -1564,7 +1564,7 @@ Based on a real-world scenario where we tracked our application usage in QA and ## ⚪ ️ 4.3 Measure logical coverage using mutation testing -:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +:white_check_mark: **Haz:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: @@ -1575,11 +1575,11 @@ Mutation-based testing is here to help by measuring the amount of code that was Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar
    -❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code +❌ **De lo contrario:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code
    -
    Code Examples +
    Código de Ejemplo
    @@ -1613,14 +1613,14 @@ it("Test addNewOrder, don't use such test names", () => { ## ⚪ ️4.4 Preventing test code issues with Test linters -:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +:white_check_mark: **Haz:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything)
    -❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation +❌ **De lo contrario:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation
    -
    Code Examples +
    Código de Ejemplo
    @@ -1650,14 +1650,14 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues -:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +:white_check_mark: **Haz:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…)
    -❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day +❌ **De lo contrario:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day
    -
    Code Examples +
    Código de Ejemplo
    @@ -1671,16 +1671,16 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI -:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +:white_check_mark: **Haz:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky))
    -❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact +❌ **De lo contrario:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact
    -
    Code Examples +
    Código de Ejemplo
    @@ -1711,16 +1711,16 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com ## ⚪ ️5.3 Perform e2e testing over a true production-mirror -:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. +:white_check_mark: **Haz:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes.
    -❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated +❌ **De lo contrario:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated
    -
    Code Examples +
    Código de Ejemplo
    @@ -1734,13 +1734,13 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo ## ⚪ ️5.4 Parallelize test execution -:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes +:white_check_mark: **Haz:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes -❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant +❌ **De lo contrario:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant
    -
    Code Examples +
    Código de Ejemplo
    @@ -1754,13 +1754,13 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo ## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights +:white_check_mark: **Haz:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights -❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues +❌ **De lo contrario:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues
    -
    Code Examples +
    Código de Ejemplo
    @@ -1785,13 +1785,13 @@ license-checker --summary --failOn BSD ## ⚪ ️5.6 Constantly inspect for vulnerable dependencies -:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +:white_check_mark: **Haz:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build -❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious +❌ **De lo contrario:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious
    -
    Code Examples +
    Código de Ejemplo
    @@ -1805,7 +1805,7 @@ license-checker --summary --failOn BSD ## ⚪ ️5.7 Automate dependency updates -:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: +:white_check_mark: **Haz:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: (1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. @@ -1814,11 +1814,11 @@ license-checker --summary --failOn BSD An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8)
    -❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky +❌ **De lo contrario:** Your production will run packages that have been explicitly tagged by their author as risky
    -
    Code Examples +
    Código de Ejemplo
    @@ -1832,25 +1832,25 @@ An efficient update policy may allow some ‘vesting period’ — let the c ## ⚪ ️ 5.8 Other, non-Node related, CI tips -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +:white_check_mark: **Haz:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known
    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

    -❌ **Otherwise:** You‘ll miss years of wisdom +❌ **De lo contrario:** You‘ll miss years of wisdom

    ## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions -:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +:white_check_mark: **Haz:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that
    -❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? +❌ **De lo contrario:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues?
    -
    Code Examples +
    Código de Ejemplo
    From f35a6d3f73266fbf9f61dcb7e949f8e8ef120fae Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Thu, 16 Jul 2020 00:45:22 +0200 Subject: [PATCH 222/502] section1.1 done --- readme-es.md | 98 ++++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/readme-es.md b/readme-es.md index 2ac92414..cd15785e 100644 --- a/readme-es.md +++ b/readme-es.md @@ -92,38 +92,38 @@ La mayoría de los siguientes consejos son derivados de este principio.
    -## ⚪ ️ 1.1 Include 3 parts in each test name +## ⚪ ️ 1.1 Incluye 3 partes en los nombres de tus test -:white_check_mark: **Haz:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: +:white_check_mark: **Haz:** El reporte de un test debe indicar si la revisión de la aplicación actual cumple los requisitos para las personas que no están necesariamente familiarizadas con el código: el tester, el DevOps que está desplegangolo y el futuro tú de dentro de dos años. Esto se puede lograr si las pruebas hablan al nivel de los requisitos e incluyen 3 partes: -(1) What is being tested? For example, the ProductsService.addNewProduct method +(1) ¿Qué se está testeando? Por ejemplo, el método ProductsService.addNewProduct -(2) Under what circumstances and scenario? For example, no price is passed to the method +(2) ¿Bajo qué escenario y circunstancias? Por ejemplo, no se pasa ningún precio al método -(3) What is the expected result? For example, the new product is not approved +(3) ¿Cuál es el resultado esperado? Por ejemplo, el nuevo producto no está aprobado
    -❌ **De lo contrario:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? +❌ **De lo contrario:** Un despliegue simplemente ha fallado, un test llamado "Agregar producto" ha fallado. ¿Esto te dice exactamente qué está funcionando mal?
    -**👇 Nota:** Each bullet has code examples and sometime also an image illustration. Click to expand +**👇 Nota:** Cada apartado tiene ejemplos de código y, en ocasiones, también una imagen ilustrativa. Haga clic para ampliar
    Código de Ejemplo
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :clap: Ejemplo de cómo hacerlo correctamente: Un nombre de test que consta de 3 partes -![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Using Mocha to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Ejemplo usando Mocha para ilustrar la idea") ```javascript -//1. unit under test +//1. unidad que esta siendo testeada describe('Products Service', function() { describe('Add new product', function() { - //2. scenario and 3. expectation + //2. escenario y 3. quá se espera it('When no price is specified, then the product status is pending approval', ()=> { const newProduct = new ProductService().add(...); expect(newProduct.status).to.equal('pendingApproval'); @@ -135,14 +135,14 @@ describe('Products Service', function() {
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :clap: Ejemplo de cómo hacerlo correctamente: Un nombre de test que consta de 3 partes -![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") +![alt text](/assets/bp-1-3-parts.jpeg "Un nombre de test que consta de 3 partes")

    -
    © Credits & read-more +
    © Creditos y más información 1. Roy Osherove - Naming standards for unit tests
    @@ -168,7 +168,7 @@ describe('Products Service', function() {
    -### :clap: Doing It Right Example: A test structured with the AAA pattern +### :clap: Ejemplo de cómo hacerlo correctamente: A test structured with the AAA pattern ![](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") @@ -248,7 +248,7 @@ test("When asking for an admin, ensure only ordered admins in results", () => {
    -### :clap: Doing It Right Example: Skimming through the following declarative test is a breeze +### :clap: Ejemplo de cómo hacerlo correctamente: Skimming through the following declarative test is a breeze ```javascript it("When asking for an admin, ensure only ordered admins in results", () => { @@ -344,7 +344,7 @@ it("When a valid product is about to be deleted, ensure data access DAL was call
    -### :clap:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals +### :clap:Ejemplo de cómo hacerlo correctamente: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals ```javascript it("When a valid product is about to be deleted, ensure an email is sent", async () => { @@ -404,7 +404,7 @@ test("Wrong: When adding new product with valid properties, get successful confi
    -### :clap:Doing It Right Example: Randomizing realistic input +### :clap:Ejemplo de cómo hacerlo correctamente: Randomizing realistic input ```javascript it("Better: When adding new valid product, get successful confirmation", async () => { @@ -433,7 +433,7 @@ it("Better: When adding new valid product, get successful confirmation", async (
    -### :clap: Doing It Right Example: Testing many input permutations with “fast-check” +### :clap: Ejemplo de cómo hacerlo correctamente: Testing many input permutations with “fast-check” ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -496,7 +496,7 @@ it("TestJavaScript.com is renderd correctly", () => {
    -### :clap: Doing It Right Example: Expectations are visible and focused +### :clap: Ejemplo de cómo hacerlo correctamente: Expectations are visible and focused ```javascript it("When visiting TestJavaScript.com home page, a menu is displayed", () => { @@ -562,7 +562,7 @@ it("When querying by site name, get the right site", async () => {
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: Ejemplo de cómo hacerlo correctamente: We can stay within the test, each test acts on its own set of data ```javascript it("When updating site name, get successful confirmation", async () => { @@ -617,7 +617,7 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM +### :clap: Ejemplo de cómo hacerlo correctamente: A human-readable expectation that could be understood easily, maybe even by QA or technical PM ```javascript it("When no product name, it throws error 400", async () => { @@ -644,7 +644,7 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) +### :clap: Ejemplo de cómo hacerlo correctamente: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -678,7 +678,7 @@ describe("Order service", function() {
    -### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below +### :clap: Ejemplo de cómo hacerlo correctamente: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -753,7 +753,7 @@ A word of caution: the TDD argument in the software world takes a typical false-
    -### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the same way’ +### :clap: Ejemplo de cómo hacerlo correctamente: Cindy Sridharan suggests a rich testing portfolio in her amazing 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’") @@ -782,7 +782,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) +### :clap: Ejemplo de cómo hacerlo correctamente: Supertest allows approaching Express API in-process (fast and cover many layers) ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") @@ -805,7 +805,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap: Doing It Right Example: +### :clap: Ejemplo de cómo hacerlo correctamente: ![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") @@ -828,7 +828,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :clap:Ejemplo de cómo hacerlo correctamente: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -871,7 +871,7 @@ Credit: {
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: Ejemplo de cómo hacerlo correctamente: We can stay within the test, each test acts on its own set of data ```javascript it("When updating site name, get successful confirmation", async () => { @@ -973,7 +973,7 @@ it("When updating site name, get successful confirmation", async () => {
    -### :clap: Doing It Right Example: Separating out the UI details +### :clap: Ejemplo de cómo hacerlo correctamente: Separating out the UI details ![](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") @@ -1027,7 +1027,7 @@ test("When flagging to show only VIP, should display only VIP members", () => {
    -### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing +### :clap: Ejemplo de cómo hacerlo correctamente: Querying an element using a dedicated attrbiute for testing ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") @@ -1093,7 +1093,7 @@ With all that said, a word of caution is in order: this technique works for smal
    -### :clap: Doing It Right Example: Working realstically with a fully rendered component +### :clap: Ejemplo de cómo hacerlo correctamente: Working realstically with a fully rendered component ![](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") @@ -1162,7 +1162,7 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) +### :clap: Ejemplo de cómo hacerlo correctamente: E2E API that resolves only when the async operations is done (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") @@ -1174,7 +1174,7 @@ cy.wait("@products"); // wait for route to appear // this line will get executed only when the route is ready ``` -### :clap: Doing It Right Example: Testing library that waits for DOM elements +### :clap: Ejemplo de cómo hacerlo correctamente: Testing library that waits for DOM elements ```javascript // @testing-library/dom @@ -1229,7 +1229,7 @@ test("movie title appears", async () => {
    Código de Ejemplo -### :clap: Doing It Right Example: Lighthouse page load inspection report +### :clap: Ejemplo de cómo hacerlo correctamente: Lighthouse page load inspection report ![](/assets/lighthouse2.png "Lighthouse page load inspection report") @@ -1251,7 +1251,7 @@ test("movie title appears", async () => {
    -### :clap: Doing It Right Example: Stubbing or intercepting API calls +### :clap: Ejemplo de cómo hacerlo correctamente: 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") @@ -1315,7 +1315,7 @@ test("When no products exist, show the appropriate message", () => {
    -### :clap: Doing It Right Example: Logging-in before-all and not before-each +### :clap: Ejemplo de cómo hacerlo correctamente: Logging-in before-all and not before-each ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") @@ -1363,7 +1363,7 @@ beforeEach(setUser => () {
    -### :clap: Doing It Right Example: Smoke travelling across all pages +### :clap: Ejemplo de cómo hacerlo correctamente: Smoke travelling across all pages ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") @@ -1396,7 +1396,7 @@ it("When doing smoke testing over all page, should load them all successfully",
    -### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js +### :clap: Ejemplo de cómo hacerlo correctamente: Describing tests in human-language using cucumber-js ![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") @@ -1417,7 +1417,7 @@ Feature: Twitter new tweet ``` -### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook +### :clap: Ejemplo de cómo hacerlo correctamente: Visualizing our components, their various states and inputs using Storybook ![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") @@ -1447,7 +1447,7 @@ Feature: Twitter new tweet
    -### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots +### :clap: Ejemplo de cómo hacerlo correctamente: Configuring wraith to capture and compare UI snapshots ![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") @@ -1476,7 +1476,7 @@ paths: path: /subscribe ``` -### :clap: Doing It Right Example: Using Applitools to get snapshot comaprison and other advanced features +### :clap: Ejemplo de cómo hacerlo correctamente: Using Applitools to get snapshot comaprison and other advanced features ![](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") @@ -1529,7 +1529,7 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :clap: Doing It Right Example: Setting up coverage per component (using Jest) +### :clap: Ejemplo de cómo hacerlo correctamente: Setting up coverage per component (using Jest) ![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") @@ -1603,7 +1603,7 @@ it("Test addNewOrder, don't use such test names", () => {
    -### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :clap: Ejemplo de cómo hacerlo correctamente: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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)") @@ -1684,7 +1684,7 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com
    -### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: Ejemplo de cómo hacerlo correctamente: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code ```javascript "scripts": { @@ -1744,7 +1744,7 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    -### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :clap: Ejemplo de cómo hacerlo correctamente: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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)") @@ -1764,7 +1764,7 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    -### :clap: Doing It Right Example: +### :clap: Ejemplo de cómo hacerlo correctamente: ```javascript //install license-checker in your CI environment or also locally From 8a323c8c83736bddbea5cd449bfa2a0206e5c77e Mon Sep 17 00:00:00 2001 From: MorganGeek Date: Fri, 17 Jul 2020 22:09:20 +0200 Subject: [PATCH 223/502] fix some typos, improve consistency / spelling --- readme.md | 58 +++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/readme.md b/readme.md index b1b6b5f2..4a5b0b2f 100644 --- a/readme.md +++ b/readme.md @@ -190,7 +190,7 @@ describe("Customer classifier", () => {
    -### :thumbsdown: Anti Pattern Example: No separation, one bulk, harder to interpret +### :thumbsdown: Anti-Pattern Example: No separation, one bulk, harder to interpret ```javascript test("Should be classified as premium", () => { @@ -218,7 +218,7 @@ test("Should be classified as premium", () => { ![](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: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story +### :thumbsdown: Anti-Pattern Example: The reader must skim through not so short, and imperative code just to get the test story ```javascript test("When asking for an admin, ensure only ordered admins in results", () => { @@ -277,7 +277,7 @@ it("When asking for an admin, ensure only ordered admins in results", () => {
    -### :thumbsdown: Anti Pattern Example: A test case is testing the internals for no good reason +### :thumbsdown: Anti-Pattern Example: A test case is testing the internals for no good reason ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") @@ -537,7 +537,7 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {
    -### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") @@ -666,7 +666,7 @@ describe("Order service", function() { ## ⚪ ️ 1.12 Categorize tests under at least 2 levels -:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and prtscn below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and print screen below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway)
    @@ -724,7 +724,7 @@ test("Then there should not be a new transfer record", () => {}); :white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satisfies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc)
    ❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades @@ -737,7 +737,7 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr :white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? -Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. +Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IoT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks @@ -857,7 +857,7 @@ test("A request without authentication header, should return http status 403", ( ## ⚪ ️2.5 Measure and refactor using static analysis tools -:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) +:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [SonarQube](https://www.sonarqube.org/) (4,900+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (2,000+ [stars](https://github.com/codeclimate/codeclimate)) Credit:
    [Keith Holliday](https://github.com/TheHollidayInn) @@ -875,7 +875,7 @@ Credit: @@ -883,7 +883,7 @@ Credit: -### :thumbsdown: Anti Pattern Example: Assertion mix UI details and data +### :thumbsdown: Anti-Pattern Example: Assertion mix UI details and data ```javascript test("When flagging to show only VIP, should display only VIP members", () => { @@ -1027,7 +1027,7 @@ test("When flagging to show only VIP, should display only VIP members", () => {
    -### :clap: Doing It Right Example: Querying an element using a dedicated attrbiute for testing +### :clap: Doing It Right Example: Querying an element using a dedicated attribute for testing ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") @@ -1093,7 +1093,7 @@ With all that said, a word of caution is in order: this technique works for smal
    -### :clap: Doing It Right Example: Working realstically with a fully rendered component +### :clap: Doing It Right Example: Working realistically with a fully rendered component ![](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") @@ -1293,7 +1293,7 @@ test("When no products exist, show the appropriate message", () => { ## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system -:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment
    @@ -1303,7 +1303,7 @@ test("When no products exist, show the appropriate message", () => { ## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials -:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). +:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6).
    @@ -1429,7 +1429,7 @@ Feature: Twitter new tweet ## ⚪ ️ 3.11 Detect visual issues with automated tools -:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue +:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge significant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by eliminating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/CSS changes that led to the issue
    @@ -1441,7 +1441,7 @@ Feature: Twitter new tweet
    -### :thumbsdown: Anti Pattern Example: A typical visual regression - right content that is served badly +### :thumbsdown: Anti-Pattern Example: A typical visual regression - right content that is served badly ![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") @@ -1476,9 +1476,9 @@ paths: path: /subscribe ``` -### :clap: Doing It Right Example: Using Applitools to get snapshot comaprison and other advanced features +### :clap: Doing It Right Example: Using Applitools to get snapshot comparison and other advanced features -![](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") +![](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"; @@ -1583,7 +1583,7 @@ Knowing that all or most of the mutations were killed gives much higher confiden
    -### :thumbsdown: Anti Pattern Example: 100% coverage, 0% testing +### :thumbsdown: Anti-Pattern Example: 100% coverage, 0% testing ![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") @@ -1597,7 +1597,7 @@ function addNewOrder(newOrder) { } it("Test addNewOrder, don't use such test names", () => { - addNewOrder({ asignee: "John@mailer.com", price: 120 }); + addNewOrder({ assignee: "John@mailer.com", price: 120 }); }); //Triggers 100% code coverage, but it doesn't check anything ``` @@ -1624,7 +1624,7 @@ it("Test addNewOrder, don't use such test names", () => {
    -### :thumbsdown: Anti Pattern Example: A test case full of errors, luckily all are caught by Linters +### :thumbsdown: Anti-Pattern Example: A test case full of errors, luckily all are caught by Linters ```javascript describe("Too short description", () => { @@ -1653,7 +1653,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test :white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…)
    -❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day +❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5 min linter setup could detect this TYPO and save your day
    @@ -1661,7 +1661,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    -### :thumbsdown: Anti Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug +### :thumbsdown: Anti-Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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") @@ -1711,9 +1711,9 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com ## ⚪ ️5.3 Perform e2e testing over a true production-mirror -:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. +:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of FaaS code. -The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes.
    ❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated @@ -1754,7 +1754,7 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo ## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stack Overflow and apparently violates some copyrights ❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues @@ -1871,7 +1871,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c **Role:** Writer -**About:** I'm an independent consultant who works with 500 fortune corporates 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) +**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) From 6d770ceca998e2c93491418c56612a498ab81bfa Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 00:08:37 +0200 Subject: [PATCH 224/502] section1.7 done --- readme-es.md | 185 +++++++++++++++++++++++++-------------------------- 1 file changed, 92 insertions(+), 93 deletions(-) diff --git a/readme-es.md b/readme-es.md index cd15785e..0788c29c 100644 --- a/readme-es.md +++ b/readme-es.md @@ -16,7 +16,7 @@ Súbete a un viaje que va más allá de lo básico, llegando a temas avanzados c ## 🌐 Full-stack: front, backend, CI, cualquier cosa -Empieza por comprender las tecnicas de testing ubicuas que son la base de cualquier nivel de aplicación. Luego, profundice en su área de elección: frontend/UI, backend, CI o tal vez todos. +Empieza por comprender las tecnicas de testing ubicuas que son la base de cualquier nivel de aplicación. Luego, profundice en tu área de elección: frontend/UI, backend, CI o tal vez todos.
    @@ -72,7 +72,7 @@ Pautas para Integracion Continua en el mundo de JS (9 apartados) ## ⚪️ 0 La Regla de Oro: Diseñando testing ligero :white_check_mark: **Haz:** -El codigo de los test no es como el código de producción - diséñelo para que sea simple, corto, sin abstracciones, plano, agradable de trabajar, ligero. Uno debe mirar un test y entender la intención al instante. +El código de los test no es como el código de producción - diséñelo para que sea simple, corto, sin abstracciones, plano, agradable de trabajar, ligero. Uno debe mirar un test y entender la intención al instante. Nuestras mentes están llenas por el código de producción, no tenemos espacio de cabeza para añadir más cosas complejas. Si intentamos introducir otro código desafiante en nuestro cerebro, ralentizará al todo el equipo, lo que va en contra de la razón por la que hacemos testing. Prácticamente aquí es donde muchos equipos simplemente abandonan el testing. @@ -148,19 +148,19 @@ describe('Products Service', function() {

    -## ⚪ ️ 1.2 Structure tests by the AAA pattern +## ⚪ ️ 1.2 Estructura tus test con el patron AAA -:white_check_mark: **Haz:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: +:white_check_mark: **Haz:** Estructura tus test con 3 secciones bien separadas Arreglar u organizar, Actuar y Afirmar (AAA en inglés Arrange, Act & Assert). Seguir esta estructura garantiza que quien lea nuestro test no gaste CPU-cerebro en comprender el plan de prueba: -1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code +1st A - Arreglar: configura el código acercarte al escenario que el test pretende simular. Esto podría incluir crear instancias de la unidad bajo el constructor del test, agregar registros de base de datos, mocks y stubs de objetos y cualquier otro código necesario -2nd A - Act: Execute the unit under test. Usually 1 line of code +2nd A - Actuar: Ejecuta la unidad en test. Normalmente 1 línea de código -3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code +3rd A - Afirmar: Comprobar que el valor recibido satisface las expectativas. Normalmente 1 línea de código
    -❌ **De lo contrario:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain +❌ **De lo contrario:** No solo emplearas horas comprendiendo el código principal, si no que lo que deberia haber sido la parate mas simple del día (testing) ha estrujado tu cerebro
    @@ -168,21 +168,21 @@ describe('Products Service', function() {
    -### :clap: Ejemplo de cómo hacerlo correctamente: A test structured with the AAA pattern +### :clap: Ejemplo de cómo hacerlo correctamente: Un test estructurado con el patron 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") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha") ```javascript describe("Customer classifier", () => { test("When customer spent more than 500$, should be classified as premium", () => { - //Arrange + //Arreglar / organizar const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); - //Act + //Actuar const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); - //Assert + //Afirmar expect(receivedClassification).toMatch("premium"); }); }); @@ -190,7 +190,7 @@ describe("Customer classifier", () => {
    -### :thumbsdown: Anti Pattern Example: No separation, one bulk, harder to interpret +### :thumbsdown: Ejemplo Anti Patrón: Sin separación, una pieza, más dificil de interpretar ```javascript test("Should be classified as premium", () => { @@ -205,24 +205,24 @@ test("Should be classified as premium", () => {

    -## ⚪ ️1.3 Describe expectations in a product language: use BDD-style assertions +## ⚪ ️1.3 Describe las expectativas en el lenguaje del producto.: usa aserciones estilo BDD -:white_check_mark: **Haz:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +:white_check_mark: **Haz:** Escribir el código de tus test de forma declarativa permite que aquel que lo lea tenga al instante la idea sin tener que catar CPU-cerebro. Cuando escribes el código de los test de forma imperativa estará lleno de condiciones lógicas y obligas al que lo lee a gastar mucho más tiempo en comprenderlo. En este caso, escribe el código de la forma más humana, de forma declarativa BDD usando `expect` o `should` y sin usar código personalizado. Si Chai o Jest no incluyen las aserciones que deseas y se hace muy repetitivo, siempre puede usar [extender Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) o escribir un [plugin de Chai](https://www.chaijs.com/guide/plugins/)
    -❌ **De lo contrario:** The team will write less tests and decorate the annoying ones with .skip() +❌ **De lo contrario:** El equipo escribirá menos test y acabará marcando los test más molestos con .skip()
    Código de Ejemplo
    -![](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") +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha y Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") -### :thumbsdown: Anti Pattern Example: The reader must skim through not so short, and imperative code just to get the test story +### :thumbsdown: Ejemplo Anti Patrón: quien lea nuestro test deberá hojear un código largo e imperativo para conocer la historia del 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" + //presuponiendo que hayamos agregado aquí dos administradores "admin1", "admin2" y "user1" const allAdmins = getUsers({ adminOnly: true }); let admin1Found, @@ -248,11 +248,11 @@ test("When asking for an admin, ensure only ordered admins in results", () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Skimming through the following declarative test is a breeze +### :clap: Ejemplo de cómo hacerlo correctamente: Ojear el siguiente test declarativo es muy sencillo ```javascript it("When asking for an admin, ensure only ordered admins in results", () => { - //assuming we've added here two admins + //presuponiendo que hayamos agregado aquí dos administradores const allAdmins = getUsers({ adminOnly: true }); expect(allAdmins) @@ -265,29 +265,28 @@ it("When asking for an admin, ensure only ordered admins in results", () => {

    -## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods +## ⚪ ️ 1.4 Acercarse al testing caja-negra: Testea solo metodos publicos -:white_check_mark: **Haz:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden -
    +:white_check_mark: **Haz:** Testear las partes internas suele traer un gasto extra enorme para muy poco beneficio. Si tu código/API comprueba todo los resultado correctamente, ¿deberias perder las proximas 3 horas en comprobar como esta funcionando internamente y despues mantener esos test tan fragiles? Cada vez que se comprueba un comportamiento publico, la implementacion privada es implicitamente testeada y tus test se romperan solo si hay un problema concreto (por ejemplo una salida incorrecta). Este enfoque tambien es conocido como `behavioral testing` (testing de comportamiento). Por otro lado, si se prueban las partes internas (caja blanca) tu enfoque cambia de planificar la salida del componente a detalles minusculos, y tus test pueden romperse debido a refactors de código menores sin que se rompan los test de salida, por lo que aumenta tremendamente el mantenimiento de los mismos.
    -❌ **De lo contrario:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored… +❌ **De lo contrario:** Tus test se comportaran como la fabula [que viene el lobo](https://es.wikipedia.org/wiki/El_pastor_mentiroso): gritando falsos positivos (por ejemplo, un test dalla por que se cambio el nombre a una variable provada). Como es de esperar, la gente empezara a ingnorar estos test hasta que un día ignoren un test de verdad...
    Código de Ejemplo
    -### :thumbsdown: Anti Pattern Example: A test case is testing the internals for no good reason +### :thumbsdown: Ejemplo Anti Patrón: Un test esta testeando la parte interna sin ninguna razón aparente -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha & Chai") +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha & Chai") ```javascript class ProductService { - //this method is only used internally - //Change this name will make the tests fail + //este metodo es usado solo internamente + //Cambiarle el nombre causara que el test falle calculateVATAdd(priceWithoutVAT) { return { finalPrice: priceWithoutVAT * 1.2 }; - //Change the result format or key name above will make the tests fail + //Cambiar el formatos de salida o nombre de la clave a continuacion hara que el test falle } //public method getPrice(productId) { @@ -298,7 +297,7 @@ class ProductService { } 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 + //No hay ningún requisito para permitir a los usuarios calcular el IVA, solo existe el de mostrar el precio final al usuario. Sin embargo, falsamente insistimos en testear las partes privadas de la clase. expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0); }); ``` @@ -307,16 +306,16 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response",

    -## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies +## ⚪ ️ ️1.5 Eligiendo los dobles de los test correctamente: Evita mocks en favor de stubs y spies -:white_check_mark: **Haz:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value (
    [Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **Haz:** Los dobles de los test son un mal necesario por que estan acoplados a las tripas de la aplicacion, sin embargo, algunos proporcionan un valor inmenso ([Aquí tienes un recordatorio sobre los dobles de los test: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). -Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. +Antes de usar un doble, hazte una simple pregunta: ¿Lo voy a usar para testear una funcionalidad que aparece, o puede aparecer, en el documento de requisitos? Si no, eso huele que estas testeando partes privadas. -For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently +Por ejemplo, si quieres testear que tu app se comporta razonablemente cuando el servicio de pagos está caido, deberias hacer un stub del servicio de pagos y devolver un 'Sin respuesta' para asegurar que la unidad que está siendo testeada devuelve el valor correcto. Esto verifica que el comportamiento/respuesta/resultado de nuesta app en ciertos escenarios. Tambien podrias usar un spy para asegurar que un email ha sifo enviado cuando este servicio está caidom — esto es nuevamente una verificacion de comportamiento que probablemente aparezca en el documento de requisitos ("Enviar un correo electrónico si no se pudo guardar el pago"). En el lado opuesto, si se mockea el servicio de pagos y se asegura que se haya llamado con el tipado correcto — entonces tu test esta comprobando cosas internas que tienen nada que ver con la funcionalidad de la app y es muy probable que cambien con frecuencia
    -❌ **De lo contrario:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend +❌ **De lo contrario:** Cualquier refactor de codigo te exigirá buscar todos los mocks que tengas y tendras que actualizarlos en consecuencia. Las pruebas pasan de ser un amigo útil a una carga más
    @@ -324,15 +323,15 @@ For example, if you want to test that your app behaves reasonably when the payme
    -### :thumbsdown: Anti-pattern example: Mocks focus on the internals +### :thumbsdown: Ejemplo Anti Patrón: Mocks centrados en la parte interna -![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg "Examples with Sinon") +![](https://img.shields.io/badge/🔧%20Example%20using%20Sinon-blue.svg "Ejemplos con 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 + //Asumimos que ya hemos añadido un producto const dataAccessMock = sinon.mock(DAL); - //hmmm BAD: testing the internals is actually our main goal here, not just a side-effect + //hmmm MAL: testear las partes internas es nuestro principal objetivo, en vez de ser un efecto secundario dataAccessMock .expects("deleteProduct") .once() @@ -344,14 +343,14 @@ it("When a valid product is about to be deleted, ensure data access DAL was call
    -### :clap:Ejemplo de cómo hacerlo correctamente: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals +### :clap:Ejemplo de cómo hacerlo correctamente: los spies se centran en testear los requisitos pero como efecto secundario inevitable se estan testeando las partes internas ```javascript it("When a valid product is about to be deleted, ensure an email is sent", async () => { - //Assume we already added here a product + //Asumimos que ya hemos añadido un producto 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) + //hmmm OK: ¿Testeamos las partes internas? Si, pero como efecto secundario de hacer test de los requisitos (enviar un email) expect(spy.calledOnce).to.be.true; }); ``` @@ -360,18 +359,18 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

    -## 📗 Want to learn all these practices with live video? +## 📗 ¿Quieres aprender todas esto con video en directo? -### Visit my online course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) +### Visita el curso online [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com)

    -## ⚪ ️1.6 Don’t “foo”, use realistic input data +## ⚪ ️1.6 No uses “foo”, usa datos realistas -:white_check_mark: **Haz:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +:white_check_mark: **Haz:** A menudo, los errores de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar errores temprano. Utiliza bibliotecas dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de prodcucción. Por ejemplo, dichas bibliotecas pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para estirar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Vea la próxima sección (pruebas basadas en propiedades).
    -❌ **De lo contrario:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **De lo contrario:** Todo tus test de desarrollo estaran en falsos verdes cuando uses datos sinteticos como "Foo", pero luego en produccion pueden ponerse en rojo cuando un hacker use cadenas extrañas como “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA”
    @@ -379,40 +378,40 @@ it("When a valid product is about to be deleted, ensure an email is sent", async
    -### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data +### :thumbsdown: Ejemplo Anti Patrón: Un conjunto de test que dan ok debido a datos no realistas -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript const addProduct = (name, price) => { - const productNameRegexNoSpace = /^\S*$/; //no white-space allowed + const productNameRegexNoSpace = /^\S*$/; //no se admiten espacios - if (!productNameRegexNoSpace.test(name)) return false; //this path never reached due to dull input + if (!productNameRegexNoSpace.test(name)) return false; //esta rama nunca se testeara debido a inputs sinteticos - //some logic here + //algo de logica aquí 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 + //La cadena "Foo" que es usada en todo los test, nunca provocara un resutado false const addProductResult = addProduct("Foo", 5); expect(addProductResult).toBe(true); - //Positive-false: the operation succeeded because we never tried with long - //product name including spaces + //Falso positivo: la operación tuvo éxito porque nunca lo intentamos con un + //nombre de producto largo que incluya espacios }); ```
    -### :clap:Ejemplo de cómo hacerlo correctamente: Randomizing realistic input +### :clap:Ejemplo de cómo hacerlo correctamente: Generando datos de entrada realistas ```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} + //Datos de entrada generados aleatoreamente: {'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! + //El test falla, El valor de entrada random ha provocado que se vaya por un camino que nunca planeamos + //!Hemos descubierto un error muy pronto! }); ``` @@ -435,7 +434,7 @@ it("Better: When adding new valid product, get successful confirmation", async ( ### :clap: Ejemplo de cómo hacerlo correctamente: Testing many input permutations with “fast-check” -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript import fc from "fast-check"; @@ -474,9 +473,9 @@ It’s worth noting that there are few cases where long & external snapshots are
    -### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code +### :thumbsdown: Ejemplo Anti Patrón: Coupling our test to unseen 2000 lines of code -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript it("TestJavaScript.com is renderd correctly", () => { @@ -537,9 +536,9 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {
    -### :thumbsdown: Anti Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :thumbsdown: Ejemplo Anti Patrón: tests are not independent and rely on some global hook to feed global DB data -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha") ```javascript before(async () => { @@ -596,9 +595,9 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e
    -### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch +### :thumbsdown: Ejemplo Anti Patrón: A long test case that tries to assert the existence of error with try-catch -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha") ```javascript it("When no product name, it throws error 400", async () => { @@ -646,7 +645,7 @@ it("When no product name, it throws error 400", async () => { ### :clap: Ejemplo de cómo hacerlo correctamente: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript //this test is fast (no DB) and we're tagging it correspondigly @@ -680,7 +679,7 @@ describe("Order service", function() { ### :clap: Ejemplo de cómo hacerlo correctamente: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript // Unit under test @@ -700,9 +699,9 @@ describe("Transfer service", () => {
    -### :thumbsdown: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests +### :thumbsdown: Ejemplo Anti Patrón: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Mocha") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Mocha") ```javascript test("Then the response status should decline", () => {}); @@ -784,7 +783,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ### :clap: Ejemplo de cómo hacerlo correctamente: Supertest allows approaching Express API in-process (fast and cover many layers) -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con 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)") @@ -807,7 +806,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ### :clap: Ejemplo de cómo hacerlo correctamente: -![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") +![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Ejemplos con PACT") ![alt text](assets/bp-14-testing-best-practices-contract-flow.png) @@ -830,7 +829,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ### :clap:Ejemplo de cómo hacerlo correctamente: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript //the middleware we want to test @@ -873,7 +872,7 @@ Credit: { @@ -975,7 +974,7 @@ it("When updating site name, get successful confirmation", async () => { ### :clap: Ejemplo de cómo hacerlo correctamente: Separating out the UI details -![](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") +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Ejemplos con React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Ejemplos con react-testing-library") ```javascript test("When users-list is flagged to show only VIP, should display only VIP members", () => { @@ -994,7 +993,7 @@ test("When users-list is flagged to show only VIP, should display only VIP membe
    -### :thumbsdown: Anti Pattern Example: Assertion mix UI details and data +### :thumbsdown: Ejemplo Anti Patrón: Assertion mix UI details and data ```javascript test("When flagging to show only VIP, should display only VIP members", () => { @@ -1029,7 +1028,7 @@ test("When flagging to show only VIP, should display only VIP members", () => { ### :clap: Ejemplo de cómo hacerlo correctamente: Querying an element using a dedicated attrbiute for testing -![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Ejemplos con React") ```html // the markup code (part of React component) @@ -1056,7 +1055,7 @@ test("Whenever no data is passed to metric, show 0 as default", () => {
    -### :thumbsdown: Anti-Pattern Example: Relying on CSS attributes +### :thumbsdown: Ejemplo Anti Patrón: Relying on CSS attributes ```html @@ -1095,7 +1094,7 @@ With all that said, a word of caution is in order: this technique works for smal ### :clap: Ejemplo de cómo hacerlo correctamente: Working realstically with a fully rendered component -![](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") +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Ejemplos con React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Enzyme-blue.svg "Ejemplos con Enzyme") ```javascript class Calendar extends React.Component { @@ -1125,7 +1124,7 @@ test("Realistic approach: When clicked to show filters, filters are displayed", }); ``` -### :thumbsdown: Anti-Pattern Example: Mocking the reality with shallow rendering +### :thumbsdown: Ejemplo Anti Patrón: Mocking the reality with shallow rendering ```javascript test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { @@ -1165,7 +1164,7 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display ### :clap: Ejemplo de cómo hacerlo correctamente: E2E API that resolves only when the async operations is done (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") +![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Ejemplos con react-testing-library") ```javascript // using Cypress @@ -1191,7 +1190,7 @@ test("movie title appears", async () => { }); ``` -### :thumbsdown: Anti-Pattern Example: custom sleep code +### :thumbsdown: Ejemplo Anti Patrón: custom sleep code ```javascript test("movie title appears", async () => { @@ -1217,7 +1216,7 @@ test("movie title appears", async () => { ## ⚪ ️ 3.5 Watch how the content is served over the network -![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Ejemplos con Lighthouse") ✅ **Haz:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 @@ -1253,7 +1252,7 @@ test("movie title appears", async () => { ### :clap: Ejemplo de cómo hacerlo correctamente: 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") +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Ejemplos con React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Ejemplos con react-testing-library") ```javascript // unit under test @@ -1441,7 +1440,7 @@ Feature: Twitter new tweet
    -### :thumbsdown: Anti Pattern Example: A typical visual regression - right content that is served badly +### :thumbsdown: Ejemplo Anti Patrón: A typical visual regression - right content that is served badly ![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") @@ -1552,7 +1551,7 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :thumbsdown: Anti-Pattern Example: What’s wrong with this coverage report? +### :thumbsdown: Ejemplo Anti Patrón: 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) @@ -1583,7 +1582,7 @@ Knowing that all or most of the mutations were killed gives much higher confiden
    -### :thumbsdown: Anti Pattern Example: 100% coverage, 0% testing +### :thumbsdown: Ejemplo Anti Patrón: 100% coverage, 0% testing ![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") @@ -1624,7 +1623,7 @@ it("Test addNewOrder, don't use such test names", () => {
    -### :thumbsdown: Anti Pattern Example: A test case full of errors, luckily all are caught by Linters +### :thumbsdown: Ejemplo Anti Patrón: A test case full of errors, luckily all are caught by Linters ```javascript describe("Too short description", () => { @@ -1661,7 +1660,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    -### :thumbsdown: Anti Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug +### :thumbsdown: Ejemplo Anti Patrón: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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") From 26eb3e9a1e492b897c5c7e19f668a4290be6aa37 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 13:13:41 +0200 Subject: [PATCH 225/502] translated up to 1.8 --- readme-es.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/readme-es.md b/readme-es.md index 0788c29c..1688dffc 100644 --- a/readme-es.md +++ b/readme-es.md @@ -12,7 +12,7 @@ Esta es una guia completa para Javascript y Node de la A a la Z. Resumen y selec ## 🚢 Advanzado: Va 10.000 kilometros más allá de lo basico -Súbete a un viaje que va más allá de lo básico, llegando a temas avanzados como pruebas en producción, mutation testing, property-based testing y muchas otras herramientas estratégicas y profesionales. Si lees esta guía completamente es probable que tus habilidades de testing este por encima de la media +Súbete a un viaje que va más allá de lo básico, llegando a temas avanzados como testeando en producción, mutation testing, property-based testing y muchas otras herramientas estratégicas y profesionales. Si lees esta guía completamente es probable que tus habilidades de testing este por encima de la media ## 🌐 Full-stack: front, backend, CI, cualquier cosa @@ -94,7 +94,7 @@ La mayoría de los siguientes consejos son derivados de este principio. ## ⚪ ️ 1.1 Incluye 3 partes en los nombres de tus test -:white_check_mark: **Haz:** El reporte de un test debe indicar si la revisión de la aplicación actual cumple los requisitos para las personas que no están necesariamente familiarizadas con el código: el tester, el DevOps que está desplegangolo y el futuro tú de dentro de dos años. Esto se puede lograr si las pruebas hablan al nivel de los requisitos e incluyen 3 partes: +:white_check_mark: **Haz:** El reporte de un test debe indicar si la revisión de la aplicación actual cumple los requisitos para las personas que no están necesariamente familiarizadas con el código: el tester, el DevOps que está desplegangolo y el futuro tú de dentro de dos años. Esto se puede lograr si los test hablan al nivel de los requisitos e incluyen 3 partes: (1) ¿Qué se está testeando? Por ejemplo, el método ProductsService.addNewProduct @@ -150,7 +150,7 @@ describe('Products Service', function() { ## ⚪ ️ 1.2 Estructura tus test con el patron AAA -:white_check_mark: **Haz:** Estructura tus test con 3 secciones bien separadas Arreglar u organizar, Actuar y Afirmar (AAA en inglés Arrange, Act & Assert). Seguir esta estructura garantiza que quien lea nuestro test no gaste CPU-cerebro en comprender el plan de prueba: +:white_check_mark: **Haz:** Estructura tus test con 3 secciones bien separadas Arreglar u organizar, Actuar y Afirmar (AAA en inglés Arrange, Act & Assert). Seguir esta estructura garantiza que quien lea nuestro test no gaste CPU-cerebro en comprender los test: 1st A - Arreglar: configura el código acercarte al escenario que el test pretende simular. Esto podría incluir crear instancias de la unidad bajo el constructor del test, agregar registros de base de datos, mocks y stubs de objetos y cualquier otro código necesario @@ -267,7 +267,7 @@ it("When asking for an admin, ensure only ordered admins in results", () => { ## ⚪ ️ 1.4 Acercarse al testing caja-negra: Testea solo metodos publicos -:white_check_mark: **Haz:** Testear las partes internas suele traer un gasto extra enorme para muy poco beneficio. Si tu código/API comprueba todo los resultado correctamente, ¿deberias perder las proximas 3 horas en comprobar como esta funcionando internamente y despues mantener esos test tan fragiles? Cada vez que se comprueba un comportamiento publico, la implementacion privada es implicitamente testeada y tus test se romperan solo si hay un problema concreto (por ejemplo una salida incorrecta). Este enfoque tambien es conocido como `behavioral testing` (testing de comportamiento). Por otro lado, si se prueban las partes internas (caja blanca) tu enfoque cambia de planificar la salida del componente a detalles minusculos, y tus test pueden romperse debido a refactors de código menores sin que se rompan los test de salida, por lo que aumenta tremendamente el mantenimiento de los mismos.
    +:white_check_mark: **Haz:** Testear las partes internas suele traer un gasto extra enorme para muy poco beneficio. Si tu código/API comprueba todo los resultado correctamente, ¿deberias perder las proximas 3 horas en comprobar como esta funcionando internamente y despues mantener esos test tan fragiles? Cada vez que se comprueba un comportamiento publico, la implementacion privada es implicitamente testeada y tus test se romperan solo si hay un problema concreto (por ejemplo una salida incorrecta). Este enfoque tambien es conocido como `behavioral testing` (testing de comportamiento). Por otro lado, si se testean las partes internas (caja blanca) tu enfoque cambia de planificar la salida del componente a detalles minusculos, y tus test pueden romperse debido a refactors de código menores sin que se rompan los test de salida, por lo que aumenta tremendamente el mantenimiento de los mismos.
    ❌ **De lo contrario:** Tus test se comportaran como la fabula [que viene el lobo](https://es.wikipedia.org/wiki/El_pastor_mentiroso): gritando falsos positivos (por ejemplo, un test dalla por que se cambio el nombre a una variable provada). Como es de esperar, la gente empezara a ingnorar estos test hasta que un día ignoren un test de verdad... @@ -315,7 +315,7 @@ Antes de usar un doble, hazte una simple pregunta: ¿Lo voy a usar para testear Por ejemplo, si quieres testear que tu app se comporta razonablemente cuando el servicio de pagos está caido, deberias hacer un stub del servicio de pagos y devolver un 'Sin respuesta' para asegurar que la unidad que está siendo testeada devuelve el valor correcto. Esto verifica que el comportamiento/respuesta/resultado de nuesta app en ciertos escenarios. Tambien podrias usar un spy para asegurar que un email ha sifo enviado cuando este servicio está caidom — esto es nuevamente una verificacion de comportamiento que probablemente aparezca en el documento de requisitos ("Enviar un correo electrónico si no se pudo guardar el pago"). En el lado opuesto, si se mockea el servicio de pagos y se asegura que se haya llamado con el tipado correcto — entonces tu test esta comprobando cosas internas que tienen nada que ver con la funcionalidad de la app y es muy probable que cambien con frecuencia
    -❌ **De lo contrario:** Cualquier refactor de codigo te exigirá buscar todos los mocks que tengas y tendras que actualizarlos en consecuencia. Las pruebas pasan de ser un amigo útil a una carga más +❌ **De lo contrario:** Cualquier refactor de codigo te exigirá buscar todos los mocks que tengas y tendras que actualizarlos en consecuencia. Los test pasan de ser un amigo útil a una carga más
    @@ -367,7 +367,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 No uses “foo”, usa datos realistas -:white_check_mark: **Haz:** A menudo, los errores de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar errores temprano. Utiliza bibliotecas dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de prodcucción. Por ejemplo, dichas bibliotecas pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para estirar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Vea la próxima sección (pruebas basadas en propiedades). +:white_check_mark: **Haz:** A menudo, los errores de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar errores temprano. Utiliza bibliotecas dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de prodcucción. Por ejemplo, dichas bibliotecas pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para estirar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Vea la próxima sección (test basados en propiedades).
    ❌ **De lo contrario:** Todo tus test de desarrollo estaran en falsos verdes cuando uses datos sinteticos como "Foo", pero luego en produccion pueden ponerse en rojo cuando un hacker use cadenas extrañas como “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” @@ -419,12 +419,12 @@ it("Better: When adding new valid product, get successful confirmation", async (

    -## ⚪ ️ 1.7 Test many input combinations using Property-based testing +## ⚪ ️ 1.7 Testea muchas combinaciones de entrada utilizando test basados en propiedades -:white_check_mark: **Haz:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +:white_check_mark: **Haz:** Por lo general elegimos pocos datos de entrada por cada test. Incluso cuando el formato de entrada se parece a datos reales (ver sección "no uses foo"), cubrimos solo unas pocas combinaciones de datos de entrada (method(‘’, true, 1), method(“string” , false” , 0)), Sin embargo, en producción, un API que es llamada con 5 parametros puede ser invocada por miles de permutaciones diferentes, una sola puede hacer que nuestro poceso falle ([ver Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). ¿Qué tal si pudieras escribir un solo test que envíe 1000 permutaciones de diferentes entradas automáticamente y capture qué entrada hace que código no devuelva la respuesta correcta? Los test basados en propiedades son una técnica que hace exactamente eso: al enviar todas las combinaciones de entrada posibles a la unidad que está siendo testada, aumenta la probabilidad de encontrar un error. Por ejemplo, dado un metodo — addNewProduct(id, name, isDiscount) — las librerias compatibles llamaran a ese metodo con muchas combinaciones (numeros, textos y boleanos) como (1, “iPhone”, false), (2, “Galaxy”, true). Puedes ejecutar test basados en propiedades usando nuestra libreria de esta favorita (Mocha, Jest, etc) como [js-verify](https://github.com/jsverify/jsverify) o [testcheck](https://github.com/leebyron/testcheck-js) (mucho mejor documentada). Actualizado: Nicolas Dubien sugiere en los comentarios [checkout fast-check](https://github.com/dubzzz/fast-check#readme) que parece ofecer caracteristicas adicionales y es activamente mantenida.
    -❌ **De lo contrario:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs +❌ **De lo contrario:** Inconscientemente, eliges los datos de entradas para tus test que cubren solo las ramas de código que funcionan bien. Desafortunadamente, esto disminuye la eficiencia de los test como vehículo para detectar errores.
    @@ -432,7 +432,7 @@ it("Better: When adding new valid product, get successful confirmation", async (
    -### :clap: Ejemplo de cómo hacerlo correctamente: Testing many input permutations with “fast-check” +### :clap: Ejemplo de cómo hacerlo correctamente: Testear muchos datos de entrada permutados con “fast-check” ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") @@ -441,7 +441,7 @@ import fc from "fast-check"; describe("Product service", () => { describe("Adding new", () => { - //this will run 100 times with different random properties + //esto ejecutara 100 veces con diferentes prodiedades al azar it("Add new product with random yet valid properties, always successful", () => fc.assert( fc.property(fc.integer(), fc.string(), (id, name) => { @@ -456,16 +456,16 @@ describe("Product service", () => {

    -## ⚪ ️ 1.8 If needed, use only short & inline snapshots +## ⚪ ️ 1.8 Si lo necesitas, usa solo snapshots cortos y en el propio test -:white_check_mark: **Haz:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. +:white_check_mark: **Haz:** Cuando hay necesidad de usar [snapshot testing](https://jestjs.io/docs/es-ES/snapshot-testing), usa solo snapshots cortos y bien enfocados (por ejemplo 3-7 lineas) y que esten incluidos en el propio test ([Inline Snapshot](https://jestjs.io/docs/es-ES/snapshot-testing#inline-snapshots)) y no como ficheros externos. Mantener esta dirección te garantiza que tus test se explican por si mismos y a la vez que sean menos fragiles. -On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much +Por otro lado, los tutoriales y herramientas basados en ‘classic snapshots’ tienden a guardar ficheros muy grandes en medios externos (por ejemplo component rendering markup, API JSON result) cada vez que se ejecutan los test para comparar los resultados recividos con la version guardada. Esto, por ejemplo, puede aosciar nuestro test a 1000 lineas con 3000 valores de datoa que quien este escribiendo test jamas leera ni razonará. ¿Por qué está mal esto? Al hacerlo, hay 1000 razones para que tu test falle - tan solo el cambio de una linea de código es suficiente para que el snapshoot se invalide y es muy probable que esto ocurra a menudo. ¿Como de frecuente? cada espacio, comentatio o pequeño cambio de css/html. Y no solo eso, el nombre del test no nos va a dar ni una sola pista de que está fallando, solo verifica que esas 1000 lineas han cambiado. Además obliga a quien escribe los test a asumir como correcto un fichero enorme que no ha podido inspeccionar y corroborar. Todo estos son sintomas de una prueba oscura que no está bien enfocada y trata de cubrir demasiadas cosas a la vez -It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +Vale la pena señalar que hay algunos casos en los que los snapshoots grandes y externos son buenos - cuando comporbamos el esquema y no los datos (ignorando los valores y centrandonos en los campos) o en los casos en el que el documento no va a cambiar apenas en el tiempo.
    -❌ **De lo contrario:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **De lo contrario:** Un test UI falla. El codigo parece correcto, la pantalla esta pintando todos los pixels correctamente, ¿que ha pasado? tu test de snapshoot ha encontrado una diferencia entre el origen y lo que ha recibido al ejecutarse: simplemente hay un espacio añadido en cualquier parte...
    @@ -473,40 +473,40 @@ It’s worth noting that there are few cases where long & external snapshots are
    -### :thumbsdown: Ejemplo Anti Patrón: Coupling our test to unseen 2000 lines of code +### :thumbsdown: Ejemplo Anti Patrón: Acoplando nuestro test a un fichero no revisado de 2000 lineas de código ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript it("TestJavaScript.com is renderd correctly", () => { - //Arrange + //Arreglar - //Act + //Actuar const receivedPage = renderer .create( Test JavaScript ) .toJSON(); - //Assert + //Afirmar expect(receivedPage).toMatchSnapshot(); - //We now implicitly maintain a 2000 lines long document - //every additional line break or comment - will break this test + //Ahora nosotros implicitamente mantenemos un fichero de 2000 lineas + //cada salto de linea o comentario añadido van a romper nuestro test }); ```
    -### :clap: Ejemplo de cómo hacerlo correctamente: Expectations are visible and focused +### :clap: Ejemplo de cómo hacerlo correctamente: Lo experado es visible y esta enfocado ```javascript it("When visiting TestJavaScript.com home page, a menu is displayed", () => { - //Arrange + //Arreglar - //Act + //Actuar const receivedPage = renderer .create( Test JavaScript ) .toJSON(); - //Assert + //Afirmar const menu = receivedPage.content.menu; expect(menu).toMatchInlineSnapshot(` From 7ab1c51112c3a7aaa66a58c372c752dad5400f46 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 13:39:44 +0200 Subject: [PATCH 226/502] translated up to 1.9 --- readme-es.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/readme-es.md b/readme-es.md index 1688dffc..4281a3f4 100644 --- a/readme-es.md +++ b/readme-es.md @@ -523,12 +523,13 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {

    -## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test +## ⚪ ️1.9 Evitar fixtures globales y seeds, añade datos por cada test + +:white_check_mark: **Haz:** Siguiendo la regla de oro (sección 0), cada test debe añadir y actuar en su propio conjunto de filas DB para evitar el acoplamiento y poder razonar fácilmente sobre el flujo del test. En realidad esto es a menudo ignorado por los desarroladores de test que siembran la DB con datos antes de ejecutar las pruebas ([tambien conocido como ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) a favor de mejorar el rendimiento. Si bien el rendimiento es una preocupación válida — puede mitigarse de otras formas (consulte la sección "test de componentes"), sin embargo, la complegidad del test es más dolorosa que otras consideraciones la mayoria de las veces. De manera practica, haga que cada test añada explicitamente los registros que necesitas y actua sobre ellos. Si el rendimiento se convierte en algo critico — se puede llegar al compromiso de utilizar los mismos datos en un conjunto de test siempre que no se muten los datos (por ejemplo en queries). -:white_check_mark: **Haz:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries)
    -❌ **De lo contrario:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **De lo contrario:** FMuchos test fallaran, un despliegue se aborta, nuestro equipo perdera mucho tiempo, ¿tenemos un bug? vamos a investigar, ah no — parece que dos test estan mutando el mismo dato
    @@ -536,36 +537,36 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {
    -### :thumbsdown: Ejemplo Anti Patrón: tests are not independent and rely on some global hook to feed global DB data +### :thumbsdown: Ejemplo Anti Patrón: tests no son independientes y dependenden de una inserción global de datos en la DB ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha") ```javascript before(async () => { - //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + //añadiendo datos de sites y admin a nuestra DB. ¿Donde estan los datos? fuera. En un json extermo o en un modelo da migración 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 + //Se que el nombre de site "portal" existe, — lo he visto en seed.json 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 + //Se que el nombre de site "portal" existe, — lo he visto en seed.json const siteToCheck = await SiteService.getSiteByName("Portal"); - expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ + expect(siteToCheck.name).to.be.equal("Portal"); //Fallo! El test anterior ha cambiado el nombre :[ }); ```
    -### :clap: Ejemplo de cómo hacerlo correctamente: We can stay within the test, each test acts on its own set of data +### :clap: Ejemplo de cómo hacerlo correctamente: Podemos permanecer dentro de nuestro test, cada test actua sobre su propio conjunto de datos ```javascript it("When updating site name, get successful confirmation", async () => { - //test is adding a fresh new records and acting on the records only + //el test esta añadiendo registors nuevos cada vez y actuando solo en esos registros const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); From 294c49c10b1028a9ac114707187f2c2579677608 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 13:51:48 +0200 Subject: [PATCH 227/502] translated up to 1.10 --- readme-es.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/readme-es.md b/readme-es.md index 4281a3f4..111c771d 100644 --- a/readme-es.md +++ b/readme-es.md @@ -581,14 +581,15 @@ it("When updating site name, get successful confirmation", async () => {
    -## ⚪ ️ 1.10 Don’t catch errors, expect them +## ⚪ ️ 1.10 No capures errores, esperalos -:white_check_mark: **Haz:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations +:white_check_mark: **Haz:** Cuando queremos comprobar que una entrada lanza un error, nos puede parecer correcto usar try-catch-finally y afirmar que se entra por el catch. El resultado es un test incomodo y verboso (ejemplo a continuación) que nos oculta la intencion de un test muy simple y las expectativas del resultado + +Una alternativa más elegante seria usar solo la aserción de una sola linea que tiene Chai: expect(method).to.throw (o en Jest: expect(method).toThrow()). Es totalmente obligatorio también asegurarse de que la excepción contenga una propiedad que indique el tipo de error, de lo contratio, lanzando solo un error generico, la app no podra hacer mucho mas que mostrarle un error decepcionante para el usuario -A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user
    -❌ **De lo contrario:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +❌ **De lo contrario:** Sería muy dificil deducir a partir de los informes de test (por ejemplo, informe de CI) que es lo que ha salido mal
    @@ -596,7 +597,7 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e
    -### :thumbsdown: Ejemplo Anti Patrón: A long test case that tries to assert the existence of error with try-catch +### :thumbsdown: Ejemplo Anti Patrón: Un test largo que trata de afirmar la existencia de un error con try-catch ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha") @@ -610,14 +611,14 @@ it("When no product name, it throws error 400", async () => { 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 + //si esta afirmación falla, el resultado/reporte del testsolo mostrará + //que algunos valores es null, no se monstrara que falta una excepción }); ```
    -### :clap: Ejemplo de cómo hacerlo correctamente: A human-readable expectation that could be understood easily, maybe even by QA or technical PM +### :clap: Ejemplo de cómo hacerlo correctamente: Una afirmacion legible para una persona puede ser comprendida facilmente, tanto por el QA como por el PM ```javascript it("When no product name, it throws error 400", async () => { From 698b07c582398be4be58c999767956c2a6bb35c3 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 14:50:07 +0200 Subject: [PATCH 228/502] translated up to 1.11 --- readme-es.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/readme-es.md b/readme-es.md index 111c771d..3c10188a 100644 --- a/readme-es.md +++ b/readme-es.md @@ -632,12 +632,13 @@ it("When no product name, it throws error 400", async () => {

    -## ⚪ ️ 1.11 Tag your tests +## ⚪ ️ 1.11 Tagea tus test + +:white_check_mark: **Haz:** Deben ejecutarse diferentes tests en diferentes escenarios: quick smoke, IO-less, los tests deben ejecutarse cuando un desarrollador guarda o hace commit de un fichero, los test end-to-end suelen ejecutarse cuando un nuevo pull request es añadido, etc. Esto se puede lograr etiquetando los test con tags como #cold #api #sanity para que se pueda filtrar e invocar solo el subconjunto deseado. Por ejemplo, así es como se ejecutan solo el grupo sanity test con Mocha: mocha — grep ‘sanity’ -:white_check_mark: **Haz:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’
    -❌ **De lo contrario:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests +❌ **De lo contrario:** Ejecutar todos los test, incluidos los test que realizan docenas de queries a DB, cada vez que un desarrollador hace un pequeño cambio, puede ser extremadamente lento y provocar que los desarrolladores ignoren correr los test.
    @@ -645,17 +646,17 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) +### :clap: Ejemplo de cómo hacerlo correctamente: Tagear los test como ‘#cold-test’ permite que el test runner ejecute solo los test más rápidos (Cold===tests rapidos que no estan haciendo operaciones de IO y que pueden ser ejecutados con frecuencia incluso mientras el desarrollador está escribiendo) ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript -//this test is fast (no DB) and we're tagging it correspondigly -//now the user/CI can run it frequently +//este test es rapido (sin DB) y lo estamos tageando correctamente +//ahora el usuario/CI puede ejecutarlo con frecuencia 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 + //logica aquí }); }); }); From 28e463592b373d1b0f4451cfbbd9e0c3d9d8d21d Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 15:07:48 +0200 Subject: [PATCH 229/502] translated up to 1.12 --- readme-es.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/readme-es.md b/readme-es.md index 3c10188a..4654a3ba 100644 --- a/readme-es.md +++ b/readme-es.md @@ -666,13 +666,13 @@ describe("Order service", function() {

    -## ⚪ ️ 1.12 Categorize tests under at least 2 levels +## ⚪ ️ 1.12 Categoriza los test en al menos 2 niveles -:white_check_mark: **Haz:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and prtscn below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **Haz:** Aplique cierta estructura a su conjunto de test para que un visitante ocasional pueda comprender facilmente los requisitos (los test siempre son la mejor documentación) y los diversos escenarios que estamos testeando. Una practica comun para esto es crear al menos 2 bloques 'describe' antes de tus test: el primero es para el nombre de la unidad que está siendo testeada y el segundo es para un nivel adicional de categorización como el escenario o las categorias personalizadas (ver ejemplos de código y pantallazos más abajo). Hacerlo también mejorará los reportes de los test: quien los lea deducirá facilmente las categorias de los test, profundizará en aquellas que lo desee y podrá relacionar mejor los test fallidos. Además, será mucho más fácil para un desarollador navegar a traves del codigo de un cojunto de test amplio. Existen múltiples formas de estructurar tus test que deben ser consideradas, como [given-when-then](https://github.com/searls/jasmine-given) y [RITE](https://github.com/ericelliott/riteway)
    -❌ **De lo contrario:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in an hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause +❌ **De lo contrario:** Cuando nos enfrentamos a un reporte con una lista plana de test, tendremos que leer rápidamente textos largos para determinar los escenarios principales y relacionar los test fallidos. Considera el siguiente caso: Cuando 7/100 test fallan, revisar una lista plana te exige leer el tesxto de las pruebas que fallan para ver como se relacionan entre ellas y que tienen en común. Sin embargo, en un informe jerarquizado, si los 7 estan bajo un mismo flujo o categoria, puedes saber rápidamente cual o donde puede estar la causa raiz del fallo
    @@ -680,19 +680,19 @@ describe("Order service", function() {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below +### :clap: Ejemplo de cómo hacerlo correctamente: Estructurando un conjunto de test con el nombre de la unidad testada y los escenarios nos conducirá al reporte que se muestra a continuación ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript -// Unit under test +// Unida que se está testeando describe("Transfer service", () => { - //Scenario + //Escenario describe("When no credit", () => { - //Expectation + //Esperado test("Then the response status should decline", () => {}); - //Expectation + //Esperado test("Then it should send email to admin", () => {}); }); }); @@ -702,7 +702,7 @@ describe("Transfer service", () => {
    -### :thumbsdown: Ejemplo Anti Patrón: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests +### :thumbsdown: Ejemplo Anti Patrón: Una lista plana de test hará mas dificil a quien lo lea el identificat las historias de usuario y detectar los test que estan fallando ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Mocha") From 7f3f1440ca920c8b3f6f31ea0e2204f20d2b288f Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 15:30:37 +0200 Subject: [PATCH 230/502] translated Section 1 --- readme-es.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme-es.md b/readme-es.md index 4654a3ba..1cbb8ac2 100644 --- a/readme-es.md +++ b/readme-es.md @@ -722,14 +722,14 @@ test("Then there should not be a new transfer record", () => {});

    -## ⚪ ️1.13 Other generic good testing hygiene +## ⚪ ️1.13 Otras buenas practicas genéricas sobre higiene de los test -:white_check_mark: **Haz:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +:white_check_mark: **Haz:** Esta publicación se centra en consejos de test relacionados con, o al menos, que se pueden ejemplificar en Node JS. Sin embargo, esta sección agrupa algunos consejos no relacionados con Node que son bien conocidos -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satsifies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +Aprenda y practique [principios TDD](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — son extremadamente valiosos para muchos pero no te dejes intimidar si no se ajustan a tu estilo, no eres el único. Considera escribir los test antes que el código con el [estilo rojo-verde-refactor](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), te asegura que cada test chequea exactamente una cosa, cuando encuentras un bug — antes de corregirlo escribe un test que lo detecte como bug en el futuro, dejando que cada test falle al menos una vez antes de convertirlo en un verde, comienza el inicio de un modulo escribieno código muy simple y rapodamente, que satisfaga el test, luego lo refatorizamos gradualmente hasta que nuestro código tenga el nivel deseado en producción, evitando siempre cualquier dependencia con el entorno (rutas en disco, sistema operativo, etc)
    -❌ **De lo contrario:** You‘ll miss pearls of wisdom that were collected for decades +❌ **De lo contrario:** Echarás de menos las perlas de sabiduría que se han ido recolectando durante décadas

    From 8b7b61ed16e042179e4ebe682820304e1441c646 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 20:06:28 +0200 Subject: [PATCH 231/502] translated up to 2.1 --- readme-es.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/readme-es.md b/readme-es.md index 1cbb8ac2..83583186 100644 --- a/readme-es.md +++ b/readme-es.md @@ -737,17 +737,17 @@ Aprenda y practique [principios TDD](https://www.sm-cloud.com/book-review-test-d ## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid -:white_check_mark: **Haz:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **Haz:** La [pirámide de test](https://martinfowler.com/bliki/TestPyramid.html), con 10> años de antiguedad, es un modelo excelente y relevante que sugiere tres tipos de test e influye en la estrategia de testeo de la mayoría de los desarrolladores. Al mismo tiempo, surgieron un puñado de nuevas y brillantes técnicas de testeo que se esconden en las sombras de la pirámide de test. Dados todos los cambios que hemos visto en los últimos 10 años (Microservicios, cloud, serverless), ¿es posible que un modelo algo antiguo se adapte a *todos* los tipos de aplicaciones? ¿No debería el mundo del testing considerar aceptar nuevas técnicas? -Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IOT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. +No me malinterpretes, en 2019 la pirámide de test, el TDD y los test unitarios siguen siendo una técnica buena y probablemente sean la mejor combinación para muchas aplicaciones. Solo como cualquier otro modelo, a pesar de su utilidad, [a veces debe estar equivocado] (https://en.wikipedia.org/wiki/All_models_are_wrong). Por ejemplo, considere una aplicación IOT que ingiere muchos eventos en un bus de mensajes como Kafka / RabbitMQ, que luego fluyen a algún data-warehouse y finalmente son consultados por alguna UI de análisis. ¿Realmente deberíamos gastar el 50% de nuestro presupuesto para tests en escribir tests unitarios para una aplicación que esté centrada en la integración y apenas tenga lógica? A medida que aumenta la diversidad de tipos de aplicaciones (bots, criptografía, Alexa-skills), aumentan las posibilidades de encontrar escenarios en los que la pirámide de test no sea la mejor opción. -It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks +Es hora de enriquecer el abanico de tests y familiarizarse con más tipos de tests (las siguientes secciones sugieren algunas ideas), modelos como la pirámide de test, pero también hacer coincidir los tipos de test con los problemas del mundo real al que te enfrentas ('Hola, nuestra API está rota, ¡escribamos contract testing dirigidos al consumidor!'), diversifica tus test como un inversor que construye una cartera de inversión basada en el análisis de riesgos — evalúa dónde pueden surgir problemas y combina algunas medidas de prevención para mitigar esos riesgos potenciales -A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] +Una advertencia: el TDD en el mundo del software adopta una cara de falsa dicotomía, algunos predican que debemos usarlo en todas partes, otros piensan que es el diablo. Todos los que hablan en absoluto están equivocados:]
    -❌ **De lo contrario:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes +❌ **De lo contrario:** Te perderás algunas herramientas con un ROI increíble, algunas como Fuzz, lint y mutation pueden proporcionar valor en 10 minutos
    @@ -755,15 +755,15 @@ A word of caution: the TDD argument in the software world takes a typical false-
    -### :clap: Ejemplo de cómo hacerlo correctamente: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the same way’ +### :clap: Ejemplo de cómo hacerlo correctamente: Cindy Sridharan sugiere un portfolio de test amplio en su increíble publicación ‘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’") +![alt text](assets/bp-12-rich-testing.jpeg "Cindy Sridharan sugiere un portfolio de test amplio en su increíble publicación ‘Testing Microservices — the same 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) +☺️Ejemplo: [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") +![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "Un nombre de test que consta de 3 partes")
    From 4e2a218232ab786a1e61fca1f3bf0deb220b3517 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 20:45:52 +0200 Subject: [PATCH 232/502] translated up to 2.2 --- readme-es.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/readme-es.md b/readme-es.md index 83583186..1736196d 100644 --- a/readme-es.md +++ b/readme-es.md @@ -771,12 +771,15 @@ Una advertencia: el TDD en el mundo del software adopta una cara de falsa dicoto ## ⚪ ️2.2 Component testing might be your best affair -:white_check_mark: **Haz:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +:white_check_mark: **Haz:** + +Cada test unitario cubre una pequeña parte de la aplicación y cubrirla totalmente cuesta muchisimo, mientras que los test end-to-end cubren facilmente mucho terreno, pero son costosas y más lentas, ¿por qué no aplicar un enfoque equilibrado y escribir test más grandes que test unitarios pero más pequeñas que las pruebas end-to-end? Los test de componente es la canción no cantada del mundo del testing — proporcionan lo mejor de ambos mundos: rendimiento razonable y la posibilidad de aplicar patrones TDD + cobertura realista. + +Los test de componente se centran en la 'unidad' de Microservicios, funcionan contra la API, no mockean nada que pertenezca al Microservico en sí (por ejemplo, DB real, o al menos la versión en memoria de esa DB) pero hace stub de cualquier cosa que sea externa como llamadas a otros microservicios. Al hacerlo, probamos lo que desplegamos, nos acercamos a la aplicación de fuera a dentro y obtenemos una gran confianza en un período de tiempo razonable. -Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time.
    -❌ **De lo contrario:** You may spend long days on writing unit tests to find out that you got only 20% system coverage +❌ **De lo contrario:** Puedes pasar muchos días escribiendo test unitarios para descubrir que solo tiene un 20% de cobertura del sistema
    @@ -784,7 +787,8 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap: Ejemplo de cómo hacerlo correctamente: Supertest allows approaching Express API in-process (fast and cover many layers) +### :clap: Ejemplo de cómo hacerlo correctamente: Supertest permite acercarse al API Express in-process (rápido y cubre muchas capas) + ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha") From 43daf5768706ebae8a20d60eecbd8b8b966adc9c Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 21:46:00 +0200 Subject: [PATCH 233/502] translated up to 2.3 --- readme-es.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/readme-es.md b/readme-es.md index 1736196d..5fbf0ea1 100644 --- a/readme-es.md +++ b/readme-es.md @@ -792,7 +792,7 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con 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)") +![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) permite acercarse al API Express in-process (rápido y cubre muchas capas)")
    @@ -800,10 +800,11 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co ## ⚪ ️2.3 Ensure new releases don’t break the API using -:white_check_mark: **Haz:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +:white_check_mark: **Haz:** Pongamos que tu Microservicio tiene multiples consumidores, y tenemos en ejecucion 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 ninguna prueba porque el servidor controla las fechas de release. [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 las pruebas 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 compilación utilizando la biblioteca PACT para detectar contratos incumplidos — una expectativa de consumidor no cumplida. Al hacerlo, todos los desajustes de la API cliente-servidor se detectan muyt pronto durante la compilación / CI y pueden ahorrarte mucha frustración. +
    -❌ **De lo contrario:** The alternatives are exhausting manual testing or deployment fear +❌ **De lo contrario:** Las alternativas son pruebas manuales agotadoras o miedo al despliegue
    From 5cdb835c2c9986a36a1863c39eb5c17b6a38ceaf Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 27 Jul 2020 22:05:51 +0200 Subject: [PATCH 234/502] translated up to 2.4 --- readme-es.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/readme-es.md b/readme-es.md index 5fbf0ea1..b68f8bfe 100644 --- a/readme-es.md +++ b/readme-es.md @@ -367,7 +367,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 No uses “foo”, usa datos realistas -:white_check_mark: **Haz:** A menudo, los errores de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar errores temprano. Utiliza bibliotecas dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de prodcucción. Por ejemplo, dichas bibliotecas pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para estirar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Vea la próxima sección (test basados en propiedades). +:white_check_mark: **Haz:** A menudo, los errores de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar errores temprano. Utiliza librerias dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de prodcucción. Por ejemplo, dichas librerias pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para estirar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Vea la próxima sección (test basados en propiedades).
    ❌ **De lo contrario:** Todo tus test de desarrollo estaran en falsos verdes cuando uses datos sinteticos como "Foo", pero luego en produccion pueden ponerse en rojo cuando un hacker use cadenas extrañas como “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” @@ -800,7 +800,7 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co ## ⚪ ️2.3 Ensure new releases don’t break the API using -:white_check_mark: **Haz:** Pongamos que tu Microservicio tiene multiples consumidores, y tenemos en ejecucion 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 ninguna prueba porque el servidor controla las fechas de release. [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 las pruebas 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 compilación utilizando la biblioteca PACT para detectar contratos incumplidos — una expectativa de consumidor no cumplida. Al hacerlo, todos los desajustes de la API cliente-servidor se detectan muyt pronto durante la compilación / CI y pueden ahorrarte mucha frustración. +:white_check_mark: **Haz:** Pongamos que tu Microservicio tiene multiples consumidores, y tenemos en ejecucion 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 ninguna prueba porque el servidor controla las fechas de release. [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 las pruebas 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 compilación utilizando la libreria PACT para detectar contratos incumplidos — una expectativa de consumidor no cumplida. Al hacerlo, todos los desajustes de la API cliente-servidor se detectan muyt pronto durante la compilación / CI y pueden ahorrarte mucha frustración.
    @@ -824,10 +824,11 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co ## ⚪ ️ 2.4 Test your middlewares in isolation -:white_check_mark: **Haz:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +:white_check_mark: **Haz:** Muchos evitan los test de middleware porque representan una pequeña porción del sistema y requieren ejecutar un servidor Express. Ambas razones son incorrectas — los middlewares son pequeños pero afectan a todas o la mayoría de las solicitudes y pueden testearse fácilmente como funciones puras que obtienen {req, res} objetos JS. Para testear una función de middleware se debe invocar y usar spy ([usando Sinon, por ejemplo] (https://www.npmjs.com/package/sinon)) sobre la interacción con los objetos {req, res} para garantizar que nuestra función middleware realiz la acción correcta. La libreria [node-mock-http] (https://www.npmjs.com/package/node-mocks-http) lo lleva aún más lejos y factoriza los objetos {req, res} ademas de añadir el spy. Por ejemplo, puede afirmar si el estado http que se estableció en el objeto res coincide con el esperado (consulta el ejemplo a continuación) +
    -❌ **De lo contrario:** A bug in Express middleware === a bug in all or most requests +❌ **De lo contrario:** Un bug en un middleware de Express === un bug todas o casi todas las peticiones
    @@ -835,15 +836,15 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co
    -### :clap:Ejemplo de cómo hacerlo correctamente: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :clap:Ejemplo de cómo hacerlo correctamente: Probar un middleware de forma aislada sin emitir llamadas de red y evitar tener que levantar Express ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript -//the middleware we want to test +//ek middleware que queremos testear const unitUnderTest = require("./middleware"); const httpMocks = require("node-mocks-http"); -//Jest syntax, equivelant to describe() & it() in Mocha +//Sintaxis de Jest, equivalente a describe() e it() en Mocha test("A request without authentication header, should return http status 403", () => { const request = httpMocks.createRequest({ method: "GET", From 31086bb536500c9830fbb55a2ef4829dc83e8ea6 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Tue, 28 Jul 2020 00:51:05 +0200 Subject: [PATCH 235/502] translated up to 2.5 --- readme-es.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/readme-es.md b/readme-es.md index b68f8bfe..d125400e 100644 --- a/readme-es.md +++ b/readme-es.md @@ -367,7 +367,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 No uses “foo”, usa datos realistas -:white_check_mark: **Haz:** A menudo, los errores de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar errores temprano. Utiliza librerias dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de prodcucción. Por ejemplo, dichas librerias pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para estirar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Vea la próxima sección (test basados en propiedades). +:white_check_mark: **Haz:** A menudo, los bugs de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar bugs temprano. Utiliza librerias dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de prodcucción. Por ejemplo, dichas librerias pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para estirar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Vea la próxima sección (test basados en propiedades).
    ❌ **De lo contrario:** Todo tus test de desarrollo estaran en falsos verdes cuando uses datos sinteticos como "Foo", pero luego en produccion pueden ponerse en rojo cuando un hacker use cadenas extrañas como “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” @@ -411,7 +411,7 @@ it("Better: When adding new valid product, get successful confirmation", async ( //Datos de entrada generados aleatoreamente: {'Sleek Cotton Computer', 85481} expect(addProductResult).to.be.true; //El test falla, El valor de entrada random ha provocado que se vaya por un camino que nunca planeamos - //!Hemos descubierto un error muy pronto! + //!Hemos descubierto un bug muy pronto! }); ``` @@ -421,10 +421,10 @@ it("Better: When adding new valid product, get successful confirmation", async ( ## ⚪ ️ 1.7 Testea muchas combinaciones de entrada utilizando test basados en propiedades -:white_check_mark: **Haz:** Por lo general elegimos pocos datos de entrada por cada test. Incluso cuando el formato de entrada se parece a datos reales (ver sección "no uses foo"), cubrimos solo unas pocas combinaciones de datos de entrada (method(‘’, true, 1), method(“string” , false” , 0)), Sin embargo, en producción, un API que es llamada con 5 parametros puede ser invocada por miles de permutaciones diferentes, una sola puede hacer que nuestro poceso falle ([ver Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). ¿Qué tal si pudieras escribir un solo test que envíe 1000 permutaciones de diferentes entradas automáticamente y capture qué entrada hace que código no devuelva la respuesta correcta? Los test basados en propiedades son una técnica que hace exactamente eso: al enviar todas las combinaciones de entrada posibles a la unidad que está siendo testada, aumenta la probabilidad de encontrar un error. Por ejemplo, dado un metodo — addNewProduct(id, name, isDiscount) — las librerias compatibles llamaran a ese metodo con muchas combinaciones (numeros, textos y boleanos) como (1, “iPhone”, false), (2, “Galaxy”, true). Puedes ejecutar test basados en propiedades usando nuestra libreria de esta favorita (Mocha, Jest, etc) como [js-verify](https://github.com/jsverify/jsverify) o [testcheck](https://github.com/leebyron/testcheck-js) (mucho mejor documentada). Actualizado: Nicolas Dubien sugiere en los comentarios [checkout fast-check](https://github.com/dubzzz/fast-check#readme) que parece ofecer caracteristicas adicionales y es activamente mantenida. +:white_check_mark: **Haz:** Por lo general elegimos pocos datos de entrada por cada test. Incluso cuando el formato de entrada se parece a datos reales (ver sección "no uses foo"), cubrimos solo unas pocas combinaciones de datos de entrada (method(‘’, true, 1), method(“string” , false” , 0)), Sin embargo, en producción, un API que es llamada con 5 parametros puede ser invocada por miles de permutaciones diferentes, una sola puede hacer que nuestro poceso falle ([ver Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). ¿Qué tal si pudieras escribir un solo test que envíe 1000 permutaciones de diferentes entradas automáticamente y capture qué entrada hace que código no devuelva la respuesta correcta? Los test basados en propiedades son una técnica que hace exactamente eso: al enviar todas las combinaciones de entrada posibles a la unidad que está siendo testada, aumenta la probabilidad de encontrar un bug. Por ejemplo, dado un metodo — addNewProduct(id, name, isDiscount) — las librerias compatibles llamaran a ese metodo con muchas combinaciones (numeros, textos y boleanos) como (1, “iPhone”, false), (2, “Galaxy”, true). Puedes ejecutar test basados en propiedades usando nuestra libreria de esta favorita (Mocha, Jest, etc) como [js-verify](https://github.com/jsverify/jsverify) o [testcheck](https://github.com/leebyron/testcheck-js) (mucho mejor documentada). Actualizado: Nicolas Dubien sugiere en los comentarios [checkout fast-check](https://github.com/dubzzz/fast-check#readme) que parece ofecer caracteristicas adicionales y es activamente mantenida.
    -❌ **De lo contrario:** Inconscientemente, eliges los datos de entradas para tus test que cubren solo las ramas de código que funcionan bien. Desafortunadamente, esto disminuye la eficiencia de los test como vehículo para detectar errores. +❌ **De lo contrario:** Inconscientemente, eliges los datos de entradas para tus test que cubren solo las ramas de código que funcionan bien. Desafortunadamente, esto disminuye la eficiencia de los test como vehículo para detectar bugs.
    @@ -800,7 +800,7 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co ## ⚪ ️2.3 Ensure new releases don’t break the API using -:white_check_mark: **Haz:** Pongamos que tu Microservicio tiene multiples consumidores, y tenemos en ejecucion 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 ninguna prueba porque el servidor controla las fechas de release. [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 las pruebas 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 compilación utilizando la libreria PACT para detectar contratos incumplidos — una expectativa de consumidor no cumplida. Al hacerlo, todos los desajustes de la API cliente-servidor se detectan muyt pronto durante la compilación / CI y pueden ahorrarte mucha frustración. +:white_check_mark: **Haz:** Pongamos que tu Microservicio tiene multiples consumidores, y tenemos en ejecucion 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 ninguna prueba porque el servidor controla las fechas de release. [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 las pruebas 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 libreria PACT para detectar contratos incumplidos — una expectativa de consumidor no cumplida. Al hacerlo, todos los desajustes de la API cliente-servidor se detectan muyt pronto durante la construcción / CI y pueden ahorrarte mucha frustración.
    @@ -863,15 +863,15 @@ test("A request without authentication header, should return http status 403", (

    -## ⚪ ️2.5 Measure and refactor using static analysis tools +## ⚪ ️2.5 Mide y refactoriza utilizando herramientas de análisis estático -:white_check_mark: **Haz:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)) +:white_check_mark: **Haz:** El uso de herramientas de análisis estático ayuda proporcionando formas objetivas para mejorar la calidad del código y a tener el código mantenible. Puede agregar herramientas de análisis estático a su pipeline de CI para abortar cuando encuentre code smells. Sus principales beneficios sobre el linter plano son la habilidad de analizar la calidad en el contexto de multiples ficheros (por ejemplo encontrar duplicados), realizando analisis avanzados (por ejemplo complegidad del código) y siguiendo el historial y el progreso de cada problema. Dos ejemplos de herramientas que puedes usar son [Sonarqube](https://www.sonarqube.org/) (2,600+ [estrellas](https://github.com/SonarSource/sonarqube)) y [Code Climate](https://codeclimate.com/) (1,500+ [estrellas](https://github.com/codeclimate/codeclimate)) -Credit:
    [Keith Holliday](https://github.com/TheHollidayInn) +Crédito: [Keith Holliday](https://github.com/TheHollidayInn)
    -❌ **De lo contrario:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix +❌ **De lo contrario:** Con una mala calidad de código, los bugs y el rendimiento siempre serán un problema que ninguna libreria completamente nueva o características punteras van a poder solucionar
    @@ -879,11 +879,11 @@ Credit: From 631c9b5af8598932096f97c61387d20c9a81a9a3 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Tue, 28 Jul 2020 17:47:42 +0200 Subject: [PATCH 236/502] translated up to 2.6 --- readme-es.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-es.md b/readme-es.md index d125400e..34fb4030 100644 --- a/readme-es.md +++ b/readme-es.md @@ -889,12 +889,12 @@ Crédito: From 47b2b64bc5910e09beaed082d7986e482edb1406 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Tue, 28 Jul 2020 18:26:56 +0200 Subject: [PATCH 237/502] translated up to 2.7, whole section 2 transalted --- readme-es.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/readme-es.md b/readme-es.md index 34fb4030..fdeeca4f 100644 --- a/readme-es.md +++ b/readme-es.md @@ -525,7 +525,7 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => { ## ⚪ ️1.9 Evitar fixtures globales y seeds, añade datos por cada test -:white_check_mark: **Haz:** Siguiendo la regla de oro (sección 0), cada test debe añadir y actuar en su propio conjunto de filas DB para evitar el acoplamiento y poder razonar fácilmente sobre el flujo del test. En realidad esto es a menudo ignorado por los desarroladores de test que siembran la DB con datos antes de ejecutar las pruebas ([tambien conocido como ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) a favor de mejorar el rendimiento. Si bien el rendimiento es una preocupación válida — puede mitigarse de otras formas (consulte la sección "test de componentes"), sin embargo, la complegidad del test es más dolorosa que otras consideraciones la mayoria de las veces. De manera practica, haga que cada test añada explicitamente los registros que necesitas y actua sobre ellos. Si el rendimiento se convierte en algo critico — se puede llegar al compromiso de utilizar los mismos datos en un conjunto de test siempre que no se muten los datos (por ejemplo en queries). +:white_check_mark: **Haz:** Siguiendo la regla de oro (sección 0), cada test debe añadir y actuar en su propio conjunto de filas DB para evitar el acoplamiento y poder explicar fácilmente sobre el flujo del test. En realidad muchos testers se saltan esta regla al añadir datos a la DB solo una vez antes de ejecutar los test ([tambien conocido como ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) a favor de mejorar el rendimiento. Si bien el rendimiento es una preocupación válida — puede mitigarse de otras formas (consulte la sección "test de componentes"), sin embargo, la complegidad del test es más dolorosa que otras consideraciones la mayoria de las veces. De manera practica, haga que cada test añada explicitamente los registros que necesitas y actua sobre ellos. Si el rendimiento se convierte en algo critico — se puede llegar al compromiso de utilizar los mismos datos en un conjunto de test siempre que no se muten los datos (por ejemplo en queries).
    @@ -910,12 +910,13 @@ Crédito:
    -❌ **De lo contrario:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **De lo contrario:** FMuchos test fallaran, un despliegue se aborta, nuestro equipo perdera mucho tiempo, ¿tenemos un bug? vamos a investigar, ah no — parece que dos test estan mutando el mismo dato
    @@ -923,40 +924,42 @@ Crédito:
    { - //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + //añadiendo datos de sites y admin a nuestra DB. ¿Donde estan los datos? fuera. En un json extermo o en un modelo da migración 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 + //Se que el nombre de site "portal" existe, — lo he visto en seed.json 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 + //Se que el nombre de site "portal" existe, — lo he visto en seed.json const siteToCheck = await SiteService.getSiteByName("Portal"); - expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ + expect(siteToCheck.name).to.be.equal("Portal"); //Fallo! El test anterior ha cambiado el nombre :[ }); ```
    -### :clap: Ejemplo de cómo hacerlo correctamente: We can stay within the test, each test acts on its own set of data +### :clap: Ejemplo de cómo hacerlo correctamente: Podemos permanecer dentro de nuestro test, cada test actua sobre su propio conjunto de datos ```javascript it("When updating site name, get successful confirmation", async () => { - //test is adding a fresh new records and acting on the records only + //el test esta añadiendo registors nuevos cada vez y actuando solo en esos registros const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); + const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); + expect(updateNameResult).to.be(true); }); ``` From 9ea4a3c668412d024cbea7e1c9d75dbaad233c15 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Tue, 28 Jul 2020 18:55:50 +0200 Subject: [PATCH 238/502] translated up to 3.1 --- readme-es.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/readme-es.md b/readme-es.md index fdeeca4f..7fc7be15 100644 --- a/readme-es.md +++ b/readme-es.md @@ -972,11 +972,11 @@ it("When updating site name, get successful confirmation", async () => { ## ⚪ ️ 3.1 Separate UI from functionality -:white_check_mark: **Haz:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI +:white_check_mark: **Haz:** Al centrarnos en testear la lógica del component, los detalles de la interfaz de usuario solo pueden entorpecernos, por lo qu debes abstraerte de ellos y que los test se centren en datos puros. En la practica, extrae los datos que necesites de una manera abstracta sin que este acoplada a la interfaz grafica, haz aserciones de los datos puros (vs detalles visuales en HTML/CSS) y desactiva las animaciones que pueden hacer lenta la interfaz. En este punto podrias pensar en desactivar la interfaz y solo hacer test de la parte back del UI (por ejemplo servicios, acciones, store) pero esto solo dará como resultado test ficticios, diferentes a la realidad y no desvelaran casos en los que los datos correctos no llegan a la interfaz de usuario.
    -❌ **De lo contrario:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation +❌ **De lo contrario:** Los datos calculados puros de tu test pueden estar listos en 10ms, pero luego todo el test tarda 500ms (100 tests = 1 min) debido a alguna animación irrelevante
    @@ -984,38 +984,38 @@ it("When updating site name, get successful confirmation", async () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Separating out the UI details +### :clap: Ejemplo de cómo hacerlo correctamente: Separar los detalles de UI ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Ejemplos con React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Ejemplos con react-testing-library") ```javascript test("When users-list is flagged to show only VIP, should display only VIP members", () => { - // Arrange + // Arreglar const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; - // Act + // Actuar const { getAllByTestId } = render(); - // Assert - Extract the data from the UI first + // Afirmar - Extrae los datos de la UI primero 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 + expect(allRenderedUsers).toEqual(allRealVIPUsers); //compara datos con datos, aqui no hay UI }); ```
    -### :thumbsdown: Ejemplo Anti Patrón: Assertion mix UI details and data +### :thumbsdown: Ejemplo Anti Patrón: Mezcla de datos y detalles de la UI en las aserciones ```javascript test("When flagging to show only VIP, should display only VIP members", () => { - // Arrange + // Arreglar const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; - // Act + // Actuar const { getAllByTestId } = render(); - // Assert - Mix UI & data in assertion + // Afirmar - Mezcla de UI y datos en las aserciones expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); }); ``` From bf4baae33d74bca4c7360142fd03417240853508 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Tue, 28 Jul 2020 20:29:57 +0200 Subject: [PATCH 239/502] translated up to 3.2 --- readme-es.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/readme-es.md b/readme-es.md index 7fc7be15..a872f2e4 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1024,13 +1024,13 @@ test("When flagging to show only VIP, should display only VIP members", () => {

    -## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change +## ⚪ ️ 3.2 Consulta elementos HTML basandote en atributos que no deberian cambiar -:white_check_mark: **Haz:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +:white_check_mark: **Haz:** Consulta elementos HTML basandote en attributos que deberian permanecer intactos a cambios graficos al contrario que selectores CSS y etiquetas de los formularios. Si el elemento designado no tiene esos attributos, crea un atributo dedicado solamente a los test como 'test-id-submit-button'. Seguir este patron no solo asegura que tus test funcionales/logicos no se rompan nunca por cambios esteticos, sino que tambien queda claro a cualquier desarrollador que ese elemento y atributo estan ahi por y para los test y no deben eliminarse
    -❌ **De lo contrario:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **De lo contrario:** Quieres testear el login de tu app que tiene muchos componentes, lógica, servicios, y todo esta bien configurado - stubs, spies, las llamadas Ajax estan aisladas. All seems perfect. Entonces el test falla porque el diseñador ha cambiado la clase de un div de 'thick-border' a 'thin-border'
    @@ -1038,7 +1038,7 @@ test("When flagging to show only VIP, should display only VIP members", () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Querying an element using a dedicated attrbiute for testing +### :clap: Ejemplo de cómo hacerlo correctamente: Consultar un elemento utilizando un atributo solo para testing ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Ejemplos con React") @@ -1053,12 +1053,12 @@ test("When flagging to show only VIP, should display only VIP members", () => { ``` ```javascript -// this example is using react-testing-library +// este ejemplo está usando react-testing-library test("Whenever no data is passed to metric, show 0 as default", () => { - // Arrange + // Arreglar const metricValue = undefined; - // Act + // Actuar const { getByTestId } = render(); expect(getByTestId("errorsLabel").text()).toBe("0"); @@ -1067,7 +1067,7 @@ test("Whenever no data is passed to metric, show 0 as default", () => {
    -### :thumbsdown: Ejemplo Anti Patrón: Relying on CSS attributes +### :thumbsdown: Ejemplo Anti Patrón: Confiando en attributos CSS ```html @@ -1076,7 +1076,7 @@ test("Whenever no data is passed to metric, show 0 as default", () => { ``` ```javascript -// this exammple is using enzyme +// este ejemplo está usando enzyme test("Whenever no data is passed, error metric shows zero", () => { // ... From da447b5d1689417a6027923ce478986e631660d4 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Tue, 28 Jul 2020 21:14:58 +0200 Subject: [PATCH 240/502] translated up to 3.3 --- readme-es.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/readme-es.md b/readme-es.md index a872f2e4..d3cba6c8 100644 --- a/readme-es.md +++ b/readme-es.md @@ -267,7 +267,7 @@ it("When asking for an admin, ensure only ordered admins in results", () => { ## ⚪ ️ 1.4 Acercarse al testing caja-negra: Testea solo metodos publicos -:white_check_mark: **Haz:** Testear las partes internas suele traer un gasto extra enorme para muy poco beneficio. Si tu código/API comprueba todo los resultado correctamente, ¿deberias perder las proximas 3 horas en comprobar como esta funcionando internamente y despues mantener esos test tan fragiles? Cada vez que se comprueba un comportamiento publico, la implementacion privada es implicitamente testeada y tus test se romperan solo si hay un problema concreto (por ejemplo una salida incorrecta). Este enfoque tambien es conocido como `behavioral testing` (testing de comportamiento). Por otro lado, si se testean las partes internas (caja blanca) tu enfoque cambia de planificar la salida del componente a detalles minusculos, y tus test pueden romperse debido a refactors de código menores sin que se rompan los test de salida, por lo que aumenta tremendamente el mantenimiento de los mismos.
    +:white_check_mark: **Haz:** Testear las partes internas suele traer un gasto extra enorme para muy poco beneficio. Si tu código/API comprueba todo los resultado correctamente, ¿deberias perder las proximas 3 horas en comprobar como esta funcionando internamente y despues mantener esos test tan fragiles? Cada vez que se comprueba un comportamiento publico, la implementacion privada es implicitamente testeada y tus test se romperan solo si hay un problema concreto (por ejemplo una salida incorrecta). Este enfoque tambien es conocido como `behavioral testing` (testing de comportamiento). Por otro lado, si se testean las partes internas (caja-blanca) tu enfoque cambia de planificar la salida del componente a detalles minusculos, y tus test pueden romperse debido a refactors de código menores sin que se rompan los test de salida, por lo que aumenta tremendamente el mantenimiento de los mismos.
    ❌ **De lo contrario:** Tus test se comportaran como la fabula [que viene el lobo](https://es.wikipedia.org/wiki/El_pastor_mentiroso): gritando falsos positivos (por ejemplo, un test dalla por que se cambio el nombre a una variable provada). Como es de esperar, la gente empezara a ingnorar estos test hasta que un día ignoren un test de verdad... @@ -1088,15 +1088,15 @@ test("Whenever no data is passed, error metric shows zero", () => {
    -## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component +## ⚪ ️ 3.3 Siempre que sea posible, testea con un componente real y totalmente renderizado -:white_check_mark: **Haz:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +:white_check_mark: **Haz:** Siempre que tenga un tamaño razonable, testea tu componente como lo hacen tus usuarios, renderiza completamente la interfaz de usuario, actúa sobre ella y comprueba que la interfaz se comporta como esperabas. Evita todo tipo de mocks, partials o shadow rendering - hacerlo puede provocar bugs no detectados debido a la falta de detalles y dificultará el mantenimiento a medida que los test interfieren con las partes internas (consulte la sección 'Acercarse al testing caja-negra'). Si uno de los componentes hijos ralentiza significativamente tu test (por ejemplo por una animación) o complica el setup, considera reemplazarlo explícitamente por un fake -With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children +Con todo esto tambien es necesario tener ciertas precauciones: esta técnica funciona para componentes pequeños / medianos que contienen un número razonable de componentes hijos. Renderizar completamente un componente con demasiados hijos hará que sea difícil analizar los fallos de los test (análisis de causa raíz) y puede ser demasiado lento. En estos casos, escribe los menos test que necesites contra ese componente principal y más test contra sus hijos
    -❌ **De lo contrario:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? +❌ **De lo contrario:** Al hurgar en las partes internas de un componente, invocando sus metodos privados y verificando el estado interno - tendras que refactorizar todos los test siempre que refactorices los componentes ¿Realmente quieres dedicar ese tiempo en hacer este mantenimiento?
    @@ -1104,7 +1104,7 @@ With all that said, a word of caution is in order: this technique works for smal
    -### :clap: Ejemplo de cómo hacerlo correctamente: Working realstically with a fully rendered component +### :clap: Ejemplo de cómo hacerlo correctamente: Trabajando con un componente totalmente renderizado ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Ejemplos con React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Enzyme-blue.svg "Ejemplos con Enzyme") @@ -1124,35 +1124,36 @@ class Calendar extends React.Component { //Examples use React & Enzyme test("Realistic approach: When clicked to show filters, filters are displayed", () => { - // Arrange + // Arreglar const wrapper = mount(); - // Act + // Actuar wrapper.find("button").simulate("click"); - // Assert + // Afirmar expect(wrapper.text().includes("Choose Filter")); - // This is how the user will approach this element: by text + // Así es como el usuario abordará este elemento: por texto }); ``` -### :thumbsdown: Ejemplo Anti Patrón: Mocking the reality with shallow rendering +### :thumbsdown: Ejemplo Anti Patrón: Mockeando con renderizado superficial (shallow rendering) ```javascript test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { - // Arrange + // Arreglar const wrapper = shallow(); - // Act + // Actuar wrapper .find("filtersPanel") .instance() .showFilters(); - // Tap into the internals, bypass the UI and invoke a method. White-box approach + // Aprovecha las partes internas, saltandote la UI e invocando el metodo ditectamente. + // Aproximación caja-blanca - // Assert + // Afirmar expect(wrapper.find("Filter").props()).toEqual({ title: "Choose Filter" }); - // what if we change the prop name or don't pass anything relevant? + // ¿Qué pasa si cambiamos el nombre de la prop o si no pasamos nada relevante? }); ``` From 8f39ab8da049361d3bc442089384eb32ed865bda Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Tue, 28 Jul 2020 22:01:14 +0200 Subject: [PATCH 241/502] translated up to 3.4 --- readme-es.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/readme-es.md b/readme-es.md index d3cba6c8..1b36aab4 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1161,12 +1161,13 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up +## ⚪ ️ 3.4 No pauses, usa soporte del framewok para eventos asincronos. Tambien intenta acelerar las cosas + +:white_check_mark: **Haz:** En muchos casos, el tiempo que tarda una unidad bajo test es desconocido (por ejemplo, la animación elimina la visualización de un elemento) - en este caso, evita esperar (por ejemplo stTimeOut) y elige metodos mas deterministas que la mayoria de las plataformas proveen. Algunas librerias permiten esperar en operaciones (por ejemplo [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), otras proveen un API para esperar como [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). A veces, una forma más elegante es hacer stub del recurso lento, como una API por ejemplo, con lo que el momento de respuesta se vuelve determinista., y volvemos a poder renderizar el componente directamente. Cuando tengas dependencias con algun componente externo que espera, puede ser util modificar el tiempo con librerias como [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Esperar es algo que debemos evitar siempre, por que fuerza que tu test sea lento o tenga ciertos riesgos (cuando esperas un tiempo muy bajo). Siempre que sea inevitable esperar y hacer polling, y el framework de testing no tenga soporte, algunas librerias de npm pueden ayudar con soluciones semi-deterministas, como [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) -:white_check_mark: **Haz:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution
    -❌ **De lo contrario:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance +❌ **De lo contrario:** Cuando se espera mucho tiempo, los tests seran un orden de magnitud más lentos. Cuando intentes espera tiempos bajos, los test fallarán cuando la unidad bajo test no haya respondido a tiempo. Por tanto, se reduce a balancear entre puntos debiles y el rendimiento malo.
    @@ -1174,42 +1175,42 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -### :clap: Ejemplo de cómo hacerlo correctamente: E2E API that resolves only when the async operations is done (Cypress) +### :clap: Ejemplo de cómo hacerlo correctamente: API E2E que se resuelve solo cuando se realizan las operaciones asíncronas (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 "Ejemplos con 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 +// usando Cypress +cy.get("#show-products").click(); // navegar +cy.wait("@products"); // esperar a que la ruta aparezca +// esta linea será ejecutada solo cuando la ruta haya teminado ``` -### :clap: Ejemplo de cómo hacerlo correctamente: Testing library that waits for DOM elements +### :clap: Ejemplo de cómo hacerlo correctamente: Libreria de testing que espera a elementos del DOM ```javascript // @testing-library/dom test("movie title appears", async () => { - // element is initially not present... + // el elemento no esta presente al comenzar... - // wait for appearance + // esperando a que este disponible await wait(() => { expect(getByText("the lion king")).toBeInTheDocument(); }); - // wait for appearance and return the element + // esperando que este disponible para devolver el elemento const movie = await waitForElement(() => getByText("the lion king")); }); ``` -### :thumbsdown: Ejemplo Anti Patrón: custom sleep code +### :thumbsdown: Ejemplo Anti Patrón: codigo de espera propio ```javascript test("movie title appears", async () => { - // element is initially not present... + // el elemento no esta presente al comenzar... - // custom wait logic (caution: simplistic, no timeout) + // espera con logica propia (precaución: simplista, sin tiempo de espera) const interval = setInterval(() => { const found = getByText("the lion king"); if (found) { @@ -1218,7 +1219,7 @@ test("movie title appears", async () => { } }, 100); - // wait for appearance and return the element + // esperando que este disponible para devolver el elemento const movie = await waitForElement(() => getByText("the lion king")); }); ``` From 6fa9f95958492c1b83c9b40d91835cc824fb68b5 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Fri, 31 Jul 2020 20:18:19 +0200 Subject: [PATCH 242/502] translated up to 3.5 --- readme-es.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-es.md b/readme-es.md index 1b36aab4..5d191c11 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1228,23 +1228,23 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.5 Watch how the content is served over the network +## ⚪ ️ 3.5 Observa como se sirve tu contenido a nivel de red ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Ejemplos con Lighthouse") -✅ **Haz:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 +✅ **Haz:** Usa un monitor que garantize que la carga de la página esté optimizada - esto incluye cualquier problema de UX como descarga lenta o un paquete no minimizado. El mercado de herramientas de este tipo no es pequeño: herramientas basicas como [pingdom](https://www.pingdom.com/), AWS CloudWatch, [gcp StackDriver](https://cloud.google.com/monitoring/uptime-checks/) se puede configurar para ver si el servidor esta corriendo y respondiendo dentro de un SLA razonable. Esto solo rallar la superficie, podria haber muchimas cosas mal, portante es mejor optar por herramientas especializadas en frontend, (por ejemplo [lighthouse](https://developers.google.com/web/tools/lighthouse/), [pagespeed](https://developers.google.com/speed/pagespeed/insights/)) y hagan un analis mucho más amplio. El foco debe ponerse en los sintomas y metricas que afecten directamente a UX, como el tiempo de carga, [render mínimo](https://scotch.io/courses/10-web-performance-audit-tips-for-your-next-billion-users-in-2018/fmp-first-meaningful-paint), [tiempo hasta que la pagina es manejable (TTI)](https://calibreapp.com/blog/time-to-interactive/). Sobre todo esto, tambien debes estar atento a posibles causas tecnicas, como garantizar que el contenido sea comprimido, tiempo hasta el primer byte, optimizar imagenes, asegurar un tamaño del DOM razonable, SSL, y muchos más. Es aconsejable tener toda esta monitorización durante el desarrollo, como parte del CI y mucho más importante - 24x7 en los servidores de producción / CDN
    -❌ **De lo contrario:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration +❌ **De lo contrario:** Es muy decepcionante darse cuenta de que después de haber tenido mucho cuidado y trabajo en crear una interfaz, pasen el 100% de los test funcionales y un pipeline sofisticado - el UX es horrible y lento por culpa de un CDN mal configurado
    Código de Ejemplo -### :clap: Ejemplo de cómo hacerlo correctamente: Lighthouse page load inspection report +### :clap: Ejemplo de cómo hacerlo correctamente: reporte de carga de la pagina con Lighthouse -![](/assets/lighthouse2.png "Lighthouse page load inspection report") +![](/assets/lighthouse2.png "Reporte de carga de la pagina con Lighthouse")
    From 35da016f0d0d9543c46d476ce596375e706b29db Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Fri, 31 Jul 2020 20:42:40 +0200 Subject: [PATCH 243/502] translated up to 3.6 --- readme-es.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme-es.md b/readme-es.md index 5d191c11..ef67b46e 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1250,13 +1250,13 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs +## ⚪ ️ 3.6 Usa stubs para recursos lentos como el API de back-end -:white_check_mark: **Haz:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +:white_check_mark: **Haz:** Cuando programas tus test principales (no los test E2E), evita interactuar con cualquier recurse que este fuera de tu responsabilidad y control, como las API de back-end y usar stubs en su lugar (es decir, un doble de test). De forma practica, en vez de hacer llamadas de red reales al API, utiliza alguna libreria (como [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) para stubear las repuestas de API. El principal beneficio es evitar la inestabilidad - testeando o suplantando APIs, por definición, no son muy estables y de vez en cuando fallarán los test, aunque TU componente se comporte bien (en producción generalmente se aceleran las respuestas, pero no esta pensado para hacer test). Hacerlo te permitirá simular varios compartamientos de API que deberían definir el comportamiento de nuestro componente para caminos no felices, por ejemplo cuando no se encuentran datos o cuando API devuelve un error. Por último, pero no por ello menos importante, las peticiones de red hacen más lentos nuestros test
    -❌ **De lo contrario:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower +❌ **De lo contrario:** La media de ejecución de los test no dura más de unos pocos ms, una llamada a API estandar dura 100ms>, lo que lo hace cada test ~20x más lento
    @@ -1264,12 +1264,12 @@ test("movie title appears", async () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Stubbing or intercepting API calls +### :clap: Ejemplo de cómo hacerlo correctamente: Stubear or interceptar las llamadas a API ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Ejemplos con React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Ejemplos con react-testing-library") ```javascript -// unit under test +// unidad testeada export default function ProductsList() { const [products, setProducts] = useState(false); @@ -1287,15 +1287,15 @@ export default function ProductsList() { // test test("When no products exist, show the appropriate message", () => { - // Arrange + // Arreglar nock("api") .get(`/products`) .reply(404); - // Act + // Actuar const { getByTestId } = render(); - // Assert + // Afirmar expect(getByTestId("no-products-message")).toBeTruthy(); }); ``` From 8ff13ff91f0d3550406d714038eb038040627dd7 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Fri, 31 Jul 2020 21:13:36 +0200 Subject: [PATCH 244/502] translated up to 3.7 --- readme-es.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme-es.md b/readme-es.md index ef67b46e..84544f03 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1304,13 +1304,13 @@ test("When no products exist, show the appropriate message", () => {
    -## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system +## ⚪ ️ 3.7 Haz muy pocos test end-to-end que abarquen todo el sistema -:white_check_mark: **Haz:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Pupeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +:white_check_mark: **Haz:** Aunque E2E (end-to-end) para algunos significa solo hacer test de UI en navegador de verdad (ver el punto 3.6), para otros significa que impliquen todo el sistema, incluido el backend real. Esto es muy valioso ya que te cubren errores de integracion entre el frontend y el backend que pueden ocurrir por diferencias de opinion en el esquema de datos. También son un método eficientes para sacar errores de integración entre backends (por ejemplo, microservicio A envia datos erroneso al microservicio B) e incluso para detectar fallos de despliegue - actualmente no hay herramientas para test E2E solo backend tan amigables y maduras como las de UI como [Cypress](https://www.cypress.io/) y [Pupeteer](https://github.com/GoogleChrome/puppeteer). La desventaja de estos test es su coste alto, tener un entorno configurado con todos los componentes, la fragilidad de los test - si tenemos 50 microservicios, solo con que falle uno, los test E2E fallan. Por estas razones tenmos que usar moderadamente esta tecnica y probablemente tener entre 1 y 10 test de este tipo. Dicho esto, incluso una cantidad pequeña de test E2E es probablemente que detecten el tipo de problemas a los que estan realmente dirigidos: despligue e integración. Es aconsejable que se ejecuten en un entorno lo mas parecido a produción
    -❌ **De lo contrario:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected +❌ **De lo contrario:** Pudes invertir mucho en testear la funcionalidad de la UI para darte cuenta demasiado tarde que el backend devuelve un contrato (el esquema de datos con el que la UI trabaja) muy diferente al esperado
    From 6517904210852223248f41b811385a68d46f8db7 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Fri, 31 Jul 2020 21:35:27 +0200 Subject: [PATCH 245/502] translated up to 3.8 --- readme-es.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme-es.md b/readme-es.md index 84544f03..66249f1d 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1314,13 +1314,13 @@ test("When no products exist, show the appropriate message", () => {
    -## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials +## ⚪ ️ 3.8 Acelera los test E2E reutilizando las credenciales de login -:white_check_mark: **Haz:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individial tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). +:white_check_mark: **Haz:** En los test E2E que involucren un backend real que usa un token para identicarse en las llamadas a API, no vale la pena aislat el test tanto como para que se cree un usuario y se haga login en cada test. En vez de esto, haz login una vez antes de ejecutar todos los test (en el before-all) guarda el token en de forma local y reutilizalo en cada petición. Esto parece violar unos de los principios básicos - manten los test autonomos sin acoplamiento de recursos. Lo que es cierto, pero en los test E2E el rendimiento es clave, y crear 1-3 peticiones a API antes de empezar cada test individual puede llevarnos a unos tiempos de ejecución horribles. Reutilizar las credenciales no significa que las pruebas tengan que actuar sobre los mismos registros de usuario - si se basan en ellos (por ejemplo, testeando el historial de pagos), asegurate de crear los registro como parte del test y evita compartirlos con otros test. Y siempre recuerda que el backend puede ser sustituido - si tus test estan focalizado en el frontend puede ser mejor aislarlos y descomnectar las API de backend (consulta el punto 3.6)
    -❌ **De lo contrario:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again +❌ **De lo contrario:** Dados 200 test y asumiendo que un login son 100ms = 20 segusdos solo para hacer el mismo login una y otra vez
    @@ -1328,14 +1328,14 @@ test("When no products exist, show the appropriate message", () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Logging-in before-all and not before-each +### :clap: Ejemplo de cómo hacerlo correctamente: Logandose en before-all y no en before-each -![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Usando Cypress para ilustrar la idea") ```javascript let authenticationToken; -// happens before ALL tests run +// ocurre antes de ejecutar TODOS los test before(() => { cy.request('POST', 'http://localhost:3000/login', { username: Cypress.env('username'), @@ -1347,7 +1347,7 @@ before(() => { }) }) -// happens before EACH test +// ocurre antes de CADA test beforeEach(setUser => () { cy.visit('/home', { onBeforeLoad (win) { From a5134246abdc04d6af11cf7b20c3ded2c47aed27 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Fri, 31 Jul 2020 21:57:29 +0200 Subject: [PATCH 246/502] translated up to 3.9 --- readme-es.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/readme-es.md b/readme-es.md index 66249f1d..520bae8f 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1177,7 +1177,7 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display ### :clap: Ejemplo de cómo hacerlo correctamente: API E2E que se resuelve solo cuando se realizan las operaciones asíncronas (Cypress) -![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Usando Cypress para ilustrar la idea") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Ejemplos con react-testing-library") ```javascript @@ -1362,13 +1362,13 @@ beforeEach(setUser => () {
    -## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map +## ⚪ ️ 3.9 Haz un test E2E que navegue toda la página (smoke test) -:white_check_mark: **Haz:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector +:white_check_mark: **Haz:** Para el monitoreo de producción y verificar que nada se rompe en tiempo de desarrollo (sanity check), ejecuta un único test E2E que visite todas o la mayoría de las páginas y se asegure que ninguna se rompe. Este tipo de test proporciona un gran retorno de la inversión ya que es un bastante sencillo de crear y mantener y puede detecta cualquier tipo de fallo, incluido funcionales, red y despliegue. Otras formas de hacer smoke y sanity checks no son tan confiables y exhaustivas - algunso equipos de operaciones simplemente hacen ping a la página de inicio (en producción) o desarrolladores que tiene muchos test de integración que no levanta errores de construcción o de navegador. No hace falta decir que esta prueba no sustituye los test funcionales, solo siver como detector de humo rápido.
    -❌ **De lo contrario:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering +❌ **De lo contrario:** Todo puede parecer estar bien, todos los test pasan, el health-check de produción está ok también, pero el componente de pago se construyó mal y simplemente la ruta /Payment no se renderiza
    @@ -1376,14 +1376,14 @@ beforeEach(setUser => () {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Smoke travelling across all pages +### :clap: Ejemplo de cómo hacerlo correctamente: Navegando a todas las páginas -![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Usando Cypress para ilustrar la 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 + // ejemplificado con Cypress pero puede implementarse + // facilmente usando cualquier herramienta E2E cy.visit("https://mysite.com/home"); cy.contains("Home"); cy.contains("https://mysite.com/Login"); @@ -1491,7 +1491,7 @@ paths: ### :clap: Ejemplo de cómo hacerlo correctamente: Using Applitools to get snapshot comaprison and other advanced features -![](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") +![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg "Using AppliTools") ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Usando Cypress para ilustrar la idea") ```javascript import * as todoPage from "../page-objects/todo-page"; From e8907c81c66ea5062c78cc58b674abf7cb56e725 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Fri, 31 Jul 2020 22:23:13 +0200 Subject: [PATCH 247/502] translated up to 3.10 --- readme-es.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme-es.md b/readme-es.md index 520bae8f..5fcbfdb0 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1397,11 +1397,11 @@ it("When doing smoke testing over all page, should load them all successfully",
    -## ⚪ ️ 3.10 Expose the tests as a live collaborative document +## ⚪ ️ 3.10 Exponer los tes como un documento colaborativo vivo -:white_check_mark: **Haz:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. +:white_check_mark: **Haz:** Además de aumentar la confiabilidad de la aplicación, los test te dan otra caracteristica muy atractiva - sirven de documentación viva. Dado que los test hablan en un leguaje menos tecnico y sobre el producto y UX, usar las herramientas correctas puede servir como un artefacto de comunicación que alina en gran medida a desarrolladores y su cliente. Por ejemplo, algunos frameworks permiten expresar el flujo y las expectativas (el test plan) utilizando un lenguaje legible para que cualquier stakeholder, incluyendo los product managers, pueden leer, aprobar y colaborar en los test convirtiendose en el documento de requerimentos vivo. Esta tecnica tambien se la conoce como 'test de aceptación', ya que permite al cliente definir sus criterios de aceptación en un leguaje sencillo. Esto es [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) en su forma más pura. Uno de los frameworks más populares para aesto es [Cucumber que tiene su sabor en JavaScript](https://github.com/cucumber/cucumber-js), ver el ejemplo más abajo. Otra forma similar pero diferente, [StoryBook](https://storybook.js.org/), permite exponer los componentes UI como un catalogo grafico, donde cualquiera puede recorrer los diferentes estados de cada componente (por ejemplo renderizar una cuadricula sin filtro, con multiples filas o con ninguna, etc), ver como queda y como se activa ese estado - esto también puede atraer a la gente de producto pero sobre todo siver como documentación viva para los desarrolladores que consumen esos componentes -❌ **De lo contrario:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value +❌ **De lo contrario:** Despues de invertir los mejores recursos en los test, es una pena no aprovechar ese tiempo y ganar un gran valor como es la documentación
    @@ -1409,12 +1409,12 @@ it("When doing smoke testing over all page, should load them all successfully",
    -### :clap: Ejemplo de cómo hacerlo correctamente: Describing tests in human-language using cucumber-js +### :clap: Ejemplo de cómo hacerlo correctamente: Describiendo test en un lenguaje para humanos usando 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 +// así es como se pueden describir los test con cucumber: lenguaje sencillo que permite a cualquiera comprenderlos y colaborar Feature: Twitter new tweet @@ -1430,9 +1430,9 @@ Feature: Twitter new tweet ``` -### :clap: Ejemplo de cómo hacerlo correctamente: Visualizing our components, their various states and inputs using Storybook +### :clap: Ejemplo de cómo hacerlo correctamente: Visualizando nuestros componentes, sus diversos estados y entradas usando Storybook -![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Usando StoryBook") ![alt text](assets/story-book.jpg "Storybook") From a16a62d32c515cfe2497e41c848bb4078350e46f Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 00:36:11 +0200 Subject: [PATCH 248/502] translated up to 3.11, whole section 3 transalted --- readme-es.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/readme-es.md b/readme-es.md index 5fcbfdb0..25a36c8d 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1440,13 +1440,13 @@ Feature: Twitter new tweet

    -## ⚪ ️ 3.11 Detect visual issues with automated tools +## ⚪ ️ 3.11 Detecta problemas visuales con herramientas automatizadas -:white_check_mark: **Haz:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge signficant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by elemeinating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/css changes that led to the issue +:white_check_mark: **Haz:** Configure herramientas automatizadas para capturar screenshoots de UI cuando se presenten cambios y detecte problemas visuales como contenido superpuesto o roto. Esto garantiza que no solo se muestren los datos correctos si no que el usuario los vea correctamente. Está tecnica no es ampliamente usada, nuestra mentalidad nos lleva a los test funcionales, pero es lo visual lo que el usuario experimenta y con la cantidad de dispositivos es relativamente facil pasar por alto algunos bugs en la UI. Algunas herramientas gratuítas pueden proporcionar lo básico - generar y guardar screenshots para la inspeccion manual por una persona. Mientras este enfoque podria ser suficiente para aplicaciones pequeñas, no es valido como cualquier otro test manual que exige trabajo de una persona cada vez que algo cambia. Por otro lado, es bastante dificil detectar problemas de UI automaticamente debido a que no está claramente deficini - aquí es donde interviene el campo de la 'Regresion Visual' a resolver este rompecabezas de comparar la UI antigua con los últimos cambios y detectar diferencias. Alguas erramientas OSS/gratuitas pueden proporcionar parte de esta funcionalidad (por ejemplo [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) pero podrian conllevar un tiempo de configuración muy alto. Algunas herramientas comerciales (por ejemplo [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) dan un paso más reducir la instalacion y contener funciones avanzadas como interfaces de administración, alertas, captura intelegiente que elimina el 'ruido visual' (por ejemplo, banners, animaciones) e incluso llegan a adelantar el analisis de la causa raiz de los cambios del DOM / css que han causado el problema.
    -❌ **De lo contrario:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? +❌ **De lo contrario:** ¿Como de bien hecha una pagina que muestra buen contendo (100% test ok), carga de forma instantanea pero la mitad del area de contenido está oculto?
    @@ -1454,23 +1454,23 @@ Feature: Twitter new tweet
    -### :thumbsdown: Ejemplo Anti Patrón: A typical visual regression - right content that is served badly +### :thumbsdown: Ejemplo Anti Patrón: Una regresion visual estandar - contenido correcto que se muestra mal ![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks")
    -### :clap: Ejemplo de cómo hacerlo correctamente: Configuring wraith to capture and compare UI snapshots +### :clap: Ejemplo de cómo hacerlo correctamente: Configurando wraith para capturar y comparar snapshots de UI -![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") +![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Usando Wraith") ``` -​# Add as many domains as necessary. Key will act as a label​ +​# Añade tantos dominios como sea necesario. La key actuará como etiqueta domains: english: "http://www.mysite.com"​ -​# Type screen widths below, here are a couple of examples​ +​# escribe los anchos de pantalla a continuación, por ejemplo screen_widths: @@ -1479,7 +1479,7 @@ screen_widths: - 1024​ - 1280​ -​# Type page URL paths below, here are a couple of examples​ +​# escribe las URL de la página a constinuación, por ejemplo paths: about: path: /about @@ -1489,7 +1489,7 @@ paths: path: /subscribe ``` -### :clap: Ejemplo de cómo hacerlo correctamente: Using Applitools to get snapshot comaprison and other advanced features +### :clap: Ejemplo de cómo hacerlo correctamente: Usando Applitools para hacer comparación de snapshoots y otras funciones avanzadas ![](https://img.shields.io/badge/🔨%20Example%20using%20AppliTools-blue.svg "Using AppliTools") ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Usando Cypress para ilustrar la idea") From 12cf74914c066acdd9dbbbf0aae4bb211f91a868 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 00:57:06 +0200 Subject: [PATCH 249/502] translated up to 4.1 --- readme-es.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/readme-es.md b/readme-es.md index 25a36c8d..eb91e696 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1516,19 +1516,19 @@ describe("visual validation", () => {

    -# Section 4️⃣: Measuring Test Effectiveness +# Section 4️⃣: Midiendo la efectividad de los Test

    -## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number +## ⚪ ️ 4.1 Completa una cobertura suficiente que de confianza, ~80% parece el numero de la suerte -:white_check_mark: **Haz:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. +:white_check_mark: **Haz:** El proposito de los test es tener suficiente confianza para moverse rápido, obviamente cuando más código se pruebe, más confianza tendremos en el equipo. La cobertura nos mide cuantas lineas de código (y ramas, declaraciones, etc) se alcanzan mediante los test. Entonces, cuanta es suficiente? Obviamente 10-30% es demasiado bajo para tener alguna idea de que puedes tener que corregir, y por otro lado el 100% es muy caro y puede cambiar el foco de los caminos criticos a rincones apenas usados del código. La respuesta larga es que depende de muchos dactores como el tipo de aplicación - si estas construyendo la siguiente generación del Airbus A380 un 100% es obligatorio, pero para una web de dibujos animados, el 50% podria hasta ser demasiado. Aunque la mayoria de los entusiastas de los test dicen que el porcentaje de cobertura correcto es contextual, la mayoria de ellos comentan que el 80% como la regla correcta ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) que posiblemente debería satisfacer la mayotia de aplicaciones. -Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets +Consejos de implementación: es posible que quieras configurar la integracion continua (CI) para que tenga un umbral de cobertura ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) y que pare el pipeline cuando no cumpla el estandar (también es posible configurar el porcentaje por compoente, vease el ejemplo a continuación). Ademas de esto, deberias considerar detectar la bajada de cobertura (cuando un nuevo commit tiene menos cobertura que antes) - esto empujara a los desarrolladores a aumentar o al menos preservar la cantidad de código con test. Y también puede ser engañado como se puestra en los siguientes puntos.
    -❌ **De lo contrario:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down +❌ **De lo contrario:** La confianza y los números van de la mano, sin saber realmente que se ha testeado la mayor parte del sistema - habrá algo de miedo y el miedo te retrasará
    @@ -1536,17 +1536,17 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :clap: Example: A typical coverage report +### :clap: Example: Un informe de cobertura típico -![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "Un informe de cobertura típico")
    -### :clap: Ejemplo de cómo hacerlo correctamente: Setting up coverage per component (using Jest) +### :clap: Ejemplo de cómo hacerlo correctamente: Configurar cobertura por componente (usando Jest) -![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") +![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Usando Jest") -![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest)") +![alt text](assets/bp-18-code-coverage2.jpeg "Configurar cobertura por componente (usando Jest)")
    From 395f1fecb9007f029dbf14b884322e233a8f8d52 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 01:16:57 +0200 Subject: [PATCH 250/502] translated up to 4.2 --- readme-es.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/readme-es.md b/readme-es.md index eb91e696..fa4fb558 100644 --- a/readme-es.md +++ b/readme-es.md @@ -589,7 +589,7 @@ Una alternativa más elegante seria usar solo la aserción de una sola linea que
    -❌ **De lo contrario:** Sería muy dificil deducir a partir de los informes de test (por ejemplo, informe de CI) que es lo que ha salido mal +❌ **De lo contrario:** Sería muy dificil deducir a partir de los reportes de test (por ejemplo, reporte de CI) que es lo que ha salido mal
    @@ -672,7 +672,7 @@ describe("Order service", function() {
    -❌ **De lo contrario:** Cuando nos enfrentamos a un reporte con una lista plana de test, tendremos que leer rápidamente textos largos para determinar los escenarios principales y relacionar los test fallidos. Considera el siguiente caso: Cuando 7/100 test fallan, revisar una lista plana te exige leer el tesxto de las pruebas que fallan para ver como se relacionan entre ellas y que tienen en común. Sin embargo, en un informe jerarquizado, si los 7 estan bajo un mismo flujo o categoria, puedes saber rápidamente cual o donde puede estar la causa raiz del fallo +❌ **De lo contrario:** Cuando nos enfrentamos a un reporte con una lista plana de test, tendremos que leer rápidamente textos largos para determinar los escenarios principales y relacionar los test fallidos. Considera el siguiente caso: Cuando 7/100 test fallan, revisar una lista plana te exige leer el tesxto de las pruebas que fallan para ver como se relacionan entre ellas y que tienen en común. Sin embargo, en un reporte jerarquizado, si los 7 estan bajo un mismo flujo o categoria, puedes saber rápidamente cual o donde puede estar la causa raiz del fallo
    @@ -1536,9 +1536,9 @@ Consejos de implementación: es posible que quieras configurar la integracion co
    -### :clap: Example: Un informe de cobertura típico +### :clap: Example: Un reporte de cobertura típico -![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "Un informe de cobertura típico") +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "Un reporte de cobertura típico")
    @@ -1552,12 +1552,13 @@ Consejos de implementación: es posible que quieras configurar la integracion co

    -## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities +## ⚪ ️ 4.2 Inspecciona los reportes de cobertura para detectar áreas no testadas y otras cosas raras. + +:white_check_mark: **Haz:** Algunos problemas se ocultan por debajo del radar y son realmente difíciles de encontrar utilizando herramientas tradicionales. Estos no son realmente bugs sino más bien un comportamientos curiosos de la aplicación que podrían tener un gran impacto. Por ejemplo, a menudo algunas áreas de código no se invocan nunca o rara vez - puedes pensar que la clase 'PricingCalculator' siempre determina el precio del producto, pero resulta que en realidad nunca se invoca, aunque tenemos 10000 productos en DB y muchas ventas… Los reportes de nos ayudan a darnos cuenta de si la aplicación se comporta de la manera que esperamos. Aparte de eso, también podemos resaltar qué tipos de código no se testean: que el 80% del código se testea, no nos indica si las partes críticas están cubiertas. Generar reportes es fácil: simplemente ejecute su aplicación en producción o durante test con cobertura y luego revisa los reportes que resaltan la frecuencia con la que se invoca cada parte del código. Si le dedicas un tiempo para echar un vistazo a estos datos, puedes encontrar algunas errores -:white_check_mark: **Haz:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas
    -❌ **De lo contrario:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from +❌ **De lo contrario:** Si no sabes qué trozos de código no se testean, no sabes dónde pueden aparecer problemas
    @@ -1565,11 +1566,11 @@ Consejos de implementación: es posible que quieras configurar la integracion co
    -### :thumbsdown: Ejemplo Anti Patrón: What’s wrong with this coverage report? +### :thumbsdown: Ejemplo Anti Patrón: ¿Qué hay mal en este reporte de cobertura? -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) +Basado en un escenario real en el que rastreamos el uso de nuestra aplicación en el control de calidad y descubrimos patrones de login interesantes (Sugerencia: la cantidad de fallos de login no es proporcional, algo está claramente mal. Finalmente, resultó que algún error de la interfaz provocaba que se siguiera llamando al API de login en segundo plano) -![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report?") +![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "¿Qué hay mal en este reporte de cobertura?")
    From 97bee5583ae519c7f6fb954fb4f68888f4e639f8 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 01:30:10 +0200 Subject: [PATCH 251/502] translated up to 4.3 --- readme-es.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/readme-es.md b/readme-es.md index fa4fb558..7ca9cf05 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1576,20 +1576,20 @@ Basado en un escenario real en el que rastreamos el uso de nuestra aplicación e

    -## ⚪ ️ 4.3 Measure logical coverage using mutation testing +## ⚪ ️ 4.3 Mide la cobertura lógica usando mutation testing -:white_check_mark: **Haz:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +:white_check_mark: **Haz:** La métrica de cobertura tradicional a menudo miente: puede mostrarle una cobertura de código del 100%, pero ninguna de sus funciones, ni siquiera una, devuelve la respuesta correcta. ¿Cómo? simplemente mide sobre qué líneas de código se paso en los test, pero no verifica si laos test realmente han comprobado algo - asercionando la respuesta correcta. Como alguien que viaja por negocios y muestra su pasaporte, esto no prueba que haya realizado ningun trabajo, solo que ha visitado ciertos aeropuertos y hoteles. -Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: +Los test basados en mutaciones nos ayudan midiendo la cantidad de código que en realidad se TESTEÓ, no solo VISITADO. [Stryker] (https://stryker-mutator.io/) es una libreria JavaScript para test de mutación y la implementación es realmente clara: -(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations +(1) cambia intencionalmente el código y "planta bugs". Por ejemplo, el código newOrder.price === 0 se convierte en newOrder.price! = 0. Estos "bugs" se llaman mutaciones. -(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. +(2) ejecuta los test, si todo va bien, entonces tenemos un problema - los test no cumplen su propósito de descubrir bugs, las mutaciones se denominan supervivientes. Si los test fallaron, entonces genial, las mutaciones fueron asesinadas. -Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +Saber que todas o la mayoría de las mutaciones fueron asesinadas da mucha más confianza que la cobertura tradicional y el tiempo de configuración es muy similar
    -❌ **De lo contrario:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code +❌ **De lo contrario:** Te engañas si crees que una cobertura del 85% significa que tus test detectarán errores en el 85% de tu código
    @@ -1597,9 +1597,9 @@ Knowing that all or most of the mutations were killed gives much higher confiden
    -### :thumbsdown: Ejemplo Anti Patrón: 100% coverage, 0% testing +### :thumbsdown: Ejemplo Anti Patrón: 100% de cobertura, 0% testeado -![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") +![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Usando Stryker") ```javascript function addNewOrder(newOrder) { @@ -1612,14 +1612,14 @@ function addNewOrder(newOrder) { it("Test addNewOrder, don't use such test names", () => { addNewOrder({ asignee: "John@mailer.com", price: 120 }); -}); //Triggers 100% code coverage, but it doesn't check anything +}); //Genera un 100% cobertura de código, pero no comprueba nada ```
    -### :clap: Ejemplo de cómo hacerlo correctamente: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :clap: Ejemplo de cómo hacerlo correctamente: Los reportes de Stryker, una herramienta para test de mutación, detecta y cuenta la cantidad de código que no se testea (Mutaciones) -![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/bp-20-yoni-goldberg-mutation-testing.jpeg "Los reportes de Stryker, una herramienta para test de mutación, detecta y cuenta la cantidad de código que no se testea (Mutaciones)")
    From 57fda05a65d3c960021d99b9775fb49d04abe872 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 01:48:23 +0200 Subject: [PATCH 252/502] translated up to 4.4, whole section 4 transalted --- readme-es.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme-es.md b/readme-es.md index 7ca9cf05..35b038f6 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1625,20 +1625,20 @@ it("Test addNewOrder, don't use such test names", () => {

    -## ⚪ ️4.4 Preventing test code issues with Test linters +## ⚪ ️4.4 Prevención de problemas de código de test con linters para test -:white_check_mark: **Haz:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +:white_check_mark: **Haz:** ESLint tiene un conjunto de plugins específicos para inspeccionar patrones de código de test y descubrir problemas. Por ejemplo, [eslint-plugin-mocha] (https://www.npmjs.com/package/eslint-plugin-mocha) avisará cuando un test se escriba a nivel global (no es hijo de un describe () ) o cuando se omiten los test (https://mochajs.org/#inclusive-tests), lo que puede llevar a creer erroneamenre de que todas los test están ok. Del mismo modo, [eslint-plugin-jest] (https://github.com/jest-community/eslint-plugin-jest) puede, por ejemplo, advertir cuando una prueba no tiene aserciones (sin verificar nada)
    -❌ **De lo contrario:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation +❌ **De lo contrario:** Ver un 90% de cobertura de código y 100% de pruebas verdes te provocara una sonrisa hasta que te das cuenta de que muchas pruebas no asercionan nada y muchas test simplemente se omitieron. Con suerte, no desplegó nada basado en esta falsa observación
    Código de Ejemplo
    -### :thumbsdown: Ejemplo Anti Patrón: A test case full of errors, luckily all are caught by Linters +### :thumbsdown: Ejemplo Anti Patrón: Un test lleno de errores, afortunadamente detectados por el linter ```javascript describe("Too short description", () => { From f60e3a43119c3f8cc4d1e6f40b475ae08cb0ba19 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 02:27:29 +0200 Subject: [PATCH 253/502] translated up to 5.2 --- readme-es.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/readme-es.md b/readme-es.md index 35b038f6..861a6841 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1658,16 +1658,17 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# Section 5️⃣: CI and Other Quality Measures +# Section 5️⃣: CI y otras medidas de calidad

    -## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues +## ⚪ ️ 5.1 Enriquece tus linters y cancela las construciones que tienen problemas de linter + +:white_check_mark: **Haz:** Los linters son comida gratis, con una configuración de 5 minutos, obtienes gratis un piloto automático que vigila tu código y detecta problemas importantes mientras escribes. Atrás quedaron los días en los que el linter era solo maquillaje (¡no hay punto y coma!). Hoy en día, los linters pueden detectar problemas graves como errores que no se lanzan correctamente y perder información. Además de su conjunto básico de reglas (como [estándar ESLint] (https://www.npmjs.com/package/eslint-plugin-standard) o [estilo Airbnb] (https://www.npmjs.com/package / eslint-config-airbnb)), considera incluir algunos conjuntos de reglas especializadas como [eslint-plugin-chai-expect] (https://www.npmjs.com/package/eslint-plugin-chai-expect) que pueden descubrir tests sin aserciones, [eslint-plugin-promise] (https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) puede descubrir promesas sin resolución (su código nunca continuará), [eslint-plugin -security] (https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) que puede descubrir expresiones regulares que podrían usarse para ataques DOS, y [eslint-plugin-you-dont- need-lodash-underscore] (https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) es capaz de avisar cuando el código utiliza métodos de librerias de utilidades que forman parte de V8, métodos básicos como Lodash.\_map (...) -:white_check_mark: **Haz:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…)
    -❌ **De lo contrario:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5min linter setup could detect this TYPO and save your day +❌ **De lo contrario:** Considera un día lluvioso donde producción sigue fallando pero los registros no muestran el call stack de errores. ¿Que pasa? Tu código emite por error un objeto sin error y se perdió el trazado, una buena razón para golpearse la cabeza contra una pared de ladrillos. Una configuración de linter de 5 minutos podría detectar este GAZAPO y salvarte el día
    @@ -1675,22 +1676,23 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    -### :thumbsdown: Ejemplo Anti Patrón: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug +### :thumbsdown: Ejemplo Anti Patrón: El objeto de Error es emitido, no parece ninguna traza pora este error. Por suerte, ESLint detecta el siguiente error de producción -![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") +![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "El objeto de Error es emitido, no parece ninguna traza pora este error. Por suerte, ESLint detecta el siguiente error de producción")


    -## ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI +## ⚪ ️ 5.2 Acorta el tiempo de feedback con local developer-CI + +:white_check_mark: **Haz:** Tienes un pipeline de CI con test, linter, verificación de vulnerabilidades, etc.? Ayuda a los desarrolladores a ejecutarlo también localmente para solicitar comentarios instantáneos y acortar el [ciclo de feedback] (https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2 -circuitos de retroalimentacion/). ¿Por qué? un proceso de prueba eficiente constituye muchos bucles iterativos: (1) tests -> (2) feedback -> (3) refactor. Cuanto más rápido sea el feedback, más iteraciones de mejora puede realizar un desarrollador por módulo y perfeccionar los resultados. Por otro lado, cuando el feedback tarda en llegar, se podrían realizar menos iteraciones de mejora en un solo día, el equipo podría estar ya haciendo otra cosa / tarea / módulo y podría no estar listo para refinar ese módulo. -:white_check_mark: **Haz:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +En la practica, algunos proveedores de CI (Ejemplo: [CircleCI CLI local] (https://circleci.com/docs/2.0/local-cli/)) permiten ejecutar el pipeline localmente. Algunas herramientas comerciales como [wallaby proporcionan información valiosa y de test] (https://wallabyjs.com/) para el desarrollador sin coste. Alternativamente, puedes agregar scripts npm en el package.json para ejecutar todos los comandos de calidad (por ejemplo, test, linter, vulnerabilidades) - usa herramientas como [concurrently] (https://www.npmjs.com/package/concurrently) para paralelizarlas y que código de salida sea distinto de cero si falla alguna de las herramientas. Ahora el desarrollador solo debe invocar un comando - por ejemplo "npm run quality": para obtener feedback en el acto. Considera también cancelar un commit si el control de calidad falla usando un githook ([husky puede ayudar] (https://github.com/typicode/husky)) -Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky))
    -❌ **De lo contrario:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact +❌ **De lo contrario:** Cuando los resultados de calidad llegan un día más tarde que el código, los test no se convierten en una parte fluida del desarrollo, sino en algo formal posterior al mismo
    @@ -1698,7 +1700,7 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com
    -### :clap: Ejemplo de cómo hacerlo correctamente: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: Ejemplo de cómo hacerlo correctamente: Scripts npm que realizan una inspección de calidad del código, todos se ejecutan en paralelo a demanda o cuando un desarrollador está tratando de hacer commit/push de código nuevo ```javascript "scripts": { From 60d703734b7ad92d23b87a3996a0a1a3fccd9772 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 12:44:23 +0200 Subject: [PATCH 254/502] translated up to 5.3 --- readme-es.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/readme-es.md b/readme-es.md index 861a6841..75c79fb6 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1686,7 +1686,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 5.2 Acorta el tiempo de feedback con local developer-CI -:white_check_mark: **Haz:** Tienes un pipeline de CI con test, linter, verificación de vulnerabilidades, etc.? Ayuda a los desarrolladores a ejecutarlo también localmente para solicitar comentarios instantáneos y acortar el [ciclo de feedback] (https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2 -circuitos de retroalimentacion/). ¿Por qué? un proceso de prueba eficiente constituye muchos bucles iterativos: (1) tests -> (2) feedback -> (3) refactor. Cuanto más rápido sea el feedback, más iteraciones de mejora puede realizar un desarrollador por módulo y perfeccionar los resultados. Por otro lado, cuando el feedback tarda en llegar, se podrían realizar menos iteraciones de mejora en un solo día, el equipo podría estar ya haciendo otra cosa / tarea / módulo y podría no estar listo para refinar ese módulo. +:white_check_mark: **Haz:** Tienes un pipeline de CI con test, linter, verificación de vulnerabilidades, etc.? Ayuda a los desarrolladores a ejecutarlo también localmente para solicitar comentarios instantáneos y acortar el [ciclo de feedback] (https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2 -circuitos de retroalimentacion/). ¿Por qué? un proceso de prueba eficiente constituye muchos bucles iterativos: (1) tests -> (2) feedback -> (3) refactor. Cuanto más rápido sea el feedback, más iteraciones de mejora puede realizar un desarrollador por módulo y perfeccionar los resultados. Por otro lado, cuando el feedback tarda en llegar, se podrían realizar menos iteraciones de mejora en un solo día, el equipo podría estar ya haciendo otra cosa / tarea / módulo y podría no estar listo para refinar ese módulo En la practica, algunos proveedores de CI (Ejemplo: [CircleCI CLI local] (https://circleci.com/docs/2.0/local-cli/)) permiten ejecutar el pipeline localmente. Algunas herramientas comerciales como [wallaby proporcionan información valiosa y de test] (https://wallabyjs.com/) para el desarrollador sin coste. Alternativamente, puedes agregar scripts npm en el package.json para ejecutar todos los comandos de calidad (por ejemplo, test, linter, vulnerabilidades) - usa herramientas como [concurrently] (https://www.npmjs.com/package/concurrently) para paralelizarlas y que código de salida sea distinto de cero si falla alguna de las herramientas. Ahora el desarrollador solo debe invocar un comando - por ejemplo "npm run quality": para obtener feedback en el acto. Considera también cancelar un commit si el control de calidad falla usando un githook ([husky puede ayudar] (https://github.com/typicode/husky)) @@ -1725,14 +1725,15 @@ En la practica, algunos proveedores de CI (Ejemplo: [CircleCI CLI local] (https:

    -## ⚪ ️5.3 Perform e2e testing over a true production-mirror +## ⚪ ️5.3 Realiza test e2e sobre un espejo de producción -:white_check_mark: **Haz:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of Faas code. +:white_check_mark: **Haz:** Los test End to end (e2e) testing son el principal desafio de un pipeline CI - crear un entorno espejo de produccion efimero que se genere sobre la marcha con todos los servicios necesarios puede ser tedioso y muy costoso. Buscando la mejor opcion: [Docker-compose](https://docs.docker.com/compose/) permite crear un entorno dockerizado aislado con mismos contenedores usando un unico fichero de texto plano, pero con la tecnologia por debajo (redes, despliegues) ser diferentes a las de produccion. Puedes combinarlo con [‘AWS Local’](https://github.com/localstack/localstack) para trabajr en hacer stubs de servicios AWS de verdad. Si usas [serverless](https://serverless.com/) existen multiple frameworks como serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) que te permiten invocar código Faas en local + +El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para la duplicación en local y CI, aunque salen herramientas nuevas cada día. Un enfoque puede ser ejecutar implementaciones de 'kubernetes reducidos' con [Minikube](https://kubernetes.io/docs/setup/minikube/) o [MicroK8s](https://microk8s.io/) que es igual que el kubernetes completo pero con menos complejidad. Otro enfoque es tener un kubernetes de verdad remoto para test, algunso proveedores de CI (por ejemplo [Codefresh](https://codefresh.io/)) tiene integración nativa con entronos kubernetes y pueden facilmente ejecutar el pipeline CI sobre algo más real, otro permiten ejecutar scripts customizado contra kubernetes remotos -The huge Kubernetes eco-system is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes.
    -❌ **De lo contrario:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated +❌ **De lo contrario:** El uso de diferentes tecnologías para la producción y los test exige mantener dos modelos de implementación y mantiene a los desarrolladores y al equipo de operaciones separados
    @@ -1740,7 +1741,7 @@ The huge Kubernetes eco-system is yet to formalize a standard convenient tool fo
    -### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly
    ([Credit: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) +### :clap: Ejemplo: un pipeline de CI que genera un clúster de Kubernetes al vuelo ([Credito: 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
    From 2ac01c0aaed5af011105e3d79fa8371c11e420ef Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 13:01:13 +0200 Subject: [PATCH 255/502] translated up to 5.4 --- readme-es.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-es.md b/readme-es.md index 75c79fb6..1a721c93 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1749,11 +1749,11 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l

    -## ⚪ ️5.4 Parallelize test execution +## ⚪ ️5.4 Parallelizar la ejecución de los test -:white_check_mark: **Haz:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes +:white_check_mark: **Haz:** Cuando se hace correctamente, los test son tu amigo 24/7 propocionando feedback instantaneo. En la practica, ejecutar 500 test unitarios en un solo proceso en CPU puede llevar demasiado tiempo. Afortunadamente, los test runner más modernos y las plataformas de CI (como [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) y [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) pueden paralelizar los test en multiples procesos y lograr una mejora importante en el tiempo en entregar feedback. Algunos proveedores de CI también paralelizan las pruebas en contenedores (!) lo que acorta aún más la entrega de feedback. Ya sea localmente con multiples procesos, o sobre algun CLI en cloud usando multiples maquinas - pararelizar la demanda manteniendo los test autonomos para que cada uno pueda ejecutarse en diferentes procesos -❌ **De lo contrario:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant +❌ **De lo contrario:** Obtener el resultado de los test en 1 hora despues de hacer push de código nuevo, mientras desarrollas nuevas funcionalidades, es la mejor receta para quitarle relevancia a los test
    @@ -1761,9 +1761,9 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l
    -### :clap: Ejemplo de cómo hacerlo correctamente: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :clap: Ejemplo de cómo hacerlo correctamente: Mocha parallel y Jest superan facilmente al Mocha tradicional gracias a la paralelización de la ejecución de los test ([Credito: 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)") +![alt text](assets/bp-24-yonigoldberg-jest-parallel.png "Mocha parallel y Jest superan facilmente al Mocha tradicional gracias a la paralelización de la ejecución de los test (Credito: JavaScript Test-Runners Benchmark)")
    From 922a9e8b647e0ad70d390013ca76bde9bf6133fe Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 13:09:16 +0200 Subject: [PATCH 256/502] translated up to 5.5 --- readme-es.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-es.md b/readme-es.md index 1a721c93..caa36006 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1769,11 +1769,11 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l

    -## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check +## ⚪ ️5.5 Mantente al margen de problemas legales usando la verificación de licencia y plagio -:white_check_mark: **Haz:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stackoverflow and apparently violates some copyrights +:white_check_mark: **Haz:** Los problemas de licencias y plagio seguramente no son tu prioridad ahora mismo, pero ¿por que no hacer check en esta tarea en solo 10 minutos? Existen muchos paquetes npm como [license check](https://www.npmjs.com/package/license-checker) y [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial pero con plan gratuito) que puedes integrar facilmente en tu pipeline CI e inspeccionar en busca de problemas como dependencias con licencias restrictivas, o código que fue copiado y pegado de stackoverflow que aparentemente viola algunos copyrights -❌ **De lo contrario:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues +❌ **De lo contrario:** Sin querer, los desarrolladores pueden usar paquete con licencias inapropiadas o copiar y pegar código comercial y encontrarse con problemas legales
    @@ -1784,10 +1784,10 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l ### :clap: Ejemplo de cómo hacerlo correctamente: ```javascript -//install license-checker in your CI environment or also locally +//instala license-checker en tu entorno de CI o localmente 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 +//pidele que escanee todas las licencias y que termine con un codigo distindo de 0 si encuentra una licencia no autorizada. De esta forma el pipeline de CI puede detectarlo y detenerse license-checker --summary --failOn BSD ``` From 8714245109a430d7d4e00491761c41d5fa477808 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 13:14:19 +0200 Subject: [PATCH 257/502] translated up to 5.6 --- readme-es.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-es.md b/readme-es.md index caa36006..76ffb256 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1800,11 +1800,11 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.6 Constantly inspect for vulnerable dependencies +## ⚪ ️5.6 Verifica constantemente las dependencias vulnerables -:white_check_mark: **Haz:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +:white_check_mark: **Haz:** Incluso las dependencias más conocidas como express, tiene vulnerabilidades conocidas. Puedes dominarlas facilmente usando herramientas libres como [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), o commerciales como [snyk](https://snyk.io/) (que tiene una version community gratuita). Ambas pueden ser ejecutadas desde tu CI en cada construcción -❌ **De lo contrario:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious +❌ **De lo contrario:** Mantener tu código limpio de vulnerabilidades sin herramientas dedicadas requiere que estes revisando constantemente las nuevas versiones. Muy tedioso
    @@ -1812,9 +1812,9 @@ license-checker --summary --failOn BSD
    -### :clap: Example: NPM Audit result +### :clap: Ejemplo: resultado de NPM Audit -![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") +![alt text](assets/bp-26-npm-audit-snyk.png "resultado de NPM Audit")
    From d5dd615536ea05a6eddbc156f57e56dbeafa5447 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 13:42:13 +0200 Subject: [PATCH 258/502] translated up to 5.7 --- readme-es.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/readme-es.md b/readme-es.md index 76ffb256..d939cef8 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1820,18 +1820,19 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.7 Automate dependency updates +## ⚪ ️5.7 Automatiza la actualización de dependencias -:white_check_mark: **Haz:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: +:white_check_mark: **Haz:** La introducción en yarn and npm de package-lock.json introduce un desafio muy importante ( el camino al infierno esta lleno de buenas intenciones) — por defecto ahora, lo paquetes no se auto actualiza más. Incluso en un equipo que ejecute un 'npm install' limpio y 'npm update' no van a caer nuevas actualizaciones. Esto conduce a veriones de paquetes desactualizadas en el mejor de los casos, y en el peor, a codigo vulnerable. Los equipos pasan a depender de la buena voluntad y memoria de los desarrolladores para actualizar el package.json a mano o a utilizar herramientas como [ncu](https://www.npmjs.com/package/npm-check-updates) manualmente. Una formula mucho mejor seria automatizar el proceso de actualizar las versiones de las dependenecias en las que más confiamos, pero no hay una solucion perfecta, existen dos caminos para esta actualización: -(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. +(1) Podemos hacer que el CI falle con dependencias obsoletas — usando herramientas como [‘npm outdated’](https://docs.npmjs.com/cli/outdated) o ‘npm-check-updates (ncu)’. Hacerlo obligará a los desarrolladores a actualizar las dependencias -(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). +(2) Usar alguna de las herramientas comerciales que escanean nuestro codigo y envian automaticamente pull-request con actualización de dependencias. Una pregunta interesante que nos queda es cual va a ser la politica de estas actualizaciones: si actualizamos cada parche se genera mucha sobrecarga, y hacerlo cuando haya una version major nos lleva directos a usar verisones inestables o incompatibles (mucho paquetes muestran vulnerabilidades justo despues de salir una version nueva [lee sobre](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) el incidente de eslint-scope). + +Una politica de actualizaciones eficiente puede permitir cierto 'periodo de concesion' - deja pasar versiones quedandote por detras de @latest un tiempo antes de considerar que tu versión en local está obsoleta (por ejemplo, la versión que tienes en local es 1.3.1 y la versión del repositorio del paquete es 1.3.8) -An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8)
    -❌ **De lo contrario:** Your production will run packages that have been explicitly tagged by their author as risky +❌ **De lo contrario:** Producción estara ejecutando versiones de paquetes que han sido marcadas por los propios autores como version con riesgos
    @@ -1839,9 +1840,9 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions +### :clap: Ejemplo: [ncu](https://www.npmjs.com/package/npm-check-updates) puede ser usado a mano o en un pipline de CI para detectar en que medida el lag de retraso contra la versión @latest -![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") +![alt text](assets/bp-27-yoni-goldberg-npm.png "ncu puede ser usado a mano o en un pipline de CI para detectar en que medida el lag de retraso contra la versión @latest")
    @@ -1871,7 +1872,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +### :clap: Ejemplo: Using Travis (CI vendor) build definition to run the same test over multiple Node versions
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    From fdd6eeb8c743f79ea917293c02c2e043f74e4e7b Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 13:56:16 +0200 Subject: [PATCH 259/502] translated up to 5.8 --- readme-es.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme-es.md b/readme-es.md index d939cef8..ca1ea4c7 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1848,14 +1848,14 @@ Una politica de actualizaciones eficiente puede permitir cierto 'periodo de conc

    -## ⚪ ️ 5.8 Other, non-Node related, CI tips +## ⚪ ️ 5.8 Otros consejos de CI no relacionados con Node -:white_check_mark: **Haz:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +:white_check_mark: **Haz:** Esta publicación se centra en los consejos sobre testing que están relacionados, o al menos pueden ejemplificarse con el Node JS. Sin embargo, este punto agrupa algunos consejos no relacionados con Node que son bien conocidos -
    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
    +
    1. Use una sintaxis declarativa. Esta es la única opción para la mayoría de los pipelines de CI, pero las versiones antiguas de Jenkins permiten usar código o incluos una interfaz de usuario
    2. Opte por un CI que tenga soporte nativo de Docker
    3. Falla antes, ejecuta tus test más rápidas primero. Crea un paso / hito de 'Smoke testing' que agrupe múltiples verificaciones rápidas (por ejemplo, linter, test unitarios) y que proporcione comentarios rápidamente al desarrollador que haya commiteado
    4. Facilite la exploración de todos las partes de construcción, incluidos reportes de test, informes de cobertura, informes de mutación, registros, etc.
    5. Crea múltiples pipelines / jobs para cada evento y reutilice los pasos entre ellos. Por ejemplo, configure un job para confirmaciones de commits a ramas de desarrollo y uno diferente para la rama master. Permita la reutilización de la logica usando pasos compartidos en los pipelines (la mayoría de los proveedores proporcionan algún mecanismo para la reutilización de código)
    6. Nunca uses secretos directamente en la declaración del job, traelos de un store de secretos o de la configuración del propio job
    7. Suba explícitamente la versión de una release o al menos asegúrate de que el desarrollador lo hizo
    8. Construye solo una vez y realiza todas las inspecciones sobre el artefacto construido único (por ejemplo, imagen Docker)
    9. Testea en un entorno efímero que no arrastre el estado entre las construcciones. El almacenamiento en caché de node_modules podría ser la única excepción

    -❌ **De lo contrario:** You‘ll miss years of wisdom +❌ **De lo contrario:** Echarás de menos años de sabiduría

    From 44e7d76d991520e06cc786fd9402dafe8d441827 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 14:03:33 +0200 Subject: [PATCH 260/502] translated up to 5.9 --- readme-es.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme-es.md b/readme-es.md index ca1ea4c7..eceefa8d 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1859,12 +1859,12 @@ Una politica de actualizaciones eficiente puede permitir cierto 'periodo de conc

    -## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions +## ⚪ ️ 5.9 Build matrix: Ejecuta los mismos pasos de CI usando multiples versiones de Node -:white_check_mark: **Haz:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +:white_check_mark: **Haz:** En control de calidad influye totalmente la casualidad, cuanto más terreno cubras, más suerte tendrás al detectar problemas temprano. Al desarrollar paquetes reutilizables o ejecutar un producto multicliente con varias configuraciones y versiones de Node, el CI debe ejecutar el pipeline de test sobre todas las combinaciones de configuraciones. Por ejemplo, suponiendo que usemos MySQL para algunos clientes y Postgres para otros, algunos proveedores de CI admiten una característica llamada 'Matrix' que permite ejecutar todos los test contra todas las combinaciones de MySQL, Postgres y versiones de Node múltiples como 8, 9 y 10. Esto se hace utilizando la configuración solo, sin ningún esfuerzo adicional (suponiendo que tenga test o cualquier otro control de calidad). Otros CI que no admiten Matrix pueden tener extensiones o ajustes para permitirlo
    -❌ **De lo contrario:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? +❌ **De lo contrario:** Entonces, después de hacer todo ese arduo trabajo de escribir los test, ¿vamos a dejar que los errores se cuelen solo por problemas de configuración?
    @@ -1872,7 +1872,7 @@ Una politica de actualizaciones eficiente puede permitir cierto 'periodo de conc
    -### :clap: Ejemplo: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +### :clap: Ejemplo: Usando la definicion de construcción de Travis (proveedor de CI) para correr los mismos test en diferentes versiones de Node
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    From 41d6aba766bb643eabbcfcb6b7e386819904a5e3 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Sat, 1 Aug 2020 14:11:19 +0200 Subject: [PATCH 261/502] translated complete --- readme-es.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/readme-es.md b/readme-es.md index eceefa8d..b8c939aa 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1879,7 +1879,7 @@ Una politica de actualizaciones eficiente puede permitir cierto 'periodo de conc

    -# Team +# Equipo ## Yoni Goldberg @@ -1887,15 +1887,15 @@ Una politica de actualizaciones eficiente puede permitir cierto 'periodo de conc
    -**Role:** Writer +**Papel:** Escritor -**About:** I'm an independent consultant who works with 500 fortune corporates 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) +**Acerda de:** Soy un consultor independiente que trabaja con empresas de 500 fortune y startups en garajes para pulir sus aplicaciones JS & Node.js. Más que ningún otro tema me fascina y tengo como objetivo dominar el arte del testing. También soy el autor de [Node.js Buenas Practicas](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) +**📗 Curso Online:** ¿Te gustó esta guía y deseas llevar tus habilidades de testing al máximo? Considera visitar mi curso completo [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com)
    -**Follow:** +**Sigeme:** - [🐦 Twitter](https://twitter.com/goldbergyoni/) - [📞 Contact](https://testjavascript.com/contact-2/) @@ -1907,30 +1907,30 @@ Una politica de actualizaciones eficiente puede permitir cierto 'periodo de conc ## [Bruno Scheufler](https://github.com/BrunoScheufler) -**Role:** Tech reviewer and advisor +**Rol:** Revisor técnico y asesor -Took care to revise, improve, lint and polish all the texts +Se encargó de revisar, mejorar, lintear y pulir todos los textos -**About:** full-stack web engineer, Node.js & GraphQL enthusiast +**Acerda de:** Ingeniero full-stack web, entusiasta de Node.js y GraphQL

    ## [Ido Richter](https://github.com/idori) -**Role:** Concept, design and great advice +**Rol:** Concepto, diseño y buenos consejos -**About:** A savvy frontend developer, CSS expert and emojis freak +**Acerda de:** Un desarrollador frontend inteligente, experto en CSS y friki de los emojis ## [Kyle Martin](https://github.com/js-kyle) -**Role:** Helps keep this project running, and reviews security related practices +**Rol:** Ayuda a mantener este proyecto en funcionamiento y revisa los test relacionadas con la seguridad -**About:** Loves working on Node.js projects and web application security. +**Acerda de:** Le encanta trabajar en proyectos Node.js y seguridad de aplicaciones web -## Contributors ✨ +## Contribuyentes ✨ -Thanks goes to these wonderful people who have contributed to this repository! +¡Gracias a estas personas maravillosas que han contribuido a este repositorio! From 41e776ea549b7ffb7c68ae23a8b59bc11aa3b69a Mon Sep 17 00:00:00 2001 From: Colder Date: Sat, 5 Sep 2020 23:13:15 +0800 Subject: [PATCH 262/502] Fix typo --- readme-zh-CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-zh-CN.md b/readme-zh-CN.md index 777107b1..0b0e1ac4 100644 --- a/readme-zh-CN.md +++ b/readme-zh-CN.md @@ -1340,7 +1340,7 @@ test('When no products exist, show the appropriate message', () => { ## ⚪ ️ 3.7 写几个跨越整个系统的端到端测试 -:white_check_mark: **建议:** 虽然 E2E (端到端) 通常表示在真实浏览器中进行 UI 测试(见 3.6),但某些情况下,它们表示覆盖整个系统的测试,包括真正的后端。后一种测试非常有价值,因为它们涵盖了前端和后端之间的集成 bug,这些 bug 可能是由于沟通 schema 时产生误会导致的。它们也是一种有效的方法来发现 backend-to-backend 集成问题 (例如微服务 A 将错误的信息发送给微服务 B) 甚至检测部署失败,目前后端没有像 [Cypress](https://www.cypress.io/) 和 [Pupeteer](https://github.com/GoogleChrome/puppeteer) 友好的 UI 框架一样友好且成熟的 E2E 框架。这种测试的缺点是,配置一个包含如此多组件的环境的成本很高,而且大多数组件都很脆弱——假设有 50 个微服务,即使其中一个失败,整个 E2E 也会失败。出于这个原因,我们应该少用这种技术,大概1-10个就够了。也就是说,即使是少量的 E2E 测试也很有可能捕获它们所针对的问题——部署和集成故障。建议在与生产环境相似的预发运行它们。 +:white_check_mark: **建议:** 虽然 E2E (端到端) 通常表示在真实浏览器中进行 UI 测试(见 3.6),但某些情况下,它们表示覆盖整个系统的测试,包括真正的后端。后一种测试非常有价值,因为它们涵盖了前端和后端之间的集成 bug,这些 bug 可能是由于沟通 schema 时产生误会导致的。它们也是一种有效的方法来发现 backend-to-backend 集成问题 (例如微服务 A 将错误的信息发送给微服务 B) 甚至检测部署失败,目前后端没有像 [Cypress](https://www.cypress.io/) 和 [Puppeteer](https://github.com/GoogleChrome/puppeteer) 友好的 UI 框架一样友好且成熟的 E2E 框架。这种测试的缺点是,配置一个包含如此多组件的环境的成本很高,而且大多数组件都很脆弱——假设有 50 个微服务,即使其中一个失败,整个 E2E 也会失败。出于这个原因,我们应该少用这种技术,大概1-10个就够了。也就是说,即使是少量的 E2E 测试也很有可能捕获它们所针对的问题——部署和集成故障。建议在与生产环境相似的预发运行它们。
    From 6b8406d125f5e75896e57a72befcb9940ae53707 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2020 11:29:59 +0000 Subject: [PATCH 263/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index b1b6b5f2..ab33fd8a 100644 --- a/readme.md +++ b/readme.md @@ -1951,6 +1951,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    roywalker

    🖋
    dangen

    🖋
    biesiadamich

    🖋 +
    Yanlin Jiang

    🖋 From a56caea89d5d9466637dd0ba8b217128bac98718 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2020 11:30:00 +0000 Subject: [PATCH 264/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 49b901d0..811feada 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -240,6 +240,15 @@ "contributions": [ "content" ] + }, + { + "login": "cncolder", + "name": "Yanlin Jiang", + "avatar_url": "https://avatars3.githubusercontent.com/u/127009?v=4", + "profile": "https://tarojsx.github.io", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From aacd28cd9fb76845598f4c2a48cd9a0a35ba7708 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2020 12:32:32 +0000 Subject: [PATCH 265/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index ab33fd8a..b4b65977 100644 --- a/readme.md +++ b/readme.md @@ -1952,6 +1952,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    dangen

    🖋
    biesiadamich

    🖋
    Yanlin Jiang

    🖋 +
    sanguino

    🖋 From 6b3036f74f3918c31384049017b76482ea7afa08 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2020 12:32:33 +0000 Subject: [PATCH 266/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 811feada..9b5ea4b3 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -249,6 +249,15 @@ "contributions": [ "content" ] + }, + { + "login": "sanguino", + "name": "sanguino", + "avatar_url": "https://avatars2.githubusercontent.com/u/2077168?v=4", + "profile": "https://github.com/sanguino", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From e89f660e0bc55e05a4f530479e21107e1cb9da77 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2020 12:39:28 +0000 Subject: [PATCH 267/502] docs: update readme.md [skip ci] --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index b4b65977..6dbf17d1 100644 --- a/readme.md +++ b/readme.md @@ -1954,6 +1954,9 @@ Thanks goes to these wonderful people who have contributed to this repository!
    Yanlin Jiang

    🖋
    sanguino

    🖋 + +
    Morgan

    🖋 + From b73f62c3de4b289ce3e7f893892e872aeae2108e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2020 12:39:29 +0000 Subject: [PATCH 268/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 9b5ea4b3..ac431a01 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -258,6 +258,15 @@ "contributions": [ "content" ] + }, + { + "login": "MorganGeek", + "name": "Morgan", + "avatar_url": "https://avatars0.githubusercontent.com/u/3721240?v=4", + "profile": "https://github.com/MorganGeek", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From dc3922c9a52a03c1b6e7c91f73d2ec047ab70ea7 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2020 12:41:46 +0000 Subject: [PATCH 269/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 1482f06b..69db165a 100644 --- a/readme.md +++ b/readme.md @@ -1956,6 +1956,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    Morgan

    🖋 +
    Lukas Bischof

    ⚠️ 🖋 From 0ba53dd20360386078fe2b3857c24c0647e75b30 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2020 12:41:47 +0000 Subject: [PATCH 270/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index ac431a01..48851b54 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -267,6 +267,16 @@ "contributions": [ "content" ] + }, + { + "login": "lukasbischof", + "name": "Lukas Bischof", + "avatar_url": "https://avatars0.githubusercontent.com/u/8350985?v=4", + "profile": "https://luk4s.dev", + "contributions": [ + "test", + "content" + ] } ], "projectName": "javascript-testing-best-practices", From ad817c0c864df25f7853b75bb999e99b3ca203a6 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Mon, 21 Sep 2020 00:54:26 +0200 Subject: [PATCH 271/502] Spanish transaltation: removed some typos, manual review up to 1.5 --- readme-es.md | 213 ++++++++++++++++++++++++++------------------------- 1 file changed, 107 insertions(+), 106 deletions(-) diff --git a/readme-es.md b/readme-es.md index b8c939aa..21ba8092 100644 --- a/readme-es.md +++ b/readme-es.md @@ -2,21 +2,21 @@
    -# 👇 Por qué esta guia puede hacerte llevar tus habilidades de testing al siguiente nivel +# 👇 Por qué esta guía puede hacerte llevar tus habilidades de testing al siguiente nivel
    -## 📗 45+ buenas practicas: Super comprensiva y exhaustiva +## 📗 45+ buenas practicas: Súper comprensiva y exhaustiva -Esta es una guia completa para Javascript y Node de la A a la Z. Resumen y selecciona docenas de los mejores post de blogs, libros, y herramientas ofrecidas por el mercado +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 -## 🚢 Advanzado: Va 10.000 kilometros más allá de lo basico +## 🚢 Avanzado: Va 10.000 kilómetros más allá de lo básico -Súbete a un viaje que va más allá de lo básico, llegando a temas avanzados como testeando en producción, mutation testing, property-based testing y muchas otras herramientas estratégicas y profesionales. Si lees esta guía completamente es probable que tus habilidades de testing este por encima de la media +Súbete a un viaje que va más allá de lo básico, llegando a temas avanzados como testeando en producción, mutation testing, property-based testing y muchas otras herramientas estratégicas y profesionales. Si lees esta guía completamente es probable que tus habilidades de testing acaben por encima de la media ## 🌐 Full-stack: front, backend, CI, cualquier cosa -Empieza por comprender las tecnicas de testing ubicuas que son la base de cualquier nivel de aplicación. Luego, profundice en tu área de elección: frontend/UI, backend, CI o tal vez todos. +Empieza por comprender las técnicas de testing ubicuas que son la base de cualquier nivel de aplicación. Luego, profundiza en tu área de elección: frontend/UI, backend, CI o tal vez todos
    @@ -24,16 +24,17 @@ Empieza por comprender las tecnicas de testing ubicuas que son la base de cualqu - Consultor JavaScript & Node.js - 📗 [Testing Node.js & JavaScript de la A a la Z](https://www.testjavascript.com) - Mi curso completamente online con más de [10 horas de video](https://www.testjavascript.com), 14 tipos de test y más de 40 buenas practicas -- [Sigueme en Twitter ](https://twitter.com/goldbergyoni/) +- [Sígueme en Twitter ](https://twitter.com/goldbergyoni/)
    ### Traducciones - leelas en tu propio idioma -- 🇨🇳[Chino](readme-zh-CN.md) - cortesia de [Yves yao](https://github.com/yvesyao) -- 🇰🇷[Coreano](readme.kr.md) - cortesia de [Rain Byun](https://github.com/ragubyun) -- 🇵🇱[Polaco](readme-pl.md) - cortesia de [Michal Biesiada](https://github.com/mbiesiad) -- Quieres traducir a tu propio lenguaje? porfavor abre una issue 💜 +- 🇨🇳[Chino](readme-zh-CN.md) - cortesía de [Yves yao](https://github.com/yvesyao) +- 🇰🇷[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) +- ¿Quieres traducir a tu propio lenguaje? por favor abre una issue 💜

    @@ -45,23 +46,23 @@ Un solo consejo que inspira a todos los demás (1 apartado especial) #### [`Sección 1: La Anatomía de un Test`](#section-1-the-test-anatomy-1) -La base - estructurando tests claros (12 apartados) +La base - estructurando test claros (12 apartados) #### [`Sección 2: Backend`](#section-2️⃣-backend-testing) -Escribiendo test de backend y Microservicios eficientemente (8 apartados) +Escribiendo test de backend y Micro Servicios eficientemente (8 apartados) #### [`Sección 3: Frontend`](#section-3️⃣-frontend-testing) Escribiendo test para web UI incluyendo test de componente y E2E (11 apartados) -#### [`Sección 4: Midiendo la Efectividad de los Tests`](#section-4️⃣-measuring-test-effectiveness) +#### [`Sección 4: Midiendo la Efectividad de los Test`](#section-4️⃣-measuring-test-effectiveness) Vigilando al vigilante - midiendo la calidad de los test (4 apartados) #### [`Sección 5: Integración Continua`](#section-5️⃣-ci-and-other-quality-measures) -Pautas para Integracion Continua en el mundo de JS (9 apartados) +Pautas para Integración Continua en el mundo de JS (9 apartados)

    @@ -72,17 +73,17 @@ Pautas para Integracion Continua en el mundo de JS (9 apartados) ## ⚪️ 0 La Regla de Oro: Diseñando testing ligero :white_check_mark: **Haz:** -El código de los test no es como el código de producción - diséñelo para que sea simple, corto, sin abstracciones, plano, agradable de trabajar, ligero. Uno debe mirar un test y entender la intención al instante. +El código de los test no es como el código de producción - diséñalo para que sea simple, corto, sin abstracciones, plano, agradable de trabajar, ligero. Uno debe mirar un test y entender la intención al instante -Nuestras mentes están llenas por el código de producción, no tenemos espacio de cabeza para añadir más cosas complejas. Si intentamos introducir otro código desafiante en nuestro cerebro, ralentizará al todo el equipo, lo que va en contra de la razón por la que hacemos testing. Prácticamente aquí es donde muchos equipos simplemente abandonan el testing. +Nuestras mentes están llenas por el código de producción, no tenemos espacio de cabeza para añadir más cosas complejas. Si intentamos que introducir otro código desafiante en nuestro cerebro, ralentizará todo el equipo, lo que va en contra de la razón por la que hacemos testing. Prácticamente esta una de las razones por la que muchos equipos simplemente abandonan el testing -Los test son una oportunidad de tener algo más: un asistente amable y sonriente, con el que es un placer trabajar y da mucho valor a cambio de una inversión tan pequeña. La ciencia nos dice que tenemos dos sistemas cerebrales: el sistema 1 se usa para actividades sin esfuerzo como conducir un automóvil en una carretera vacía y el sistema 2, que está destinado a operaciones complejas y conscientes como resolver una ecuación matemática. Diseña tus test para el sistema 1, cuando observes el código de un test, debería parecer tan fácil como modificar un documento HTML y no como resolver 2X(17 × 24). +Los test son una oportunidad de tener algo más: un asistente amable y sonriente, con el que es un placer trabajar y da mucho valor a cambio de una inversión muy pequeña. La ciencia nos dice que tenemos dos sistemas cerebrales: el sistema 1 se usa para actividades sin esfuerzo como conducir un automóvil en una carretera vacía y el sistema 2, que está destinado a operaciones complejas y conscientes como resolver una ecuación matemática. Diseña tus test para el sistema 1, cuando observes el código de un test, debería parecer tan fácil como modificar un documento HTML y no como resolver 2X(17 × 24) -Esto se puede lograr mediante la seleccion de técnicas cuidadosamente, herramientas y objetivos de test que son rentables y proporcionan un gran retorno de la inversión. Testee solo lo que sea necesario, esfuércese por mantenerlo ágil, a veces incluso vale la pena abandonar algunos test y cambiar la confiabilidad por agilidad y simplicidad. +Esto se puede lograr mediante la selección de técnicas cuidadosamente, herramientas y objetivos de test que son rentables y proporcionan un gran retorno de la inversión. Testea solo lo que sea necesario, esfuérzate por mantenerlo ágil, a veces incluso vale la pena abandonar algunos test y cambiar la confiabilidad por agilidad y simplicidad -![alt text](/assets/headspace.png "No tenemos espacio para la más complejidad") +![alt text](/assets/headspace.png "No tenemos espacio para más complejidad") -La mayoría de los siguientes consejos son derivados de este principio. +La mayoría de los siguientes consejos son derivados de este principio ### ¿Listo para empezar? @@ -94,7 +95,7 @@ La mayoría de los siguientes consejos son derivados de este principio. ## ⚪ ️ 1.1 Incluye 3 partes en los nombres de tus test -:white_check_mark: **Haz:** El reporte de un test debe indicar si la revisión de la aplicación actual cumple los requisitos para las personas que no están necesariamente familiarizadas con el código: el tester, el DevOps que está desplegangolo y el futuro tú de dentro de dos años. Esto se puede lograr si los test hablan al nivel de los requisitos e incluyen 3 partes: +:white_check_mark: **Haz:** El reporte de un test debe indicar si la revisión de la aplicación actual cumple los requisitos para las personas que no están necesariamente familiarizadas con el código: el tester, el DevOps que está desplegángolo y el futuro tú de dentro de dos años. Esto se puede lograr si los test hablan al nivel de los requisitos e incluyen 3 partes: (1) ¿Qué se está testeando? Por ejemplo, el método ProductsService.addNewProduct @@ -104,7 +105,7 @@ La mayoría de los siguientes consejos son derivados de este principio.
    -❌ **De lo contrario:** Un despliegue simplemente ha fallado, un test llamado "Agregar producto" ha fallado. ¿Esto te dice exactamente qué está funcionando mal? +❌ **De lo contrario:** Un despliegue falla, un test llamado "Agregar producto" ha fallado. ¿Esto te dice exactamente qué está funcionando mal?
    @@ -142,7 +143,7 @@ describe('Products Service', function() {

    -
    © Creditos y más información +
    © Créditos y más información 1. Roy Osherove - Naming standards for unit tests
    @@ -150,17 +151,17 @@ describe('Products Service', function() { ## ⚪ ️ 1.2 Estructura tus test con el patron AAA -:white_check_mark: **Haz:** Estructura tus test con 3 secciones bien separadas Arreglar u organizar, Actuar y Afirmar (AAA en inglés Arrange, Act & Assert). Seguir esta estructura garantiza que quien lea nuestro test no gaste CPU-cerebro en comprender los test: +:white_check_mark: **Haz:** Estructura tus test con 3 secciones bien separadas Ajustar, Actuar y Afirmar (AAA en inglés Arrange, Act & Assert). Seguir esta estructura garantiza que quien lea nuestro test no se estruje el cerebro en comprender los test: -1st A - Arreglar: configura el código acercarte al escenario que el test pretende simular. Esto podría incluir crear instancias de la unidad bajo el constructor del test, agregar registros de base de datos, mocks y stubs de objetos y cualquier otro código necesario +1a A - Ajustar: configura el código, crea el escenario que el test pretende simular. Esto podría incluir crear instancias de la unidad bajo el constructor del test, agregar registros de base de datos, mocks y stubs de objetos y cualquier otro código necesario -2nd A - Actuar: Ejecuta la unidad en test. Normalmente 1 línea de código +2a A - Actuar: Ejecuta la unidad en test. Normalmente 1 línea de código -3rd A - Afirmar: Comprobar que el valor recibido satisface las expectativas. Normalmente 1 línea de código +3a A - Afirmar: Comprobar que el valor recibido satisface las expectativas. Normalmente 1 línea de código
    -❌ **De lo contrario:** No solo emplearas horas comprendiendo el código principal, si no que lo que deberia haber sido la parate mas simple del día (testing) ha estrujado tu cerebro +❌ **De lo contrario:** No solo emplearas horas comprendiendo el código principal, si no que lo que debería haber sido la parate mas simple del día (testing) te ha estrujado el cerebro
    @@ -175,7 +176,7 @@ describe('Products Service', function() { ```javascript describe("Customer classifier", () => { test("When customer spent more than 500$, should be classified as premium", () => { - //Arreglar / organizar + //Ajustar const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); @@ -190,7 +191,7 @@ describe("Customer classifier", () => {
    -### :thumbsdown: Ejemplo Anti Patrón: Sin separación, una pieza, más dificil de interpretar +### :thumbsdown: Ejemplo Anti Patrón: Sin separación, un bloque, más dificil de interpretar ```javascript test("Should be classified as premium", () => { @@ -205,9 +206,9 @@ test("Should be classified as premium", () => {

    -## ⚪ ️1.3 Describe las expectativas en el lenguaje del producto.: usa aserciones estilo BDD +## ⚪ ️1.3 Describe las expectativas en el lenguaje del producto: usa aserciones estilo BDD -:white_check_mark: **Haz:** Escribir el código de tus test de forma declarativa permite que aquel que lo lea tenga al instante la idea sin tener que catar CPU-cerebro. Cuando escribes el código de los test de forma imperativa estará lleno de condiciones lógicas y obligas al que lo lee a gastar mucho más tiempo en comprenderlo. En este caso, escribe el código de la forma más humana, de forma declarativa BDD usando `expect` o `should` y sin usar código personalizado. Si Chai o Jest no incluyen las aserciones que deseas y se hace muy repetitivo, siempre puede usar [extender Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) o escribir un [plugin de Chai](https://www.chaijs.com/guide/plugins/) +:white_check_mark: **Haz:** Escribir el código de tus test de forma declarativa permite que aquel que lo lea tenga al instante la idea sin tener que estrujarse el cerebro. Cuando escribes el código de los test de forma imperativa estará lleno de condiciones lógicas y obligas al que lo lee a gastar mucho más tiempo en comprenderlo. En este caso, escribe el código de la forma más humana, de forma declarativa con BDD, usando `expect` o `should` y sin usar código personalizado. Si Chai o Jest no incluyen las aserciones que deseas y se hace muy repetitivo, siempre puedes [extender Jest con Jest matcher](https://jestjs.io/docs/en/expect#expectextendmatchers) o escribir un [plugin de Chai](https://www.chaijs.com/guide/plugins/)
    ❌ **De lo contrario:** El equipo escribirá menos test y acabará marcando los test más molestos con .skip() @@ -218,7 +219,7 @@ test("Should be classified as premium", () => { ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha y Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") -### :thumbsdown: Ejemplo Anti Patrón: quien lea nuestro test deberá hojear un código largo e imperativo para conocer la historia del test +### :thumbsdown: Ejemplo Anti Patrón: quien lea nuestro test deberá enfrentarse a un código largo e imperativo para conocer la historia del test ```javascript test("When asking for an admin, ensure only ordered admins in results", () => { @@ -248,7 +249,7 @@ test("When asking for an admin, ensure only ordered admins in results", () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Ojear el siguiente test declarativo es muy sencillo +### :clap: Ejemplo de cómo hacerlo correctamente: Comprender el siguiente test declarativo es muy sencillo ```javascript it("When asking for an admin, ensure only ordered admins in results", () => { @@ -265,11 +266,11 @@ it("When asking for an admin, ensure only ordered admins in results", () => {

    -## ⚪ ️ 1.4 Acercarse al testing caja-negra: Testea solo metodos publicos +## ⚪ ️ 1.4 Acercarse al testing caja-negra: Testea solo métodos públicos -:white_check_mark: **Haz:** Testear las partes internas suele traer un gasto extra enorme para muy poco beneficio. Si tu código/API comprueba todo los resultado correctamente, ¿deberias perder las proximas 3 horas en comprobar como esta funcionando internamente y despues mantener esos test tan fragiles? Cada vez que se comprueba un comportamiento publico, la implementacion privada es implicitamente testeada y tus test se romperan solo si hay un problema concreto (por ejemplo una salida incorrecta). Este enfoque tambien es conocido como `behavioral testing` (testing de comportamiento). Por otro lado, si se testean las partes internas (caja-blanca) tu enfoque cambia de planificar la salida del componente a detalles minusculos, y tus test pueden romperse debido a refactors de código menores sin que se rompan los test de salida, por lo que aumenta tremendamente el mantenimiento de los mismos.
    +:white_check_mark: **Haz:** Testear las partes internas suele traer un gasto extra enorme para obtener muy poco beneficio. Si tu código/API comprueba todos los resultado posibles correctamente, ¿deberías perder las próximas 3 horas en comprobar como esta funcionando internamente y después mantener esos test tan frágiles? Cada vez que se comprueba un comportamiento público, la implementación privada es implícitamente testeada y tus test se romperán sólo si hay un problema concreto (por ejemplo una salida incorrecta). Este enfoque también es conocido como `behavioral testing` (testing de comportamiento). Por otro lado, si se testean las partes internas (caja-blanca) tu enfoque cambia de planificar la salida del componente a detalles minúsculos, y tus test pueden romperse debido a refactors de código menores sin que se rompan los test de salida, por lo que aumenta tremendamente el mantenimiento de los mismos
    -❌ **De lo contrario:** Tus test se comportaran como la fabula [que viene el lobo](https://es.wikipedia.org/wiki/El_pastor_mentiroso): gritando falsos positivos (por ejemplo, un test dalla por que se cambio el nombre a una variable provada). Como es de esperar, la gente empezara a ingnorar estos test hasta que un día ignoren un test de verdad... +❌ **De lo contrario:** Tus test se comportaran como la fabula de [que viene el lobo](https://es.wikipedia.org/wiki/El_pastor_mentiroso): gritando falsos positivos (por ejemplo, un test falla porque se cambio el nombre a una variable probada). Como es de esperar, la gente empezara a ignorar estos test hasta que un día ignoren un test de verdad...
    Código de Ejemplo @@ -282,11 +283,11 @@ it("When asking for an admin, ensure only ordered admins in results", () => { ```javascript class ProductService { - //este metodo es usado solo internamente + //este método es usado solo internamente //Cambiarle el nombre causara que el test falle calculateVATAdd(priceWithoutVAT) { return { finalPrice: priceWithoutVAT * 1.2 }; - //Cambiar el formatos de salida o nombre de la clave a continuacion hara que el test falle + //Cambiar el formatos de salida o nombre de la clave a continuación hará que el test falle } //public method getPrice(productId) { @@ -297,7 +298,7 @@ class ProductService { } it("White-box test: When the internal methods get 0 vat, it return 0 response", async () => { - //No hay ningún requisito para permitir a los usuarios calcular el IVA, solo existe el de mostrar el precio final al usuario. Sin embargo, falsamente insistimos en testear las partes privadas de la clase. + //No hay ningún requisito para permitir a los usuarios calcular el IVA, solo existe el de mostrar el precio final al usuario. Sin embargo, falsamente insistimos en testear las partes privadas de la clase expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0); }); ``` @@ -308,14 +309,14 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response", ## ⚪ ️ ️1.5 Eligiendo los dobles de los test correctamente: Evita mocks en favor de stubs y spies -:white_check_mark: **Haz:** Los dobles de los test son un mal necesario por que estan acoplados a las tripas de la aplicacion, sin embargo, algunos proporcionan un valor inmenso ([Aquí tienes un recordatorio sobre los dobles de los test: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **Haz:** Los dobles de los test son un mal necesario porque están acoplados a las tripas de la aplicación, sin embargo, algunos proporcionan un valor inmenso ([Aquí tienes un bueno recordatorio de que son los dobles de los test: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)) -Antes de usar un doble, hazte una simple pregunta: ¿Lo voy a usar para testear una funcionalidad que aparece, o puede aparecer, en el documento de requisitos? Si no, eso huele que estas testeando partes privadas. +Antes de usar un doble, hazte una simple pregunta: ¿Lo voy a usar para testear una funcionalidad que aparece, o puede aparecer, en el documento de requisitos? Si no, eso huele que estas testeando partes privadas -Por ejemplo, si quieres testear que tu app se comporta razonablemente cuando el servicio de pagos está caido, deberias hacer un stub del servicio de pagos y devolver un 'Sin respuesta' para asegurar que la unidad que está siendo testeada devuelve el valor correcto. Esto verifica que el comportamiento/respuesta/resultado de nuesta app en ciertos escenarios. Tambien podrias usar un spy para asegurar que un email ha sifo enviado cuando este servicio está caidom — esto es nuevamente una verificacion de comportamiento que probablemente aparezca en el documento de requisitos ("Enviar un correo electrónico si no se pudo guardar el pago"). En el lado opuesto, si se mockea el servicio de pagos y se asegura que se haya llamado con el tipado correcto — entonces tu test esta comprobando cosas internas que tienen nada que ver con la funcionalidad de la app y es muy probable que cambien con frecuencia +Por ejemplo, si quieres testear que tu app se comporta razonablemente cuando el servicio de pagos está caído, deberías hacer un stub del servicio de pagos y devolver un 'Sin respuesta' para asegurar que la unidad que está siendo testeada devuelve el valor correcto. Esto verifica que el comportamiento/respuesta/resultado de nuestra app en ciertos escenarios. También podrías usar un spy para asegurar que un email ha sido enviado cuando este servicio está caído — esto es nuevamente una verificación de comportamiento que probablemente aparezca en el documento de requisitos ("Enviar un correo electrónico si no se pudo guardar el pago"). En el lado opuesto, si se mockea el servicio de pagos y se asegura que se haya llamado con el tipado correcto — entonces tu test esta comprobando cosas internas que no tienen nada que ver con la funcionalidad de la app y es muy probable que cambien con frecuencia
    -❌ **De lo contrario:** Cualquier refactor de codigo te exigirá buscar todos los mocks que tengas y tendras que actualizarlos en consecuencia. Los test pasan de ser un amigo útil a una carga más +❌ **De lo contrario:** Cualquier refactor de código te exigirá buscar todos los mocks que tengas y tendrás que actualizarlos en consecuencia. Los test pasan de ser un amigo útil a una carga más
    @@ -331,7 +332,7 @@ Por ejemplo, si quieres testear que tu app se comporta razonablemente cuando el 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 () => { //Asumimos que ya hemos añadido un producto const dataAccessMock = sinon.mock(DAL); - //hmmm MAL: testear las partes internas es nuestro principal objetivo, en vez de ser un efecto secundario + //hmmm MAL: testear las partes internas está siendo nuestro principal objetivo, en vez de ser un efecto secundario dataAccessMock .expects("deleteProduct") .once() @@ -343,7 +344,7 @@ it("When a valid product is about to be deleted, ensure data access DAL was call
    -### :clap:Ejemplo de cómo hacerlo correctamente: los spies se centran en testear los requisitos pero como efecto secundario inevitable se estan testeando las partes internas +### :clap: Ejemplo de cómo hacerlo correctamente: los spies se centran en testear los requisitos pero como efecto secundario inevitable se están testeando las partes internas ```javascript it("When a valid product is about to be deleted, ensure an email is sent", async () => { @@ -367,7 +368,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 No uses “foo”, usa datos realistas -:white_check_mark: **Haz:** A menudo, los bugs de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar bugs temprano. Utiliza librerias dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de prodcucción. Por ejemplo, dichas librerias pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para estirar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Vea la próxima sección (test basados en propiedades). +:white_check_mark: **Haz:** A menudo, los bugs de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar bugs temprano. Utiliza librerias dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de prodcucción. Por ejemplo, dichas librerias pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para estirar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Vea la próxima sección (test basados en propiedades)
    ❌ **De lo contrario:** Todo tus test de desarrollo estaran en falsos verdes cuando uses datos sinteticos como "Foo", pero luego en produccion pueden ponerse en rojo cuando un hacker use cadenas extrañas como “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” @@ -421,10 +422,10 @@ it("Better: When adding new valid product, get successful confirmation", async ( ## ⚪ ️ 1.7 Testea muchas combinaciones de entrada utilizando test basados en propiedades -:white_check_mark: **Haz:** Por lo general elegimos pocos datos de entrada por cada test. Incluso cuando el formato de entrada se parece a datos reales (ver sección "no uses foo"), cubrimos solo unas pocas combinaciones de datos de entrada (method(‘’, true, 1), method(“string” , false” , 0)), Sin embargo, en producción, un API que es llamada con 5 parametros puede ser invocada por miles de permutaciones diferentes, una sola puede hacer que nuestro poceso falle ([ver Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). ¿Qué tal si pudieras escribir un solo test que envíe 1000 permutaciones de diferentes entradas automáticamente y capture qué entrada hace que código no devuelva la respuesta correcta? Los test basados en propiedades son una técnica que hace exactamente eso: al enviar todas las combinaciones de entrada posibles a la unidad que está siendo testada, aumenta la probabilidad de encontrar un bug. Por ejemplo, dado un metodo — addNewProduct(id, name, isDiscount) — las librerias compatibles llamaran a ese metodo con muchas combinaciones (numeros, textos y boleanos) como (1, “iPhone”, false), (2, “Galaxy”, true). Puedes ejecutar test basados en propiedades usando nuestra libreria de esta favorita (Mocha, Jest, etc) como [js-verify](https://github.com/jsverify/jsverify) o [testcheck](https://github.com/leebyron/testcheck-js) (mucho mejor documentada). Actualizado: Nicolas Dubien sugiere en los comentarios [checkout fast-check](https://github.com/dubzzz/fast-check#readme) que parece ofecer caracteristicas adicionales y es activamente mantenida. +:white_check_mark: **Haz:** Por lo general elegimos pocos datos de entrada por cada test. Incluso cuando el formato de entrada se parece a datos reales (ver sección "no uses foo"), cubrimos solo unas pocas combinaciones de datos de entrada (method(‘’, true, 1), method(“string” , false” , 0)), Sin embargo, en producción, un API que es llamada con 5 parametros puede ser invocada por miles de permutaciones diferentes, una sola puede hacer que nuestro poceso falle ([ver Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). ¿Qué tal si pudieras escribir un solo test que envíe 1000 permutaciones de diferentes entradas automáticamente y capture qué entrada hace que código no devuelva la respuesta correcta? Los test basados en propiedades son una técnica que hace exactamente eso: al enviar todas las combinaciones de entrada posibles a la unidad que está siendo testada, aumenta la probabilidad de encontrar un bug. Por ejemplo, dado un metodo — addNewProduct(id, name, isDiscount) — las librerias compatibles llamaran a ese metodo con muchas combinaciones (numeros, textos y boleanos) como (1, “iPhone”, false), (2, “Galaxy”, true). Puedes ejecutar test basados en propiedades usando nuestra libreria de esta favorita (Mocha, Jest, etc) como [js-verify](https://github.com/jsverify/jsverify) o [testcheck](https://github.com/leebyron/testcheck-js) (mucho mejor documentada). Actualizado: Nicolas Dubien sugiere en los comentarios [checkout fast-check](https://github.com/dubzzz/fast-check#readme) que parece ofecer caracteristicas adicionales y es activamente mantenida
    -❌ **De lo contrario:** Inconscientemente, eliges los datos de entradas para tus test que cubren solo las ramas de código que funcionan bien. Desafortunadamente, esto disminuye la eficiencia de los test como vehículo para detectar bugs. +❌ **De lo contrario:** Inconscientemente, eliges los datos de entradas para tus test que cubren solo las ramas de código que funcionan bien. Desafortunadamente, esto disminuye la eficiencia de los test como vehículo para detectar bugs
    @@ -458,14 +459,14 @@ describe("Product service", () => { ## ⚪ ️ 1.8 Si lo necesitas, usa solo snapshots cortos y en el propio test -:white_check_mark: **Haz:** Cuando hay necesidad de usar [snapshot testing](https://jestjs.io/docs/es-ES/snapshot-testing), usa solo snapshots cortos y bien enfocados (por ejemplo 3-7 lineas) y que esten incluidos en el propio test ([Inline Snapshot](https://jestjs.io/docs/es-ES/snapshot-testing#inline-snapshots)) y no como ficheros externos. Mantener esta dirección te garantiza que tus test se explican por si mismos y a la vez que sean menos fragiles. +:white_check_mark: **Haz:** Cuando hay necesidad de usar [snapshot testing](https://jestjs.io/docs/es-ES/snapshot-testing), usa solo snapshots cortos y bien enfocados (por ejemplo 3-7 lineas) y que esten incluidos en el propio test ([Inline Snapshot](https://jestjs.io/docs/es-ES/snapshot-testing#inline-snapshots)) y no como ficheros externos. Mantener esta dirección te garantiza que tus test se explican por si mismos y a la vez que sean menos fragiles -Por otro lado, los tutoriales y herramientas basados en ‘classic snapshots’ tienden a guardar ficheros muy grandes en medios externos (por ejemplo component rendering markup, API JSON result) cada vez que se ejecutan los test para comparar los resultados recividos con la version guardada. Esto, por ejemplo, puede aosciar nuestro test a 1000 lineas con 3000 valores de datoa que quien este escribiendo test jamas leera ni razonará. ¿Por qué está mal esto? Al hacerlo, hay 1000 razones para que tu test falle - tan solo el cambio de una linea de código es suficiente para que el snapshoot se invalide y es muy probable que esto ocurra a menudo. ¿Como de frecuente? cada espacio, comentatio o pequeño cambio de css/html. Y no solo eso, el nombre del test no nos va a dar ni una sola pista de que está fallando, solo verifica que esas 1000 lineas han cambiado. Además obliga a quien escribe los test a asumir como correcto un fichero enorme que no ha podido inspeccionar y corroborar. Todo estos son sintomas de una prueba oscura que no está bien enfocada y trata de cubrir demasiadas cosas a la vez +Por otro lado, los tutoriales y herramientas basados en ‘classic snapshots’ tienden a guardar ficheros muy grandes en medios externos (por ejemplo component rendering markup, API JSON result) cada vez que se ejecutan los test para comparar los resultados recividos con la version guardada. Esto, por ejemplo, puede aosciar nuestro test a 1000 lineas con 3000 valores de datoa que quien este escribiendo test jamas leera ni razonará. ¿Por qué está mal esto? Al hacerlo, hay 1000 razones para que tu test falle - tan solo el cambio de una linea de código es suficiente para que el snapshoot se invalide y es muy probable que esto ocurra a menudo. ¿Como de frecuente? cada espacio, comentatio o pequeño cambio de css/html. Y no solo eso, el nombre del test no nos va a dar ni una sola pista de que está fallando, solo verifica que esas 1000 lineas han cambiado. Además obliga a quien escribe los test a asumir como correcto un fichero enorme que no ha podido inspeccionar y corroborar. Todo estos son sintomas de un test oscuro que no está bien enfocado y trata de cubrir demasiadas cosas a la vez -Vale la pena señalar que hay algunos casos en los que los snapshoots grandes y externos son buenos - cuando comporbamos el esquema y no los datos (ignorando los valores y centrandonos en los campos) o en los casos en el que el documento no va a cambiar apenas en el tiempo. +Vale la pena señalar que hay algunos casos en los que los snapshoots grandes y externos son buenos - cuando comporbamos el esquema y no los datos (ignorando los valores y centrandonos en los campos) o en los casos en el que el documento no va a cambiar apenas en el tiempo
    -❌ **De lo contrario:** Un test UI falla. El codigo parece correcto, la pantalla esta pintando todos los pixels correctamente, ¿que ha pasado? tu test de snapshoot ha encontrado una diferencia entre el origen y lo que ha recibido al ejecutarse: simplemente hay un espacio añadido en cualquier parte... +❌ **De lo contrario:** Un test UI falla. El código parece correcto, la pantalla esta pintando todos los pixels correctamente, ¿que ha pasado? tu test de snapshoot ha encontrado una diferencia entre el origen y lo que ha recibido al ejecutarse: simplemente hay un espacio añadido en cualquier parte...
    @@ -479,7 +480,7 @@ Vale la pena señalar que hay algunos casos en los que los snapshoots grandes y ```javascript it("TestJavaScript.com is renderd correctly", () => { - //Arreglar + //Ajustar //Actuar const receivedPage = renderer @@ -499,7 +500,7 @@ it("TestJavaScript.com is renderd correctly", () => { ```javascript it("When visiting TestJavaScript.com home page, a menu is displayed", () => { - //Arreglar + //Ajustar //Actuar const receivedPage = renderer @@ -525,7 +526,7 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => { ## ⚪ ️1.9 Evitar fixtures globales y seeds, añade datos por cada test -:white_check_mark: **Haz:** Siguiendo la regla de oro (sección 0), cada test debe añadir y actuar en su propio conjunto de filas DB para evitar el acoplamiento y poder explicar fácilmente sobre el flujo del test. En realidad muchos testers se saltan esta regla al añadir datos a la DB solo una vez antes de ejecutar los test ([tambien conocido como ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) a favor de mejorar el rendimiento. Si bien el rendimiento es una preocupación válida — puede mitigarse de otras formas (consulte la sección "test de componentes"), sin embargo, la complegidad del test es más dolorosa que otras consideraciones la mayoria de las veces. De manera practica, haga que cada test añada explicitamente los registros que necesitas y actua sobre ellos. Si el rendimiento se convierte en algo critico — se puede llegar al compromiso de utilizar los mismos datos en un conjunto de test siempre que no se muten los datos (por ejemplo en queries). +:white_check_mark: **Haz:** Siguiendo la regla de oro (sección 0), cada test debe añadir y actuar en su propio conjunto de filas DB para evitar el acoplamiento y poder explicar fácilmente sobre el flujo del test. En realidad muchos testers se saltan esta regla al añadir datos a la DB solo una vez antes de ejecutar los test ([tambien conocido como ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) a favor de mejorar el rendimiento. Si bien el rendimiento es una preocupación válida — puede mitigarse de otras formas (consulte la sección "test de componentes"), sin embargo, la complegidad del test es más dolorosa que otras consideraciones la mayoria de las veces. De manera practica, haga que cada test añada explicitamente los registros que necesitas y actua sobre ellos. Si el rendimiento se convierte en algo critico — se puede llegar al compromiso de utilizar los mismos datos en un conjunto de test siempre que no se muten los datos (por ejemplo en queries)
    @@ -537,7 +538,7 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {
    -### :thumbsdown: Ejemplo Anti Patrón: tests no son independientes y dependenden de una inserción global de datos en la DB +### :thumbsdown: Ejemplo Anti Patrón: los test no son independientes y dependenden de una inserción global de datos en la DB ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha") @@ -583,7 +584,7 @@ it("When updating site name, get successful confirmation", async () => { ## ⚪ ️ 1.10 No capures errores, esperalos -:white_check_mark: **Haz:** Cuando queremos comprobar que una entrada lanza un error, nos puede parecer correcto usar try-catch-finally y afirmar que se entra por el catch. El resultado es un test incomodo y verboso (ejemplo a continuación) que nos oculta la intencion de un test muy simple y las expectativas del resultado +:white_check_mark: **Haz:** Cuando queremos comprobar que una entrada lanza un error, nos puede parecer correcto usar try-catch-finally y asertar que se entra por el catch. El resultado es un test incomodo y verboso (ejemplo a continuación) que nos oculta la intencion de un test muy simple y las expectativas del resultado Una alternativa más elegante seria usar solo la aserción de una sola linea que tiene Chai: expect(method).to.throw (o en Jest: expect(method).toThrow()). Es totalmente obligatorio también asegurarse de que la excepción contenga una propiedad que indique el tipo de error, de lo contratio, lanzando solo un error generico, la app no podra hacer mucho mas que mostrarle un error decepcionante para el usuario @@ -597,7 +598,7 @@ Una alternativa más elegante seria usar solo la aserción de una sola linea que
    -### :thumbsdown: Ejemplo Anti Patrón: Un test largo que trata de afirmar la existencia de un error con try-catch +### :thumbsdown: Ejemplo Anti Patrón: Un test largo que trata de asertar la existencia de un error con try-catch ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha") @@ -611,7 +612,7 @@ it("When no product name, it throws error 400", async () => { errorWeExceptFor = error; } expect(errorWeExceptFor).not.to.be.null; - //si esta afirmación falla, el resultado/reporte del testsolo mostrará + //si esta afirmación falla, el resultado/reporte del test solo mostrará //que algunos valores es null, no se monstrara que falta una excepción }); ``` @@ -634,11 +635,11 @@ it("When no product name, it throws error 400", async () => { ## ⚪ ️ 1.11 Tagea tus test -:white_check_mark: **Haz:** Deben ejecutarse diferentes tests en diferentes escenarios: quick smoke, IO-less, los tests deben ejecutarse cuando un desarrollador guarda o hace commit de un fichero, los test end-to-end suelen ejecutarse cuando un nuevo pull request es añadido, etc. Esto se puede lograr etiquetando los test con tags como #cold #api #sanity para que se pueda filtrar e invocar solo el subconjunto deseado. Por ejemplo, así es como se ejecutan solo el grupo sanity test con Mocha: mocha — grep ‘sanity’ +:white_check_mark: **Haz:** Deben ejecutarse diferentes test en diferentes escenarios: quick smoke, IO-less, los test deben ejecutarse cuando un desarrollador guarda o hace commit de un fichero, los test end-to-end suelen ejecutarse cuando un nuevo pull request es añadido, etc. Esto se puede lograr etiquetando los test con tags como #cold #api #sanity para que se pueda filtrar e invocar solo el subconjunto deseado. Por ejemplo, así es como se ejecutan solo el grupo sanity test con Mocha: mocha — grep ‘sanity’
    -❌ **De lo contrario:** Ejecutar todos los test, incluidos los test que realizan docenas de queries a DB, cada vez que un desarrollador hace un pequeño cambio, puede ser extremadamente lento y provocar que los desarrolladores ignoren correr los test. +❌ **De lo contrario:** Ejecutar todos los test, incluidos los test que realizan docenas de queries a DB, cada vez que un desarrollador hace un pequeño cambio, puede ser extremadamente lento y provocar que los desarrolladores ignoren correr los test
    @@ -646,7 +647,7 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Tagear los test como ‘#cold-test’ permite que el test runner ejecute solo los test más rápidos (Cold===tests rapidos que no estan haciendo operaciones de IO y que pueden ser ejecutados con frecuencia incluso mientras el desarrollador está escribiendo) +### :clap: Ejemplo de cómo hacerlo correctamente: Tagear los test como ‘#cold-test’ permite que el test runner ejecute solo los test más rápidos (Cold === 'test rapidos' que no estan haciendo operaciones de IO y que pueden ser ejecutados con frecuencia incluso mientras el desarrollador está escribiendo código) ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") @@ -668,11 +669,11 @@ describe("Order service", function() { ## ⚪ ️ 1.12 Categoriza los test en al menos 2 niveles -:white_check_mark: **Haz:** Aplique cierta estructura a su conjunto de test para que un visitante ocasional pueda comprender facilmente los requisitos (los test siempre son la mejor documentación) y los diversos escenarios que estamos testeando. Una practica comun para esto es crear al menos 2 bloques 'describe' antes de tus test: el primero es para el nombre de la unidad que está siendo testeada y el segundo es para un nivel adicional de categorización como el escenario o las categorias personalizadas (ver ejemplos de código y pantallazos más abajo). Hacerlo también mejorará los reportes de los test: quien los lea deducirá facilmente las categorias de los test, profundizará en aquellas que lo desee y podrá relacionar mejor los test fallidos. Además, será mucho más fácil para un desarollador navegar a traves del codigo de un cojunto de test amplio. Existen múltiples formas de estructurar tus test que deben ser consideradas, como [given-when-then](https://github.com/searls/jasmine-given) y [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **Haz:** Aplique cierta estructura a su conjunto de test para que un visitante ocasional pueda comprender facilmente los requisitos (los test siempre son la mejor documentación) y los diversos escenarios que estamos testeando. Una practica comun para esto es crear al menos 2 bloques 'describe' antes de tus test: el primero es para el nombre de la unidad que está siendo testeada y el segundo es para un nivel adicional de categorización como el escenario o las categorias personalizadas (ver ejemplos de código y pantallazos más abajo). Hacerlo también mejorará los reportes de los test: quien los lea deducirá facilmente las categorias de los test, profundizará en aquellas que lo desee y podrá relacionar mejor los test fallidos. Además, será mucho más fácil para un desarollador navegar a traves del código de un cojunto de test amplio. Existen múltiples formas de estructurar tus test que deben ser consideradas, como [given-when-then](https://github.com/searls/jasmine-given) y [RITE](https://github.com/ericelliott/riteway)
    -❌ **De lo contrario:** Cuando nos enfrentamos a un reporte con una lista plana de test, tendremos que leer rápidamente textos largos para determinar los escenarios principales y relacionar los test fallidos. Considera el siguiente caso: Cuando 7/100 test fallan, revisar una lista plana te exige leer el tesxto de las pruebas que fallan para ver como se relacionan entre ellas y que tienen en común. Sin embargo, en un reporte jerarquizado, si los 7 estan bajo un mismo flujo o categoria, puedes saber rápidamente cual o donde puede estar la causa raiz del fallo +❌ **De lo contrario:** Cuando nos enfrentamos a un reporte con una lista plana de test, tendremos que leer rápidamente textos largos para determinar los escenarios principales y relacionar los test fallidos. Considera el siguiente caso: Cuando 7/100 test fallan, revisar una lista plana te exige leer el texto de los test que fallan para ver como se relacionan entre ellos y que tienen en común. Sin embargo, en un reporte jerarquizado, si los 7 estan bajo un mismo flujo o categoria, puedes saber rápidamente cual o donde puede estar la causa raiz del fallo
    @@ -735,13 +736,13 @@ Aprenda y practique [principios TDD](https://www.sm-cloud.com/book-review-test-d # Section 2️⃣: Backend Testing -## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid +## ⚪ ️2.1 Enriqueze tu abanico de test: mira más allá de los test unitarios y la pirámide :white_check_mark: **Haz:** La [pirámide de test](https://martinfowler.com/bliki/TestPyramid.html), con 10> años de antiguedad, es un modelo excelente y relevante que sugiere tres tipos de test e influye en la estrategia de testeo de la mayoría de los desarrolladores. Al mismo tiempo, surgieron un puñado de nuevas y brillantes técnicas de testeo que se esconden en las sombras de la pirámide de test. Dados todos los cambios que hemos visto en los últimos 10 años (Microservicios, cloud, serverless), ¿es posible que un modelo algo antiguo se adapte a *todos* los tipos de aplicaciones? ¿No debería el mundo del testing considerar aceptar nuevas técnicas? -No me malinterpretes, en 2019 la pirámide de test, el TDD y los test unitarios siguen siendo una técnica buena y probablemente sean la mejor combinación para muchas aplicaciones. Solo como cualquier otro modelo, a pesar de su utilidad, [a veces debe estar equivocado] (https://en.wikipedia.org/wiki/All_models_are_wrong). Por ejemplo, considere una aplicación IOT que ingiere muchos eventos en un bus de mensajes como Kafka / RabbitMQ, que luego fluyen a algún data-warehouse y finalmente son consultados por alguna UI de análisis. ¿Realmente deberíamos gastar el 50% de nuestro presupuesto para tests en escribir tests unitarios para una aplicación que esté centrada en la integración y apenas tenga lógica? A medida que aumenta la diversidad de tipos de aplicaciones (bots, criptografía, Alexa-skills), aumentan las posibilidades de encontrar escenarios en los que la pirámide de test no sea la mejor opción. +No me malinterpretes, en 2019 la pirámide de test, el TDD y los test unitarios siguen siendo una técnica buena y probablemente sean la mejor combinación para muchas aplicaciones. Solo como cualquier otro modelo, a pesar de su utilidad, [a veces debe estar equivocado] (https://en.wikipedia.org/wiki/All_models_are_wrong). Por ejemplo, considere una aplicación IOT que ingiere muchos eventos en un bus de mensajes como Kafka / RabbitMQ, que luego fluyen a algún data-warehouse y finalmente son consultados por alguna UI de análisis. ¿Realmente deberíamos gastar el 50% de nuestro presupuesto para test en escribir test unitarios para una aplicación que esté centrada en la integración y apenas tenga lógica? A medida que aumenta la diversidad de tipos de aplicaciones (bots, criptografía, Alexa-skills), aumentan las posibilidades de encontrar escenarios en los que la pirámide de test no sea la mejor opción -Es hora de enriquecer el abanico de tests y familiarizarse con más tipos de tests (las siguientes secciones sugieren algunas ideas), modelos como la pirámide de test, pero también hacer coincidir los tipos de test con los problemas del mundo real al que te enfrentas ('Hola, nuestra API está rota, ¡escribamos contract testing dirigidos al consumidor!'), diversifica tus test como un inversor que construye una cartera de inversión basada en el análisis de riesgos — evalúa dónde pueden surgir problemas y combina algunas medidas de prevención para mitigar esos riesgos potenciales +Es hora de enriquecer el abanico de test y familiarizarse con más tipos de test (las siguientes secciones sugieren algunas ideas), modelos como la pirámide de test, pero también hacer coincidir los tipos de test con los problemas del mundo real al que te enfrentas ('Hola, nuestra API está rota, ¡escribamos contract testing dirigidos al consumidor!'), diversifica tus test como un inversor que construye una cartera de inversión basada en el análisis de riesgos — evalúa dónde pueden surgir problemas y combina algunas medidas de prevención para mitigar esos riesgos potenciales Una advertencia: el TDD en el mundo del software adopta una cara de falsa dicotomía, algunos predican que debemos usarlo en todas partes, otros piensan que es el diablo. Todos los que hablan en absoluto están equivocados:] @@ -773,9 +774,9 @@ Una advertencia: el TDD en el mundo del software adopta una cara de falsa dicoto :white_check_mark: **Haz:** -Cada test unitario cubre una pequeña parte de la aplicación y cubrirla totalmente cuesta muchisimo, mientras que los test end-to-end cubren facilmente mucho terreno, pero son costosas y más lentas, ¿por qué no aplicar un enfoque equilibrado y escribir test más grandes que test unitarios pero más pequeñas que las pruebas end-to-end? Los test de componente es la canción no cantada del mundo del testing — proporcionan lo mejor de ambos mundos: rendimiento razonable y la posibilidad de aplicar patrones TDD + cobertura realista. +Cada test unitario cubre una pequeña parte de la aplicación y cubrirla totalmente cuesta muchisimo, mientras que los test end-to-end cubren facilmente mucho terreno, pero son costosas y más lentas, ¿por qué no aplicar un enfoque equilibrado y escribir test más grandes que test unitarios pero más pequeños que los test end-to-end? Los test de componente es la canción no cantada del mundo del testing — proporcionan lo mejor de ambos mundos: rendimiento razonable y la posibilidad de aplicar patrones TDD + cobertura realista -Los test de componente se centran en la 'unidad' de Microservicios, funcionan contra la API, no mockean nada que pertenezca al Microservico en sí (por ejemplo, DB real, o al menos la versión en memoria de esa DB) pero hace stub de cualquier cosa que sea externa como llamadas a otros microservicios. Al hacerlo, probamos lo que desplegamos, nos acercamos a la aplicación de fuera a dentro y obtenemos una gran confianza en un período de tiempo razonable. +Los test de componente se centran en la 'unidad' de Microservicios, funcionan contra la API, no mockean nada que pertenezca al Microservico en sí (por ejemplo, DB real, o al menos la versión en memoria de esa DB) pero hace stub de cualquier cosa que sea externa como llamadas a otros microservicios. Al hacerlo, probamos lo que desplegamos, nos acercamos a la aplicación de fuera a dentro y obtenemos una gran confianza en un período de tiempo razonable
    @@ -800,11 +801,11 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co ## ⚪ ️2.3 Ensure new releases don’t break the API using -:white_check_mark: **Haz:** Pongamos que tu Microservicio tiene multiples consumidores, y tenemos en ejecucion 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 ninguna prueba porque el servidor controla las fechas de release. [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 las pruebas 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 libreria PACT para detectar contratos incumplidos — una expectativa de consumidor no cumplida. Al hacerlo, todos los desajustes de la API cliente-servidor se detectan muyt pronto durante la construcción / CI y pueden ahorrarte mucha frustración. +:white_check_mark: **Haz:** Pongamos que tu Microservicio tiene multiples consumidores, y tenemos en ejecucion 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 ningun test porque el servidor controla las fechas de release. [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 libreria PACT para detectar contratos incumplidos — una expectativa de consumidor no cumplida. Al hacerlo, todos los desajustes de la API cliente-servidor se detectan muyt pronto durante la construcción / CI y pueden ahorrarte mucha frustración
    -❌ **De lo contrario:** Las alternativas son pruebas manuales agotadoras o miedo al despliegue +❌ **De lo contrario:** Las alternativas son test manuales agotadoras o miedo al despliegue
    @@ -824,7 +825,7 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co ## ⚪ ️ 2.4 Test your middlewares in isolation -:white_check_mark: **Haz:** Muchos evitan los test de middleware porque representan una pequeña porción del sistema y requieren ejecutar un servidor Express. Ambas razones son incorrectas — los middlewares son pequeños pero afectan a todas o la mayoría de las solicitudes y pueden testearse fácilmente como funciones puras que obtienen {req, res} objetos JS. Para testear una función de middleware se debe invocar y usar spy ([usando Sinon, por ejemplo] (https://www.npmjs.com/package/sinon)) sobre la interacción con los objetos {req, res} para garantizar que nuestra función middleware realiz la acción correcta. La libreria [node-mock-http] (https://www.npmjs.com/package/node-mocks-http) lo lleva aún más lejos y factoriza los objetos {req, res} ademas de añadir el spy. Por ejemplo, puede afirmar si el estado http que se estableció en el objeto res coincide con el esperado (consulta el ejemplo a continuación) +:white_check_mark: **Haz:** Muchos evitan los test de middleware porque representan una pequeña porción del sistema y requieren ejecutar un servidor Express. Ambas razones son incorrectas — los middlewares son pequeños pero afectan a todas o la mayoría de las solicitudes y pueden testearse fácilmente como funciones puras que obtienen {req, res} objetos JS. Para testear una función de middleware se debe invocar y usar spy ([usando Sinon, por ejemplo] (https://www.npmjs.com/package/sinon)) sobre la interacción con los objetos {req, res} para garantizar que nuestra función middleware realiz la acción correcta. La libreria [node-mock-http] (https://www.npmjs.com/package/node-mocks-http) lo lleva aún más lejos y factoriza los objetos {req, res} ademas de añadir el spy. Por ejemplo, puede asertar si el estado http que se estableció en el objeto res coincide con el esperado (consulta el ejemplo a continuación)
    @@ -912,7 +913,7 @@ Crédito: @@ -924,7 +925,7 @@ Crédito: { ## ⚪ ️ 3.1 Separate UI from functionality -:white_check_mark: **Haz:** Al centrarnos en testear la lógica del component, los detalles de la interfaz de usuario solo pueden entorpecernos, por lo qu debes abstraerte de ellos y que los test se centren en datos puros. En la practica, extrae los datos que necesites de una manera abstracta sin que este acoplada a la interfaz grafica, haz aserciones de los datos puros (vs detalles visuales en HTML/CSS) y desactiva las animaciones que pueden hacer lenta la interfaz. En este punto podrias pensar en desactivar la interfaz y solo hacer test de la parte back del UI (por ejemplo servicios, acciones, store) pero esto solo dará como resultado test ficticios, diferentes a la realidad y no desvelaran casos en los que los datos correctos no llegan a la interfaz de usuario. +:white_check_mark: **Haz:** Al centrarnos en testear la lógica del component, los detalles de la interfaz de usuario solo pueden entorpecernos, por lo qu debes abstraerte de ellos y que los test se centren en datos puros. En la practica, extrae los datos que necesites de una manera abstracta sin que este acoplada a la interfaz grafica, haz aserciones de los datos puros (vs detalles visuales en HTML/CSS) y desactiva las animaciones que pueden hacer lenta la interfaz. En este punto podrias pensar en desactivar la interfaz y solo hacer test de la parte back del UI (por ejemplo servicios, acciones, store) pero esto solo dará como resultado test ficticios, diferentes a la realidad y no desvelaran casos en los que los datos correctos no llegan a la interfaz de usuario
    -❌ **De lo contrario:** Los datos calculados puros de tu test pueden estar listos en 10ms, pero luego todo el test tarda 500ms (100 tests = 1 min) debido a alguna animación irrelevante +❌ **De lo contrario:** Los datos calculados puros de tu test pueden estar listos en 10ms, pero luego todo el test tarda 500ms (100 test = 1 min) debido a alguna animación irrelevante
    @@ -990,7 +991,7 @@ it("When updating site name, get successful confirmation", async () => { ```javascript test("When users-list is flagged to show only VIP, should display only VIP members", () => { - // Arreglar + // Ajustar const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; // Actuar @@ -1009,7 +1010,7 @@ test("When users-list is flagged to show only VIP, should display only VIP membe ```javascript test("When flagging to show only VIP, should display only VIP members", () => { - // Arreglar + // Ajustar const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }]; // Actuar @@ -1055,7 +1056,7 @@ test("When flagging to show only VIP, should display only VIP members", () => { ```javascript // este ejemplo está usando react-testing-library test("Whenever no data is passed to metric, show 0 as default", () => { - // Arreglar + // Ajustar const metricValue = undefined; // Actuar @@ -1124,7 +1125,7 @@ class Calendar extends React.Component { //Examples use React & Enzyme test("Realistic approach: When clicked to show filters, filters are displayed", () => { - // Arreglar + // Ajustar const wrapper = mount(); // Actuar @@ -1140,7 +1141,7 @@ test("Realistic approach: When clicked to show filters, filters are displayed", ```javascript test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { - // Arreglar + // Ajustar const wrapper = shallow(); // Actuar @@ -1167,7 +1168,7 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -❌ **De lo contrario:** Cuando se espera mucho tiempo, los tests seran un orden de magnitud más lentos. Cuando intentes espera tiempos bajos, los test fallarán cuando la unidad bajo test no haya respondido a tiempo. Por tanto, se reduce a balancear entre puntos debiles y el rendimiento malo. +❌ **De lo contrario:** Cuando se espera mucho tiempo, los test seran un orden de magnitud más lentos. Cuando intentes espera tiempos bajos, los test fallarán cuando la unidad bajo test no haya respondido a tiempo. Por tanto, se reduce a balancear entre puntos debiles y el rendimiento malo
    @@ -1204,7 +1205,7 @@ test("movie title appears", async () => { }); ``` -### :thumbsdown: Ejemplo Anti Patrón: codigo de espera propio +### :thumbsdown: Ejemplo Anti Patrón: código de espera propio ```javascript test("movie title appears", async () => { @@ -1287,7 +1288,7 @@ export default function ProductsList() { // test test("When no products exist, show the appropriate message", () => { - // Arreglar + // Ajustar nock("api") .get(`/products`) .reply(404); @@ -1316,7 +1317,7 @@ test("When no products exist, show the appropriate message", () => { ## ⚪ ️ 3.8 Acelera los test E2E reutilizando las credenciales de login -:white_check_mark: **Haz:** En los test E2E que involucren un backend real que usa un token para identicarse en las llamadas a API, no vale la pena aislat el test tanto como para que se cree un usuario y se haga login en cada test. En vez de esto, haz login una vez antes de ejecutar todos los test (en el before-all) guarda el token en de forma local y reutilizalo en cada petición. Esto parece violar unos de los principios básicos - manten los test autonomos sin acoplamiento de recursos. Lo que es cierto, pero en los test E2E el rendimiento es clave, y crear 1-3 peticiones a API antes de empezar cada test individual puede llevarnos a unos tiempos de ejecución horribles. Reutilizar las credenciales no significa que las pruebas tengan que actuar sobre los mismos registros de usuario - si se basan en ellos (por ejemplo, testeando el historial de pagos), asegurate de crear los registro como parte del test y evita compartirlos con otros test. Y siempre recuerda que el backend puede ser sustituido - si tus test estan focalizado en el frontend puede ser mejor aislarlos y descomnectar las API de backend (consulta el punto 3.6) +:white_check_mark: **Haz:** En los test E2E que involucren un backend real que usa un token para identicarse en las llamadas a API, no vale la pena aislat el test tanto como para que se cree un usuario y se haga login en cada test. En vez de esto, haz login una vez antes de ejecutar todos los test (en el before-all) guarda el token en de forma local y reutilizalo en cada petición. Esto parece violar unos de los principios básicos - manten los test autonomos sin acoplamiento de recursos. Lo que es cierto, pero en los test E2E el rendimiento es clave, y crear 1-3 peticiones a API antes de empezar cada test individual puede llevarnos a unos tiempos de ejecución horribles. Reutilizar las credenciales no significa que los test tengan que actuar sobre los mismos registros de usuario - si se basan en ellos (por ejemplo, testeando el historial de pagos), asegurate de crear los registro como parte del test y evita compartirlos con otros test. Y siempre recuerda que el backend puede ser sustituido - si tus test estan focalizado en el frontend puede ser mejor aislarlos y descomnectar las API de backend (consulta el punto 3.6)
    @@ -1364,7 +1365,7 @@ beforeEach(setUser => () { ## ⚪ ️ 3.9 Haz un test E2E que navegue toda la página (smoke test) -:white_check_mark: **Haz:** Para el monitoreo de producción y verificar que nada se rompe en tiempo de desarrollo (sanity check), ejecuta un único test E2E que visite todas o la mayoría de las páginas y se asegure que ninguna se rompe. Este tipo de test proporciona un gran retorno de la inversión ya que es un bastante sencillo de crear y mantener y puede detecta cualquier tipo de fallo, incluido funcionales, red y despliegue. Otras formas de hacer smoke y sanity checks no son tan confiables y exhaustivas - algunso equipos de operaciones simplemente hacen ping a la página de inicio (en producción) o desarrolladores que tiene muchos test de integración que no levanta errores de construcción o de navegador. No hace falta decir que esta prueba no sustituye los test funcionales, solo siver como detector de humo rápido. +:white_check_mark: **Haz:** Para el monitoreo de producción y verificar que nada se rompe en tiempo de desarrollo (sanity check), ejecuta un único test E2E que visite todas o la mayoría de las páginas y se asegure que ninguna se rompe. Este tipo de test proporciona un gran retorno de la inversión ya que es un bastante sencillo de crear y mantener y puede detecta cualquier tipo de fallo, incluido funcionales, red y despliegue. Otras formas de hacer smoke y sanity checks no son tan confiables y exhaustivas - algunso equipos de operaciones simplemente hacen ping a la página de inicio (en producción) o desarrolladores que tiene muchos test de integración que no levanta errores de construcción o de navegador. No hace falta decir que este test no sustituye los test funcionales, solo siver como detector de humo rápido
    @@ -1442,7 +1443,7 @@ Feature: Twitter new tweet ## ⚪ ️ 3.11 Detecta problemas visuales con herramientas automatizadas -:white_check_mark: **Haz:** Configure herramientas automatizadas para capturar screenshoots de UI cuando se presenten cambios y detecte problemas visuales como contenido superpuesto o roto. Esto garantiza que no solo se muestren los datos correctos si no que el usuario los vea correctamente. Está tecnica no es ampliamente usada, nuestra mentalidad nos lleva a los test funcionales, pero es lo visual lo que el usuario experimenta y con la cantidad de dispositivos es relativamente facil pasar por alto algunos bugs en la UI. Algunas herramientas gratuítas pueden proporcionar lo básico - generar y guardar screenshots para la inspeccion manual por una persona. Mientras este enfoque podria ser suficiente para aplicaciones pequeñas, no es valido como cualquier otro test manual que exige trabajo de una persona cada vez que algo cambia. Por otro lado, es bastante dificil detectar problemas de UI automaticamente debido a que no está claramente deficini - aquí es donde interviene el campo de la 'Regresion Visual' a resolver este rompecabezas de comparar la UI antigua con los últimos cambios y detectar diferencias. Alguas erramientas OSS/gratuitas pueden proporcionar parte de esta funcionalidad (por ejemplo [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) pero podrian conllevar un tiempo de configuración muy alto. Algunas herramientas comerciales (por ejemplo [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) dan un paso más reducir la instalacion y contener funciones avanzadas como interfaces de administración, alertas, captura intelegiente que elimina el 'ruido visual' (por ejemplo, banners, animaciones) e incluso llegan a adelantar el analisis de la causa raiz de los cambios del DOM / css que han causado el problema. +:white_check_mark: **Haz:** Configure herramientas automatizadas para capturar screenshoots de UI cuando se presenten cambios y detecte problemas visuales como contenido superpuesto o roto. Esto garantiza que no solo se muestren los datos correctos si no que el usuario los vea correctamente. Está tecnica no es ampliamente usada, nuestra mentalidad nos lleva a los test funcionales, pero es lo visual lo que el usuario experimenta y con la cantidad de dispositivos es relativamente facil pasar por alto algunos bugs en la UI. Algunas herramientas gratuítas pueden proporcionar lo básico - generar y guardar screenshots para la inspeccion manual por una persona. Mientras este enfoque podria ser suficiente para aplicaciones pequeñas, no es valido como cualquier otro test manual que exige trabajo de una persona cada vez que algo cambia. Por otro lado, es bastante dificil detectar problemas de UI automaticamente debido a que no está claramente deficini - aquí es donde interviene el campo de la 'Regresion Visual' a resolver este rompecabezas de comparar la UI antigua con los últimos cambios y detectar diferencias. Alguas erramientas OSS/gratuitas pueden proporcionar parte de esta funcionalidad (por ejemplo [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) pero podrian conllevar un tiempo de configuración muy alto. Algunas herramientas comerciales (por ejemplo [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) dan un paso más reducir la instalacion y contener funciones avanzadas como interfaces de administración, alertas, captura intelegiente que elimina el 'ruido visual' (por ejemplo, banners, animaciones) e incluso llegan a adelantar el analisis de la causa raiz de los cambios del DOM / css que han causado el problema
    @@ -1522,9 +1523,9 @@ describe("visual validation", () => { ## ⚪ ️ 4.1 Completa una cobertura suficiente que de confianza, ~80% parece el numero de la suerte -:white_check_mark: **Haz:** El proposito de los test es tener suficiente confianza para moverse rápido, obviamente cuando más código se pruebe, más confianza tendremos en el equipo. La cobertura nos mide cuantas lineas de código (y ramas, declaraciones, etc) se alcanzan mediante los test. Entonces, cuanta es suficiente? Obviamente 10-30% es demasiado bajo para tener alguna idea de que puedes tener que corregir, y por otro lado el 100% es muy caro y puede cambiar el foco de los caminos criticos a rincones apenas usados del código. La respuesta larga es que depende de muchos dactores como el tipo de aplicación - si estas construyendo la siguiente generación del Airbus A380 un 100% es obligatorio, pero para una web de dibujos animados, el 50% podria hasta ser demasiado. Aunque la mayoria de los entusiastas de los test dicen que el porcentaje de cobertura correcto es contextual, la mayoria de ellos comentan que el 80% como la regla correcta ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) que posiblemente debería satisfacer la mayotia de aplicaciones. +:white_check_mark: **Haz:** El proposito de los test es tener suficiente confianza para moverse rápido, obviamente cuando más código se pruebe, más confianza tendremos en el equipo. La cobertura nos mide cuantas lineas de código (y ramas, declaraciones, etc) se alcanzan mediante los test. Entonces, ¿cuanta es suficiente? Obviamente 10-30% es demasiado bajo para tener alguna idea de que puedes tener que corregir, y por otro lado el 100% es muy caro y puede cambiar el foco de los caminos criticos a rincones apenas usados del código. La respuesta larga es que depende de muchos dactores como el tipo de aplicación - si estas construyendo la siguiente generación del Airbus A380 un 100% es obligatorio, pero para una web de dibujos animados, el 50% podria hasta ser demasiado. Aunque la mayoria de los entusiastas de los test dicen que el porcentaje de cobertura correcto es contextual, la mayoria de ellos comentan que el 80% como la regla correcta ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) que posiblemente debería satisfacer la mayotia de aplicaciones -Consejos de implementación: es posible que quieras configurar la integracion continua (CI) para que tenga un umbral de cobertura ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) y que pare el pipeline cuando no cumpla el estandar (también es posible configurar el porcentaje por compoente, vease el ejemplo a continuación). Ademas de esto, deberias considerar detectar la bajada de cobertura (cuando un nuevo commit tiene menos cobertura que antes) - esto empujara a los desarrolladores a aumentar o al menos preservar la cantidad de código con test. Y también puede ser engañado como se puestra en los siguientes puntos. +Consejos de implementación: es posible que quieras configurar la integracion continua (CI) para que tenga un umbral de cobertura ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) y que pare el pipeline cuando no cumpla el estandar (también es posible configurar el porcentaje por compoente, vease el ejemplo a continuación). Ademas de esto, deberias considerar detectar la bajada de cobertura (cuando un nuevo commit tiene menos cobertura que antes) - esto empujara a los desarrolladores a aumentar o al menos preservar la cantidad de código con test. Y también puede ser engañado como se puestra en los siguientes puntos
    @@ -1552,7 +1553,7 @@ Consejos de implementación: es posible que quieras configurar la integracion co

    -## ⚪ ️ 4.2 Inspecciona los reportes de cobertura para detectar áreas no testadas y otras cosas raras. +## ⚪ ️ 4.2 Inspecciona los reportes de cobertura para detectar áreas no testadas y otras cosas raras :white_check_mark: **Haz:** Algunos problemas se ocultan por debajo del radar y son realmente difíciles de encontrar utilizando herramientas tradicionales. Estos no son realmente bugs sino más bien un comportamientos curiosos de la aplicación que podrían tener un gran impacto. Por ejemplo, a menudo algunas áreas de código no se invocan nunca o rara vez - puedes pensar que la clase 'PricingCalculator' siempre determina el precio del producto, pero resulta que en realidad nunca se invoca, aunque tenemos 10000 productos en DB y muchas ventas… Los reportes de nos ayudan a darnos cuenta de si la aplicación se comporta de la manera que esperamos. Aparte de eso, también podemos resaltar qué tipos de código no se testean: que el 80% del código se testea, no nos indica si las partes críticas están cubiertas. Generar reportes es fácil: simplemente ejecute su aplicación en producción o durante test con cobertura y luego revisa los reportes que resaltan la frecuencia con la que se invoca cada parte del código. Si le dedicas un tiempo para echar un vistazo a estos datos, puedes encontrar algunas errores @@ -1566,11 +1567,11 @@ Consejos de implementación: es posible que quieras configurar la integracion co
    -### :thumbsdown: Ejemplo Anti Patrón: ¿Qué hay mal en este reporte de cobertura? +### :thumbsdown: Ejemplo Anti Patrón: ¿Qué está mal en este reporte de cobertura? Basado en un escenario real en el que rastreamos el uso de nuestra aplicación en el control de calidad y descubrimos patrones de login interesantes (Sugerencia: la cantidad de fallos de login no es proporcional, algo está claramente mal. Finalmente, resultó que algún error de la interfaz provocaba que se siguiera llamando al API de login en segundo plano) -![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "¿Qué hay mal en este reporte de cobertura?") +![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "¿Qué está mal en este reporte de cobertura?")
    @@ -1578,13 +1579,13 @@ Basado en un escenario real en el que rastreamos el uso de nuestra aplicación e ## ⚪ ️ 4.3 Mide la cobertura lógica usando mutation testing -:white_check_mark: **Haz:** La métrica de cobertura tradicional a menudo miente: puede mostrarle una cobertura de código del 100%, pero ninguna de sus funciones, ni siquiera una, devuelve la respuesta correcta. ¿Cómo? simplemente mide sobre qué líneas de código se paso en los test, pero no verifica si laos test realmente han comprobado algo - asercionando la respuesta correcta. Como alguien que viaja por negocios y muestra su pasaporte, esto no prueba que haya realizado ningun trabajo, solo que ha visitado ciertos aeropuertos y hoteles. +:white_check_mark: **Haz:** La métrica de cobertura tradicional a menudo miente: puede mostrarle una cobertura de código del 100%, pero ninguna de sus funciones, ni siquiera una, devuelve la respuesta correcta. ¿Cómo? simplemente mide sobre qué líneas de código se paso en los test, pero no verifica si laos test realmente han comprobado algo - asercionando la respuesta correcta. Como alguien que viaja por negocios y muestra su pasaporte, esto no te asegura que haya realizado ningun trabajo, solo que ha visitado ciertos aeropuertos y hoteles Los test basados en mutaciones nos ayudan midiendo la cantidad de código que en realidad se TESTEÓ, no solo VISITADO. [Stryker] (https://stryker-mutator.io/) es una libreria JavaScript para test de mutación y la implementación es realmente clara: -(1) cambia intencionalmente el código y "planta bugs". Por ejemplo, el código newOrder.price === 0 se convierte en newOrder.price! = 0. Estos "bugs" se llaman mutaciones. +(1) cambia intencionalmente el código y "planta bugs". Por ejemplo, el código newOrder.price === 0 se convierte en newOrder.price! = 0. Estos "bugs" se llaman mutaciones -(2) ejecuta los test, si todo va bien, entonces tenemos un problema - los test no cumplen su propósito de descubrir bugs, las mutaciones se denominan supervivientes. Si los test fallaron, entonces genial, las mutaciones fueron asesinadas. +(2) ejecuta los test, si todo va bien, entonces tenemos un problema - los test no cumplen su propósito de descubrir bugs, las mutaciones se denominan supervivientes. Si los test fallaron, entonces genial, las mutaciones fueron asesinadas Saber que todas o la mayoría de las mutaciones fueron asesinadas da mucha más confianza que la cobertura tradicional y el tiempo de configuración es muy similar
    @@ -1627,11 +1628,11 @@ it("Test addNewOrder, don't use such test names", () => { ## ⚪ ️4.4 Prevención de problemas de código de test con linters para test -:white_check_mark: **Haz:** ESLint tiene un conjunto de plugins específicos para inspeccionar patrones de código de test y descubrir problemas. Por ejemplo, [eslint-plugin-mocha] (https://www.npmjs.com/package/eslint-plugin-mocha) avisará cuando un test se escriba a nivel global (no es hijo de un describe () ) o cuando se omiten los test (https://mochajs.org/#inclusive-tests), lo que puede llevar a creer erroneamenre de que todas los test están ok. Del mismo modo, [eslint-plugin-jest] (https://github.com/jest-community/eslint-plugin-jest) puede, por ejemplo, advertir cuando una prueba no tiene aserciones (sin verificar nada) +:white_check_mark: **Haz:** ESLint tiene un conjunto de plugins específicos para inspeccionar patrones de código de test y descubrir problemas. Por ejemplo, [eslint-plugin-mocha] (https://www.npmjs.com/package/eslint-plugin-mocha) avisará cuando un test se escriba a nivel global (no es hijo de un describe () ) o cuando se omiten los test (https://mochajs.org/#inclusive-tests), lo que puede llevar a creer erroneamenre de que todas los test están ok. Del mismo modo, [eslint-plugin-jest] (https://github.com/jest-community/eslint-plugin-jest) puede, por ejemplo, advertir cuando un test no tiene aserciones (sin verificar nada)
    -❌ **De lo contrario:** Ver un 90% de cobertura de código y 100% de pruebas verdes te provocara una sonrisa hasta que te das cuenta de que muchas pruebas no asercionan nada y muchas test simplemente se omitieron. Con suerte, no desplegó nada basado en esta falsa observación +❌ **De lo contrario:** Ver un 90% de cobertura de código y 100% de test verdes te provocara una sonrisa hasta que te das cuenta de que muchos test no asercionan nada y muchos test simplemente se omitieron. Con suerte, no desplegó nada basado en esta falsa observación
    Código de Ejemplo @@ -1664,7 +1665,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 5.1 Enriquece tus linters y cancela las construciones que tienen problemas de linter -:white_check_mark: **Haz:** Los linters son comida gratis, con una configuración de 5 minutos, obtienes gratis un piloto automático que vigila tu código y detecta problemas importantes mientras escribes. Atrás quedaron los días en los que el linter era solo maquillaje (¡no hay punto y coma!). Hoy en día, los linters pueden detectar problemas graves como errores que no se lanzan correctamente y perder información. Además de su conjunto básico de reglas (como [estándar ESLint] (https://www.npmjs.com/package/eslint-plugin-standard) o [estilo Airbnb] (https://www.npmjs.com/package / eslint-config-airbnb)), considera incluir algunos conjuntos de reglas especializadas como [eslint-plugin-chai-expect] (https://www.npmjs.com/package/eslint-plugin-chai-expect) que pueden descubrir tests sin aserciones, [eslint-plugin-promise] (https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) puede descubrir promesas sin resolución (su código nunca continuará), [eslint-plugin -security] (https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) que puede descubrir expresiones regulares que podrían usarse para ataques DOS, y [eslint-plugin-you-dont- need-lodash-underscore] (https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) es capaz de avisar cuando el código utiliza métodos de librerias de utilidades que forman parte de V8, métodos básicos como Lodash.\_map (...) +:white_check_mark: **Haz:** Los linters son comida gratis, con una configuración de 5 minutos, obtienes gratis un piloto automático que vigila tu código y detecta problemas importantes mientras escribes. Atrás quedaron los días en los que el linter era solo maquillaje (¡no hay punto y coma!). Hoy en día, los linters pueden detectar problemas graves como errores que no se lanzan correctamente y perder información. Además de su conjunto básico de reglas (como [estándar ESLint] (https://www.npmjs.com/package/eslint-plugin-standard) o [estilo Airbnb] (https://www.npmjs.com/package / eslint-config-airbnb)), considera incluir algunos conjuntos de reglas especializadas como [eslint-plugin-chai-expect] (https://www.npmjs.com/package/eslint-plugin-chai-expect) que pueden descubrir test sin aserciones, [eslint-plugin-promise] (https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) puede descubrir promesas sin resolución (tu código nunca continuará), [eslint-plugin -security] (https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) que puede descubrir expresiones regulares que podrían usarse para ataques DOS, y [eslint-plugin-you-dont- need-lodash-underscore] (https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) es capaz de avisar cuando el código utiliza métodos de librerias de utilidades que forman parte de V8, métodos básicos como Lodash.\_map (...)
    @@ -1686,7 +1687,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 5.2 Acorta el tiempo de feedback con local developer-CI -:white_check_mark: **Haz:** Tienes un pipeline de CI con test, linter, verificación de vulnerabilidades, etc.? Ayuda a los desarrolladores a ejecutarlo también localmente para solicitar comentarios instantáneos y acortar el [ciclo de feedback] (https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2 -circuitos de retroalimentacion/). ¿Por qué? un proceso de prueba eficiente constituye muchos bucles iterativos: (1) tests -> (2) feedback -> (3) refactor. Cuanto más rápido sea el feedback, más iteraciones de mejora puede realizar un desarrollador por módulo y perfeccionar los resultados. Por otro lado, cuando el feedback tarda en llegar, se podrían realizar menos iteraciones de mejora en un solo día, el equipo podría estar ya haciendo otra cosa / tarea / módulo y podría no estar listo para refinar ese módulo +:white_check_mark: **Haz:** ¿Tienes un pipeline de CI con test, linter, verificación de vulnerabilidades, etc? Ayuda a los desarrolladores a ejecutarlo también localmente para solicitar comentarios instantáneos y acortar el [ciclo de feedback] (https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2 -circuitos de retroalimentacion/). ¿Por qué? un proceso de testing eficiente constituye muchos bucles iterativos: (1) test -> (2) feedback -> (3) refactor. Cuanto más rápido sea el feedback, más iteraciones de mejora puede realizar un desarrollador por módulo y perfeccionar los resultados. Por otro lado, cuando el feedback tarda en llegar, se podrían realizar menos iteraciones de mejora en un solo día, el equipo podría estar ya haciendo otra cosa / tarea / módulo y podría no estar listo para refinar ese módulo En la practica, algunos proveedores de CI (Ejemplo: [CircleCI CLI local] (https://circleci.com/docs/2.0/local-cli/)) permiten ejecutar el pipeline localmente. Algunas herramientas comerciales como [wallaby proporcionan información valiosa y de test] (https://wallabyjs.com/) para el desarrollador sin coste. Alternativamente, puedes agregar scripts npm en el package.json para ejecutar todos los comandos de calidad (por ejemplo, test, linter, vulnerabilidades) - usa herramientas como [concurrently] (https://www.npmjs.com/package/concurrently) para paralelizarlas y que código de salida sea distinto de cero si falla alguna de las herramientas. Ahora el desarrollador solo debe invocar un comando - por ejemplo "npm run quality": para obtener feedback en el acto. Considera también cancelar un commit si el control de calidad falla usando un githook ([husky puede ayudar] (https://github.com/typicode/husky)) @@ -1751,7 +1752,7 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l ## ⚪ ️5.4 Parallelizar la ejecución de los test -:white_check_mark: **Haz:** Cuando se hace correctamente, los test son tu amigo 24/7 propocionando feedback instantaneo. En la practica, ejecutar 500 test unitarios en un solo proceso en CPU puede llevar demasiado tiempo. Afortunadamente, los test runner más modernos y las plataformas de CI (como [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) y [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) pueden paralelizar los test en multiples procesos y lograr una mejora importante en el tiempo en entregar feedback. Algunos proveedores de CI también paralelizan las pruebas en contenedores (!) lo que acorta aún más la entrega de feedback. Ya sea localmente con multiples procesos, o sobre algun CLI en cloud usando multiples maquinas - pararelizar la demanda manteniendo los test autonomos para que cada uno pueda ejecutarse en diferentes procesos +:white_check_mark: **Haz:** Cuando se hace correctamente, los test son tu amigo 24/7 propocionando feedback instantaneo. En la practica, ejecutar 500 test unitarios en un solo proceso en CPU puede llevar demasiado tiempo. Afortunadamente, los test runner más modernos y las plataformas de CI (como [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) y [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) pueden paralelizar los test en multiples procesos y lograr una mejora importante en el tiempo en entregar feedback. Algunos proveedores de CI también paralelizan los test en contenedores (!) lo que acorta aún más la entrega de feedback. Ya sea localmente con multiples procesos, o sobre algun CLI en cloud usando multiples maquinas - pararelizar la demanda manteniendo los test autonomos para que cada uno pueda ejecutarse en diferentes procesos ❌ **De lo contrario:** Obtener el resultado de los test en 1 hora despues de hacer push de código nuevo, mientras desarrollas nuevas funcionalidades, es la mejor receta para quitarle relevancia a los test @@ -1771,7 +1772,7 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l ## ⚪ ️5.5 Mantente al margen de problemas legales usando la verificación de licencia y plagio -:white_check_mark: **Haz:** Los problemas de licencias y plagio seguramente no son tu prioridad ahora mismo, pero ¿por que no hacer check en esta tarea en solo 10 minutos? Existen muchos paquetes npm como [license check](https://www.npmjs.com/package/license-checker) y [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial pero con plan gratuito) que puedes integrar facilmente en tu pipeline CI e inspeccionar en busca de problemas como dependencias con licencias restrictivas, o código que fue copiado y pegado de stackoverflow que aparentemente viola algunos copyrights +:white_check_mark: **Haz:** Los problemas de licencias y plagio seguramente no son tu prioridad ahora mismo, pero ¿por qué no hacer check en esta tarea en solo 10 minutos? Existen muchos paquetes npm como [license check](https://www.npmjs.com/package/license-checker) y [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial pero con plan gratuito) que puedes integrar facilmente en tu pipeline CI e inspeccionar en busca de problemas como dependencias con licencias restrictivas, o código que fue copiado y pegado de stackoverflow que aparentemente viola algunos copyrights ❌ **De lo contrario:** Sin querer, los desarrolladores pueden usar paquete con licencias inapropiadas o copiar y pegar código comercial y encontrarse con problemas legales @@ -1787,7 +1788,7 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l //instala license-checker en tu entorno de CI o localmente npm install -g license-checker -//pidele que escanee todas las licencias y que termine con un codigo distindo de 0 si encuentra una licencia no autorizada. De esta forma el pipeline de CI puede detectarlo y detenerse +//pidele que escanee todas las licencias y que termine con un código distindo de 0 si encuentra una licencia no autorizada. De esta forma el pipeline de CI puede detectarlo y detenerse license-checker --summary --failOn BSD ``` @@ -1822,11 +1823,11 @@ license-checker --summary --failOn BSD ## ⚪ ️5.7 Automatiza la actualización de dependencias -:white_check_mark: **Haz:** La introducción en yarn and npm de package-lock.json introduce un desafio muy importante ( el camino al infierno esta lleno de buenas intenciones) — por defecto ahora, lo paquetes no se auto actualiza más. Incluso en un equipo que ejecute un 'npm install' limpio y 'npm update' no van a caer nuevas actualizaciones. Esto conduce a veriones de paquetes desactualizadas en el mejor de los casos, y en el peor, a codigo vulnerable. Los equipos pasan a depender de la buena voluntad y memoria de los desarrolladores para actualizar el package.json a mano o a utilizar herramientas como [ncu](https://www.npmjs.com/package/npm-check-updates) manualmente. Una formula mucho mejor seria automatizar el proceso de actualizar las versiones de las dependenecias en las que más confiamos, pero no hay una solucion perfecta, existen dos caminos para esta actualización: +:white_check_mark: **Haz:** La introducción en yarn y del package-lock.json de npm introduce un desafio muy importante ( el camino al infierno esta lleno de buenas intenciones) — por defecto ahora, lo paquetes no se auto actualiza más. Incluso en un equipo que ejecute un 'npm install' limpio y 'npm update' no van a caer nuevas actualizaciones. Esto conduce a veriones de paquetes desactualizadas en el mejor de los casos, y en el peor, a código vulnerable. Los equipos pasan a depender de la buena voluntad y memoria de los desarrolladores para actualizar el package.json a mano o a utilizar herramientas como [ncu](https://www.npmjs.com/package/npm-check-updates) manualmente. Una formula mucho mejor seria automatizar el proceso de actualizar las versiones de las dependenecias en las que más confiamos, pero no hay una solucion perfecta, existen dos caminos para esta actualización: (1) Podemos hacer que el CI falle con dependencias obsoletas — usando herramientas como [‘npm outdated’](https://docs.npmjs.com/cli/outdated) o ‘npm-check-updates (ncu)’. Hacerlo obligará a los desarrolladores a actualizar las dependencias -(2) Usar alguna de las herramientas comerciales que escanean nuestro codigo y envian automaticamente pull-request con actualización de dependencias. Una pregunta interesante que nos queda es cual va a ser la politica de estas actualizaciones: si actualizamos cada parche se genera mucha sobrecarga, y hacerlo cuando haya una version major nos lleva directos a usar verisones inestables o incompatibles (mucho paquetes muestran vulnerabilidades justo despues de salir una version nueva [lee sobre](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) el incidente de eslint-scope). +(2) Usar alguna de las herramientas comerciales que escanean nuestro código y envian automaticamente pull-request con actualización de dependencias. Una pregunta interesante que nos queda es cual va a ser la politica de estas actualizaciones: si actualizamos cada parche se genera mucha sobrecarga, y hacerlo cuando haya una version major nos lleva directos a usar verisones inestables o incompatibles (mucho paquetes muestran vulnerabilidades justo despues de salir una version nueva [lee sobre](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) el incidente de eslint-scope) Una politica de actualizaciones eficiente puede permitir cierto 'periodo de concesion' - deja pasar versiones quedandote por detras de @latest un tiempo antes de considerar que tu versión en local está obsoleta (por ejemplo, la versión que tienes en local es 1.3.1 y la versión del repositorio del paquete es 1.3.8) From dc44a9ec1d85e60e21b2945555e9aeb235ef6e42 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Wed, 23 Sep 2020 00:30:25 +0200 Subject: [PATCH 272/502] removed some typos of the entire spanish translation --- readme-es.md | 288 +++++++++++++++++++++++++-------------------------- 1 file changed, 144 insertions(+), 144 deletions(-) diff --git a/readme-es.md b/readme-es.md index 21ba8092..353f2278 100644 --- a/readme-es.md +++ b/readme-es.md @@ -50,7 +50,7 @@ La base - estructurando test claros (12 apartados) #### [`Sección 2: Backend`](#section-2️⃣-backend-testing) -Escribiendo test de backend y Micro Servicios eficientemente (8 apartados) +Escribiendo test de backend y microservicios eficientemente (8 apartados) #### [`Sección 3: Frontend`](#section-3️⃣-frontend-testing) @@ -161,7 +161,7 @@ describe('Products Service', function() {
    -❌ **De lo contrario:** No solo emplearas horas comprendiendo el código principal, si no que lo que debería haber sido la parate mas simple del día (testing) te ha estrujado el cerebro +❌ **De lo contrario:** No solo emplearas horas comprendiendo el código principal, si no que lo que debería haber sido la parate más simple del día (testing) te ha estrujado el cerebro
    @@ -368,10 +368,10 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 No uses “foo”, usa datos realistas -:white_check_mark: **Haz:** A menudo, los bugs de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar bugs temprano. Utiliza librerias dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de prodcucción. Por ejemplo, dichas librerias pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para estirar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Vea la próxima sección (test basados en propiedades) +:white_check_mark: **Haz:** A menudo, los bugs de producción se revelan bajo una entrada muy específica y sorprendente: cuanto más realista sea la entrada de un test, mayores serán las posibilidades de detectar bugs temprano. Utiliza librerías dedicadas como [Faker] (https://www.npmjs.com/package/faker) para generar datos pseudo-reales que se asemejan en variedad y forma a los datos de producción. Por ejemplo, dichas librerías pueden generar números de teléfono realistas, nombres de usuario, tarjetas de crédito, nombres de empresas e incluso texto "lorem ipsum". También puedes crear algunos test (además de los test unitarios, no como un reemplazo) que aleatorizan los datos falsos para forzar la unidad que estamos testeando o incluso importar datos reales de su entorno de producción. ¿Quieres llevarlo al siguiente nivel? Ve la próxima sección (test basados en propiedades)
    -❌ **De lo contrario:** Todo tus test de desarrollo estaran en falsos verdes cuando uses datos sinteticos como "Foo", pero luego en produccion pueden ponerse en rojo cuando un hacker use cadenas extrañas como “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **De lo contrario:** Todo tus test de desarrollo estarán en verde falsamente cuando uses datos sintéticos como "Foo", pero luego en producción pueden ponerse en rojo cuando un hacker use cadenas extrañas como “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA”
    @@ -387,14 +387,14 @@ it("When a valid product is about to be deleted, ensure an email is sent", async const addProduct = (name, price) => { const productNameRegexNoSpace = /^\S*$/; //no se admiten espacios - if (!productNameRegexNoSpace.test(name)) return false; //esta rama nunca se testeara debido a inputs sinteticos + if (!productNameRegexNoSpace.test(name)) return false; //esta rama nunca se testeara debido a inputs sintéticos - //algo de logica aquí + //algo de lógica aquí return true; }; test("Wrong: When adding new product with valid properties, get successful confirmation", async () => { - //La cadena "Foo" que es usada en todo los test, nunca provocara un resutado false + //La cadena "Foo" que es usada en todo los test, nunca provocará un resultado false const addProductResult = addProduct("Foo", 5); expect(addProductResult).toBe(true); //Falso positivo: la operación tuvo éxito porque nunca lo intentamos con un @@ -409,7 +409,7 @@ test("Wrong: When adding new product with valid properties, get successful confi ```javascript it("Better: When adding new valid product, get successful confirmation", async () => { const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); - //Datos de entrada generados aleatoreamente: {'Sleek Cotton Computer', 85481} + //Datos de entrada generados aleatoriamente: {'Sleek Cotton Computer', 85481} expect(addProductResult).to.be.true; //El test falla, El valor de entrada random ha provocado que se vaya por un camino que nunca planeamos //!Hemos descubierto un bug muy pronto! @@ -422,10 +422,10 @@ it("Better: When adding new valid product, get successful confirmation", async ( ## ⚪ ️ 1.7 Testea muchas combinaciones de entrada utilizando test basados en propiedades -:white_check_mark: **Haz:** Por lo general elegimos pocos datos de entrada por cada test. Incluso cuando el formato de entrada se parece a datos reales (ver sección "no uses foo"), cubrimos solo unas pocas combinaciones de datos de entrada (method(‘’, true, 1), method(“string” , false” , 0)), Sin embargo, en producción, un API que es llamada con 5 parametros puede ser invocada por miles de permutaciones diferentes, una sola puede hacer que nuestro poceso falle ([ver Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). ¿Qué tal si pudieras escribir un solo test que envíe 1000 permutaciones de diferentes entradas automáticamente y capture qué entrada hace que código no devuelva la respuesta correcta? Los test basados en propiedades son una técnica que hace exactamente eso: al enviar todas las combinaciones de entrada posibles a la unidad que está siendo testada, aumenta la probabilidad de encontrar un bug. Por ejemplo, dado un metodo — addNewProduct(id, name, isDiscount) — las librerias compatibles llamaran a ese metodo con muchas combinaciones (numeros, textos y boleanos) como (1, “iPhone”, false), (2, “Galaxy”, true). Puedes ejecutar test basados en propiedades usando nuestra libreria de esta favorita (Mocha, Jest, etc) como [js-verify](https://github.com/jsverify/jsverify) o [testcheck](https://github.com/leebyron/testcheck-js) (mucho mejor documentada). Actualizado: Nicolas Dubien sugiere en los comentarios [checkout fast-check](https://github.com/dubzzz/fast-check#readme) que parece ofecer caracteristicas adicionales y es activamente mantenida +:white_check_mark: **Haz:** Por lo general elegimos pocos datos de entrada por cada test. Incluso cuando el formato de entrada se parece a datos reales (ver sección "no uses foo"), cubrimos solo unas pocas combinaciones de datos de entrada (method(‘’, true, 1), method(“string” , false” , 0)), Sin embargo, en producción, un API que es llamada con 5 parámetros puede ser invocada por miles de combinaciones diferentes, una sola puede hacer que nuestro proceso falle ([ver Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). ¿Qué tal si pudieras escribir un solo test que envíe 1000 combinaciones de diferentes entradas automáticamente y capture qué entrada hace que código no devuelva la respuesta correcta? Los test basados en propiedades son una técnica que hace exactamente eso: al enviar todas las combinaciones de entrada posibles a la unidad que está siendo testada, aumenta la probabilidad de encontrar un bug. Por ejemplo, dado un método — addNewProduct(id, name, isDiscount) — las librerías compatibles llamaran a ese método con muchas combinaciones (números, textos y booleanos) como (1, “iPhone”, false), (2, “Galaxy”, true). Puedes ejecutar test basados en propiedades usando tu librería de test favorita (Mocha, Jest, etc) como [js-verify](https://github.com/jsverify/jsverify) o [testcheck](https://github.com/leebyron/testcheck-js) (mucho mejor documentada). Actualizado: Nicolas Dubien sugiere en los comentarios [checkout fast-check](https://github.com/dubzzz/fast-check#readme) que parece ofrecer características adicionales y es activamente mantenida
    -❌ **De lo contrario:** Inconscientemente, eliges los datos de entradas para tus test que cubren solo las ramas de código que funcionan bien. Desafortunadamente, esto disminuye la eficiencia de los test como vehículo para detectar bugs +❌ **De lo contrario:** Inconscientemente, eliges los datos de entrada para tus test que cubren solo las ramas de código que funcionan bien. Desafortunadamente, esto disminuye la eficiencia de los test como vehículo para detectar bugs
    @@ -442,7 +442,7 @@ import fc from "fast-check"; describe("Product service", () => { describe("Adding new", () => { - //esto ejecutara 100 veces con diferentes prodiedades al azar + //esto ejecutara 100 veces con diferentes propiedades al azar it("Add new product with random yet valid properties, always successful", () => fc.assert( fc.property(fc.integer(), fc.string(), (id, name) => { @@ -459,11 +459,11 @@ describe("Product service", () => { ## ⚪ ️ 1.8 Si lo necesitas, usa solo snapshots cortos y en el propio test -:white_check_mark: **Haz:** Cuando hay necesidad de usar [snapshot testing](https://jestjs.io/docs/es-ES/snapshot-testing), usa solo snapshots cortos y bien enfocados (por ejemplo 3-7 lineas) y que esten incluidos en el propio test ([Inline Snapshot](https://jestjs.io/docs/es-ES/snapshot-testing#inline-snapshots)) y no como ficheros externos. Mantener esta dirección te garantiza que tus test se explican por si mismos y a la vez que sean menos fragiles +:white_check_mark: **Haz:** Cuando hay necesidad de usar [snapshot testing](https://jestjs.io/docs/es-ES/snapshot-testing), usa solo snapshots cortos y bien enfocados (por ejemplo 3-7 lineas) y que estén incluidos en el propio test ([Inline Snapshot](https://jestjs.io/docs/es-ES/snapshot-testing#inline-snapshots)) y no como ficheros externos. Mantener esta dirección te garantiza que tus test se explican por si mismos y a la vez que sean menos frágiles -Por otro lado, los tutoriales y herramientas basados en ‘classic snapshots’ tienden a guardar ficheros muy grandes en medios externos (por ejemplo component rendering markup, API JSON result) cada vez que se ejecutan los test para comparar los resultados recividos con la version guardada. Esto, por ejemplo, puede aosciar nuestro test a 1000 lineas con 3000 valores de datoa que quien este escribiendo test jamas leera ni razonará. ¿Por qué está mal esto? Al hacerlo, hay 1000 razones para que tu test falle - tan solo el cambio de una linea de código es suficiente para que el snapshoot se invalide y es muy probable que esto ocurra a menudo. ¿Como de frecuente? cada espacio, comentatio o pequeño cambio de css/html. Y no solo eso, el nombre del test no nos va a dar ni una sola pista de que está fallando, solo verifica que esas 1000 lineas han cambiado. Además obliga a quien escribe los test a asumir como correcto un fichero enorme que no ha podido inspeccionar y corroborar. Todo estos son sintomas de un test oscuro que no está bien enfocado y trata de cubrir demasiadas cosas a la vez +Por otro lado, los tutoriales y herramientas basados en ‘classic snapshots’ tienden a guardar ficheros muy grandes en medios externos (por ejemplo component rendering markup, API JSON result) cada vez que se ejecutan los test para comparar los resultados recibidos con la versión guardada. Esto, por ejemplo, puede asociar nuestro test a 1000 lineas con 3000 valores de datos que quien este escribiendo test jamas leerá ni razonará. ¿Por qué está mal esto? Al hacerlo, hay 1000 razones para que tu test falle - tan solo el cambio de una linea de código es suficiente para que el snapshoot se invalide y es muy probable que esto ocurra a menudo. ¿Como de frecuente? cada espacio, comentario o pequeño cambio de css/html. Y no solo eso, el nombre del test no nos va a dar ni una sola pista de que está fallando, solo verifica que esas 1000 lineas han cambiado. Además obliga a quien escribe los test a asumir como correcto un fichero enorme que no ha podido inspeccionar y corroborar. Todo estos son síntomas de un test oscuro que no está bien enfocado y trata de cubrir demasiadas cosas a la vez -Vale la pena señalar que hay algunos casos en los que los snapshoots grandes y externos son buenos - cuando comporbamos el esquema y no los datos (ignorando los valores y centrandonos en los campos) o en los casos en el que el documento no va a cambiar apenas en el tiempo +Vale la pena señalar que hay algunos casos en los que los snapshoots grandes y externos son buenos - cuando comprobamos el esquema y no los datos (ignorando los valores y centrándonos en los campos) o en los casos en el que el documento no va a cambiar apenas en el tiempo
    ❌ **De lo contrario:** Un test UI falla. El código parece correcto, la pantalla esta pintando todos los pixels correctamente, ¿que ha pasado? tu test de snapshoot ha encontrado una diferencia entre el origen y lo que ha recibido al ejecutarse: simplemente hay un espacio añadido en cualquier parte... @@ -489,14 +489,14 @@ it("TestJavaScript.com is renderd correctly", () => { //Afirmar expect(receivedPage).toMatchSnapshot(); - //Ahora nosotros implicitamente mantenemos un fichero de 2000 lineas + //Ahora nosotros implícitamente mantenemos un fichero de 2000 lineas //cada salto de linea o comentario añadido van a romper nuestro test }); ```
    -### :clap: Ejemplo de cómo hacerlo correctamente: Lo experado es visible y esta enfocado +### :clap: Ejemplo de cómo hacerlo correctamente: Lo esperado es visible y esta enfocado ```javascript it("When visiting TestJavaScript.com home page, a menu is displayed", () => { @@ -526,11 +526,11 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => { ## ⚪ ️1.9 Evitar fixtures globales y seeds, añade datos por cada test -:white_check_mark: **Haz:** Siguiendo la regla de oro (sección 0), cada test debe añadir y actuar en su propio conjunto de filas DB para evitar el acoplamiento y poder explicar fácilmente sobre el flujo del test. En realidad muchos testers se saltan esta regla al añadir datos a la DB solo una vez antes de ejecutar los test ([tambien conocido como ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) a favor de mejorar el rendimiento. Si bien el rendimiento es una preocupación válida — puede mitigarse de otras formas (consulte la sección "test de componentes"), sin embargo, la complegidad del test es más dolorosa que otras consideraciones la mayoria de las veces. De manera practica, haga que cada test añada explicitamente los registros que necesitas y actua sobre ellos. Si el rendimiento se convierte en algo critico — se puede llegar al compromiso de utilizar los mismos datos en un conjunto de test siempre que no se muten los datos (por ejemplo en queries) +:white_check_mark: **Haz:** Siguiendo la regla de oro (sección 0), cada test debe añadir y actuar en su propio conjunto de filas en base de datos para evitar el acoplamiento y poder explicar fácilmente el flujo del test. En realidad muchos testers se saltan esta regla al añadir datos a la DB solo una vez antes de ejecutar los test ([también conocido como ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) a favor de mejorar el rendimiento. Si bien el rendimiento es una preocupación válida — puede mitigarse de otras formas (consulte la sección "test de componentes"), sin embargo, la complejidad del test es más dolorosa que otras consideraciones la mayoría de las veces. De manera practica, haz que cada test añada explícitamente los registros que necesitas y actúe sobre ellos. Si el rendimiento se convierte en algo critico — se puede llegar al compromiso de utilizar los mismos datos en un conjunto de test siempre que no se muten los datos (por ejemplo en queries)
    -❌ **De lo contrario:** FMuchos test fallaran, un despliegue se aborta, nuestro equipo perdera mucho tiempo, ¿tenemos un bug? vamos a investigar, ah no — parece que dos test estan mutando el mismo dato +❌ **De lo contrario:** Muchos test fallaran, un despliegue se aborta, nuestro equipo perderá mucho tiempo, ¿tenemos un bug? vamos a investigar, ah no — parece que dos test están mutando el mismo dato
    @@ -538,13 +538,13 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {
    -### :thumbsdown: Ejemplo Anti Patrón: los test no son independientes y dependenden de una inserción global de datos en la DB +### :thumbsdown: Ejemplo Anti Patrón: los test no son independientes y dependen de una inserción global de datos en la base de datos ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha") ```javascript before(async () => { - //añadiendo datos de sites y admin a nuestra DB. ¿Donde estan los datos? fuera. En un json extermo o en un modelo da migración + //añadiendo datos de sites y admin a nuestra base de datos. ¿Donde están los datos? fuera. En un json externo o en un modelo da migración await DB.AddSeedDataFromJson('seed.json'); }); it("When updating site name, get successful confirmation", async () => { @@ -563,7 +563,7 @@ it("When querying by site name, get the right site", async () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Podemos permanecer dentro de nuestro test, cada test actua sobre su propio conjunto de datos +### :clap: Ejemplo de cómo hacerlo correctamente: Podemos permanecer dentro de nuestro test, cada test actúa sobre su propio conjunto de datos ```javascript it("When updating site name, get successful confirmation", async () => { @@ -582,15 +582,15 @@ it("When updating site name, get successful confirmation", async () => {
    -## ⚪ ️ 1.10 No capures errores, esperalos +## ⚪ ️ 1.10 No captures errores, esperalos -:white_check_mark: **Haz:** Cuando queremos comprobar que una entrada lanza un error, nos puede parecer correcto usar try-catch-finally y asertar que se entra por el catch. El resultado es un test incomodo y verboso (ejemplo a continuación) que nos oculta la intencion de un test muy simple y las expectativas del resultado +:white_check_mark: **Haz:** Cuando queremos comprobar que una entrada lanza un error, nos puede parecer correcto usar try-catch-finally y asertar que se entra por el catch. El resultado es un test incomodo y verboso (ejemplo a continuación) que nos oculta la intención de un test muy simple y las expectativas del resultado -Una alternativa más elegante seria usar solo la aserción de una sola linea que tiene Chai: expect(method).to.throw (o en Jest: expect(method).toThrow()). Es totalmente obligatorio también asegurarse de que la excepción contenga una propiedad que indique el tipo de error, de lo contratio, lanzando solo un error generico, la app no podra hacer mucho mas que mostrarle un error decepcionante para el usuario +Una alternativa más elegante seria usar solo la aserción de una sola linea que tiene Chai: expect(method).to.throw (o en Jest: expect(method).toThrow()). Es totalmente obligatorio también asegurarse de que la excepción contenga una propiedad que indique el tipo de error, de lo contrario, lanzando solo un error genérico, la app no podrá hacer mucho más que mostrarle un error decepcionante para el usuario
    -❌ **De lo contrario:** Sería muy dificil deducir a partir de los reportes de test (por ejemplo, reporte de CI) que es lo que ha salido mal +❌ **De lo contrario:** Sería muy difícil deducir a partir de los reportes de test (por ejemplo, reporte de CI) que es lo que ha salido mal
    @@ -612,14 +612,14 @@ it("When no product name, it throws error 400", async () => { errorWeExceptFor = error; } expect(errorWeExceptFor).not.to.be.null; - //si esta afirmación falla, el resultado/reporte del test solo mostrará - //que algunos valores es null, no se monstrara que falta una excepción + //si esta aserción falla, el resultado/reporte del test solo mostrará + //que algunos valores son null, no se mostrara que falta una excepción }); ```
    -### :clap: Ejemplo de cómo hacerlo correctamente: Una afirmacion legible para una persona puede ser comprendida facilmente, tanto por el QA como por el PM +### :clap: Ejemplo de cómo hacerlo correctamente: Una afirmación legible para una persona puede ser comprendida fácilmente, tanto por el QA como por el PM ```javascript it("When no product name, it throws error 400", async () => { @@ -633,13 +633,13 @@ it("When no product name, it throws error 400", async () => {

    -## ⚪ ️ 1.11 Tagea tus test +## ⚪ ️ 1.11 Etiqueta tus test :white_check_mark: **Haz:** Deben ejecutarse diferentes test en diferentes escenarios: quick smoke, IO-less, los test deben ejecutarse cuando un desarrollador guarda o hace commit de un fichero, los test end-to-end suelen ejecutarse cuando un nuevo pull request es añadido, etc. Esto se puede lograr etiquetando los test con tags como #cold #api #sanity para que se pueda filtrar e invocar solo el subconjunto deseado. Por ejemplo, así es como se ejecutan solo el grupo sanity test con Mocha: mocha — grep ‘sanity’
    -❌ **De lo contrario:** Ejecutar todos los test, incluidos los test que realizan docenas de queries a DB, cada vez que un desarrollador hace un pequeño cambio, puede ser extremadamente lento y provocar que los desarrolladores ignoren correr los test +❌ **De lo contrario:** Ejecutar todos los test, incluidos los test que realizan docenas de queries a base de datos, cada vez que un desarrollador hace un pequeño cambio, puede ser extremadamente lento y provocar que los desarrolladores ignoren correr los test
    @@ -647,17 +647,17 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Tagear los test como ‘#cold-test’ permite que el test runner ejecute solo los test más rápidos (Cold === 'test rapidos' que no estan haciendo operaciones de IO y que pueden ser ejecutados con frecuencia incluso mientras el desarrollador está escribiendo código) +### :clap: Ejemplo de cómo hacerlo correctamente: Etiquetar los test como ‘#cold-test’ permite que el test runner ejecute solo los test más rápidos (Cold === 'test rápidos' que no están haciendo operaciones de IO y que pueden ser ejecutados con frecuencia incluso mientras el desarrollador está escribiendo código) ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript -//este test es rapido (sin DB) y lo estamos tageando correctamente +//este test es rápido (sin DB) y lo estamos etiquetando correctamente //ahora el usuario/CI puede ejecutarlo con frecuencia 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() { - //logica aquí + //lógica aquí }); }); }); @@ -669,11 +669,11 @@ describe("Order service", function() { ## ⚪ ️ 1.12 Categoriza los test en al menos 2 niveles -:white_check_mark: **Haz:** Aplique cierta estructura a su conjunto de test para que un visitante ocasional pueda comprender facilmente los requisitos (los test siempre son la mejor documentación) y los diversos escenarios que estamos testeando. Una practica comun para esto es crear al menos 2 bloques 'describe' antes de tus test: el primero es para el nombre de la unidad que está siendo testeada y el segundo es para un nivel adicional de categorización como el escenario o las categorias personalizadas (ver ejemplos de código y pantallazos más abajo). Hacerlo también mejorará los reportes de los test: quien los lea deducirá facilmente las categorias de los test, profundizará en aquellas que lo desee y podrá relacionar mejor los test fallidos. Además, será mucho más fácil para un desarollador navegar a traves del código de un cojunto de test amplio. Existen múltiples formas de estructurar tus test que deben ser consideradas, como [given-when-then](https://github.com/searls/jasmine-given) y [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **Haz:** Aplica cierta estructura a tu conjunto de test para que un visitante ocasional pueda comprender fácilmente los requisitos (los test siempre son la mejor documentación) y los diversos escenarios que estamos testeando. Una practica común para esto es crear al menos 2 bloques 'describe' antes de tus test: el primero es para el nombre de la unidad que está siendo testeada y el segundo es para un nivel adicional de categorización como el escenario o las categorías personalizadas (ver ejemplos de código y pantallazos más abajo). Hacerlo también mejorará los reportes de los test: quien los lea deducirá fácilmente las categorías de los test, profundizará en aquellas que lo desee y podrá relacionar mejor los test fallidos. Además, será mucho más fácil para un desarrollador navegar a través del código de un conjunto de test amplio. Existen múltiples formas de estructurar tus test que deben ser consideradas, como [given-when-then](https://github.com/searls/jasmine-given) y [RITE](https://github.com/ericelliott/riteway)
    -❌ **De lo contrario:** Cuando nos enfrentamos a un reporte con una lista plana de test, tendremos que leer rápidamente textos largos para determinar los escenarios principales y relacionar los test fallidos. Considera el siguiente caso: Cuando 7/100 test fallan, revisar una lista plana te exige leer el texto de los test que fallan para ver como se relacionan entre ellos y que tienen en común. Sin embargo, en un reporte jerarquizado, si los 7 estan bajo un mismo flujo o categoria, puedes saber rápidamente cual o donde puede estar la causa raiz del fallo +❌ **De lo contrario:** Cuando nos enfrentamos a un reporte con una lista plana de test, tendremos que leer rápidamente textos largos para determinar los escenarios principales y relacionar los test fallidos. Considera el siguiente caso: Cuando 7/100 test fallan, revisar una lista plana te exige leer el texto de los test que fallan para ver como se relacionan entre ellos y que tienen en común. Sin embargo, en un reporte jerarquizado, si los 7 están bajo un mismo flujo o categoría, puedes saber rápidamente cual o donde puede estar la causa raíz del fallo
    @@ -686,7 +686,7 @@ describe("Order service", function() { ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript -// Unida que se está testeando +// Unidad que se está testeando describe("Transfer service", () => { //Escenario describe("When no credit", () => { @@ -703,7 +703,7 @@ describe("Transfer service", () => {
    -### :thumbsdown: Ejemplo Anti Patrón: Una lista plana de test hará mas dificil a quien lo lea el identificat las historias de usuario y detectar los test que estan fallando +### :thumbsdown: Ejemplo Anti Patrón: Una lista plana de test hará más difícil a quien lo lea el identificar las historias de usuario y detectar los test que están fallando ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Mocha") @@ -727,20 +727,20 @@ test("Then there should not be a new transfer record", () => {}); :white_check_mark: **Haz:** Esta publicación se centra en consejos de test relacionados con, o al menos, que se pueden ejemplificar en Node JS. Sin embargo, esta sección agrupa algunos consejos no relacionados con Node que son bien conocidos -Aprenda y practique [principios TDD](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — son extremadamente valiosos para muchos pero no te dejes intimidar si no se ajustan a tu estilo, no eres el único. Considera escribir los test antes que el código con el [estilo rojo-verde-refactor](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), te asegura que cada test chequea exactamente una cosa, cuando encuentras un bug — antes de corregirlo escribe un test que lo detecte como bug en el futuro, dejando que cada test falle al menos una vez antes de convertirlo en un verde, comienza el inicio de un modulo escribieno código muy simple y rapodamente, que satisfaga el test, luego lo refatorizamos gradualmente hasta que nuestro código tenga el nivel deseado en producción, evitando siempre cualquier dependencia con el entorno (rutas en disco, sistema operativo, etc) +Aprenda y practique [principios TDD](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — son extremadamente valiosos para muchos pero no te dejes intimidar si no se ajustan a tu estilo, no eres el único. Considera escribir los test antes que el código con el [estilo rojo-verde-refactor](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), te asegura que cada test chequea exactamente una cosa, cuando encuentras un bug — antes de corregirlo escribe un test que lo detecte como bug en el futuro, dejando que cada test falle al menos una vez antes de convertirlo en un verde, comienza el inicio de un modulo escribiendo código muy simple y rápidamente, que satisfaga el test, luego lo refatorizamos gradualmente hasta que nuestro código tenga el nivel deseado en producción, evitando siempre cualquier dependencia con el entorno (rutas en disco, sistema operativo, etc)
    ❌ **De lo contrario:** Echarás de menos las perlas de sabiduría que se han ido recolectando durante décadas

    -# Section 2️⃣: Backend Testing +# Sección 2️⃣: Backend Testing -## ⚪ ️2.1 Enriqueze tu abanico de test: mira más allá de los test unitarios y la pirámide +## ⚪ ️2.1 Enriquece tu abanico de test: mira más allá de los test unitarios y la pirámide -:white_check_mark: **Haz:** La [pirámide de test](https://martinfowler.com/bliki/TestPyramid.html), con 10> años de antiguedad, es un modelo excelente y relevante que sugiere tres tipos de test e influye en la estrategia de testeo de la mayoría de los desarrolladores. Al mismo tiempo, surgieron un puñado de nuevas y brillantes técnicas de testeo que se esconden en las sombras de la pirámide de test. Dados todos los cambios que hemos visto en los últimos 10 años (Microservicios, cloud, serverless), ¿es posible que un modelo algo antiguo se adapte a *todos* los tipos de aplicaciones? ¿No debería el mundo del testing considerar aceptar nuevas técnicas? +:white_check_mark: **Haz:** La [pirámide de test](https://martinfowler.com/bliki/TestPyramid.html), con 10> años de antigüedad, es un modelo excelente y relevante que sugiere tres tipos de test e influye en la estrategia de testeo de la mayoría de los desarrolladores. Al mismo tiempo, surgieron un puñado de nuevas y brillantes técnicas de testeo que se esconden en las sombras de la pirámide de test. Dados todos los cambios que hemos visto en los últimos 10 años (microservicios, cloud, serverless), ¿es posible que un modelo algo antiguo se adapte a *todos* los tipos de aplicaciones? ¿No debería el mundo del testing considerar aceptar nuevas técnicas? -No me malinterpretes, en 2019 la pirámide de test, el TDD y los test unitarios siguen siendo una técnica buena y probablemente sean la mejor combinación para muchas aplicaciones. Solo como cualquier otro modelo, a pesar de su utilidad, [a veces debe estar equivocado] (https://en.wikipedia.org/wiki/All_models_are_wrong). Por ejemplo, considere una aplicación IOT que ingiere muchos eventos en un bus de mensajes como Kafka / RabbitMQ, que luego fluyen a algún data-warehouse y finalmente son consultados por alguna UI de análisis. ¿Realmente deberíamos gastar el 50% de nuestro presupuesto para test en escribir test unitarios para una aplicación que esté centrada en la integración y apenas tenga lógica? A medida que aumenta la diversidad de tipos de aplicaciones (bots, criptografía, Alexa-skills), aumentan las posibilidades de encontrar escenarios en los que la pirámide de test no sea la mejor opción +No me malinterpretes, en 2019 la pirámide de test, el TDD y los test unitarios siguen siendo una técnica buena y probablemente sean la mejor combinación para muchas aplicaciones. Sólo como cualquier otro modelo, a pesar de su utilidad, [a veces debe estar equivocado] (https://en.wikipedia.org/wiki/All_models_are_wrong). Por ejemplo, considera una aplicación IOT que ingiere muchos eventos en un bus de mensajes como Kafka / RabbitMQ, que luego fluyen a algún data-warehouse y finalmente son consultados por alguna UI de análisis. ¿Realmente deberíamos gastar el 50% de nuestro presupuesto para test en escribir test unitarios para una aplicación que esté centrada en la integración y apenas tenga lógica? A medida que aumenta la diversidad de tipos de aplicaciones (bots, criptografía, Alexa-skills), aumentan las posibilidades de encontrar escenarios en los que la pirámide de test no sea la mejor opción Es hora de enriquecer el abanico de test y familiarizarse con más tipos de test (las siguientes secciones sugieren algunas ideas), modelos como la pirámide de test, pero también hacer coincidir los tipos de test con los problemas del mundo real al que te enfrentas ('Hola, nuestra API está rota, ¡escribamos contract testing dirigidos al consumidor!'), diversifica tus test como un inversor que construye una cartera de inversión basada en el análisis de riesgos — evalúa dónde pueden surgir problemas y combina algunas medidas de prevención para mitigar esos riesgos potenciales @@ -756,7 +756,7 @@ Una advertencia: el TDD en el mundo del software adopta una cara de falsa dicoto
    -### :clap: Ejemplo de cómo hacerlo correctamente: Cindy Sridharan sugiere un portfolio de test amplio en su increíble publicación ‘Testing Microservices — the same way’ +### :clap: Ejemplo de cómo hacerlo correctamente: Cindy Sridharan sugiere un porfolio de test amplio en su increíble publicación ‘Testing Microservices — the same way’ ![alt text](assets/bp-12-rich-testing.jpeg "Cindy Sridharan sugiere un portfolio de test amplio en su increíble publicación ‘Testing Microservices — the same way’") @@ -770,13 +770,13 @@ Una advertencia: el TDD en el mundo del software adopta una cara de falsa dicoto

    -## ⚪ ️2.2 Component testing might be your best affair +## ⚪ ️2.2 Los test de Componentes pueden ser tu mejor amigo :white_check_mark: **Haz:** -Cada test unitario cubre una pequeña parte de la aplicación y cubrirla totalmente cuesta muchisimo, mientras que los test end-to-end cubren facilmente mucho terreno, pero son costosas y más lentas, ¿por qué no aplicar un enfoque equilibrado y escribir test más grandes que test unitarios pero más pequeños que los test end-to-end? Los test de componente es la canción no cantada del mundo del testing — proporcionan lo mejor de ambos mundos: rendimiento razonable y la posibilidad de aplicar patrones TDD + cobertura realista +Cada test unitario cubre una pequeña parte de la aplicación y cubrirla totalmente cuesta muchísimo, mientras que los test end-to-end cubren fácilmente mucho terreno, pero son costosos y más lentos, ¿por qué no aplicar un enfoque equilibrado y escribir test más grandes que test unitarios pero más pequeños que los test end-to-end? Los test de componente es la canción no cantada del mundo del testing — proporcionan lo mejor de ambos mundos: rendimiento razonable y la posibilidad de aplicar patrones TDD + cobertura realista -Los test de componente se centran en la 'unidad' de Microservicios, funcionan contra la API, no mockean nada que pertenezca al Microservico en sí (por ejemplo, DB real, o al menos la versión en memoria de esa DB) pero hace stub de cualquier cosa que sea externa como llamadas a otros microservicios. Al hacerlo, probamos lo que desplegamos, nos acercamos a la aplicación de fuera a dentro y obtenemos una gran confianza en un período de tiempo razonable +Los test de componente se centran en la 'unidad' de microservicios, funcionan contra la API, no mockean nada que pertenezca al microservicio en sí (por ejemplo, base de datos real, o al menos la versión en memoria de esa base de datos) pero hace stub de cualquier cosa que sea externa como llamadas a otros microservicios. Al hacerlo, probamos lo que desplegamos, nos acercamos a la aplicación de fuera a dentro y obtenemos una gran confianza en un período de tiempo razonable
    @@ -788,24 +788,24 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co
    -### :clap: Ejemplo de cómo hacerlo correctamente: Supertest permite acercarse al API Express in-process (rápido y cubre muchas capas) +### :clap: Ejemplo de cómo hacerlo correctamente: Supertest permite acercarse al API Express (rápido y cubre muchas capas) ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Ejemplos con Mocha") -![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) permite acercarse al API Express in-process (rápido y cubre muchas capas)") +![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) permite acercarse al API Express (rápido y cubre muchas capas)")


    -## ⚪ ️2.3 Ensure new releases don’t break the API using +## ⚪ ️2.3 Asegúrate de que las nuevas versiones no rompan el API en uso -:white_check_mark: **Haz:** Pongamos que tu Microservicio tiene multiples consumidores, y tenemos en ejecucion 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 ningun test porque el servidor controla las fechas de release. [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 libreria PACT para detectar contratos incumplidos — una expectativa de consumidor no cumplida. Al hacerlo, todos los desajustes de la API cliente-servidor se detectan muyt pronto durante la construcción / CI y pueden ahorrarte mucha frustración +: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
    -❌ **De lo contrario:** Las alternativas son test manuales agotadoras o miedo al despliegue +❌ **De lo contrario:** Las alternativas son test manuales agotadores o miedo al despliegue
    @@ -823,9 +823,9 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co

    -## ⚪ ️ 2.4 Test your middlewares in isolation +## ⚪ ️ 2.4 Testea tus middlewares aisladamente -:white_check_mark: **Haz:** Muchos evitan los test de middleware porque representan una pequeña porción del sistema y requieren ejecutar un servidor Express. Ambas razones son incorrectas — los middlewares son pequeños pero afectan a todas o la mayoría de las solicitudes y pueden testearse fácilmente como funciones puras que obtienen {req, res} objetos JS. Para testear una función de middleware se debe invocar y usar spy ([usando Sinon, por ejemplo] (https://www.npmjs.com/package/sinon)) sobre la interacción con los objetos {req, res} para garantizar que nuestra función middleware realiz la acción correcta. La libreria [node-mock-http] (https://www.npmjs.com/package/node-mocks-http) lo lleva aún más lejos y factoriza los objetos {req, res} ademas de añadir el spy. Por ejemplo, puede asertar si el estado http que se estableció en el objeto res coincide con el esperado (consulta el ejemplo a continuación) +:white_check_mark: **Haz:** Muchos evitan los test de middleware porque representan una pequeña porción del sistema y requieren ejecutar un servidor Express. Ambas razones son incorrectas — los middlewares son pequeños pero afectan a todas o la mayoría de las solicitudes y pueden testearse fácilmente como funciones puras que obtienen {req, res} objetos JS. Para testear una función de middleware se debe invocar y usar spy ([usando Sinon, por ejemplo] (https://www.npmjs.com/package/sinon)) sobre la interacción con los objetos {req, res} para garantizar que nuestra función middleware realiza la acción correcta. La librería [node-mock-http] (https://www.npmjs.com/package/node-mocks-http) lo lleva aún más lejos y factoriza los objetos {req, res} ademas de añadir el spy. Por ejemplo, puede asertar si el estado http que se estableció en el objeto res coincide con el esperado (consulta el ejemplo a continuación)
    @@ -842,7 +842,7 @@ Los test de componente se centran en la 'unidad' de Microservicios, funcionan co ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Ejemplos con Jest") ```javascript -//ek middleware que queremos testear +//el middleware que queremos testear const unitUnderTest = require("./middleware"); const httpMocks = require("node-mocks-http"); //Sintaxis de Jest, equivalente a describe() e it() en Mocha @@ -866,13 +866,13 @@ test("A request without authentication header, should return http status 403", ( ## ⚪ ️2.5 Mide y refactoriza utilizando herramientas de análisis estático -:white_check_mark: **Haz:** El uso de herramientas de análisis estático ayuda proporcionando formas objetivas para mejorar la calidad del código y a tener el código mantenible. Puede agregar herramientas de análisis estático a su pipeline de CI para abortar cuando encuentre code smells. Sus principales beneficios sobre el linter plano son la habilidad de analizar la calidad en el contexto de multiples ficheros (por ejemplo encontrar duplicados), realizando analisis avanzados (por ejemplo complegidad del código) y siguiendo el historial y el progreso de cada problema. Dos ejemplos de herramientas que puedes usar son [Sonarqube](https://www.sonarqube.org/) (2,600+ [estrellas](https://github.com/SonarSource/sonarqube)) y [Code Climate](https://codeclimate.com/) (1,500+ [estrellas](https://github.com/codeclimate/codeclimate)) +:white_check_mark: **Haz:** El uso de herramientas de análisis estático ayuda proporcionando formas objetivas para mejorar la calidad del código y a tener el código mantenible. Puede agregar herramientas de análisis estático a su pipeline de CI para abortar cuando encuentre code smells. Sus principales beneficios sobre el linter plano son la habilidad de analizar la calidad en el contexto de múltiples ficheros (por ejemplo encontrar duplicados), realizando análisis avanzados (por ejemplo complejidad del código) y siguiendo el historial y el progreso de cada problema. Dos ejemplos de herramientas que puedes usar son [Sonarqube](https://www.sonarqube.org/) (2,600+ [estrellas](https://github.com/SonarSource/sonarqube)) y [Code Climate](https://codeclimate.com/) (1,500+ [estrellas](https://github.com/codeclimate/codeclimate)) Crédito:
    [Keith Holliday](https://github.com/TheHollidayInn)
    -❌ **De lo contrario:** Con una mala calidad de código, los bugs y el rendimiento siempre serán un problema que ninguna libreria completamente nueva o características punteras van a poder solucionar +❌ **De lo contrario:** Con una mala calidad de código, los bugs y el rendimiento siempre serán un problema que ninguna librería completamente nueva o características punteras van a poder solucionar
    @@ -880,19 +880,19 @@ Crédito:

    -## ⚪ ️ 2.6 Compueba tu predisposicion al caos relacionado con Node +## ⚪ ️ 2.6 Comprueba tu predisposición al caos relacionado con Node -:white_check_mark: **Haz:** Extrañamente, la matroia de los test de software son acerca de logica y datos, pero los errores más grabes (y que son muy dificiles de resolver) son problemas de infraestructura. Por ejemplo, ¿alguna vez has testeado que pasa cuando se sobrecarga la memoria? ¿o cuando el servidor/proceso muere? ¿o tu sitema de monitorización es capar de darse cuenta cuando la API es un 50% más lenta de lo normal? Para probar y evitar este tipo de problemas — [Chaos engineering](https://principlesofchaos.org/) fue creado por Netflix. Su objetivo es proporcionar conciencia, frameworks, y herramientas para testear la resiliencia de nuestras aplicaciones en problmeas caoticos. Por ejemplo, una de las herramientas mas conocidas [the chaos monkey](https://github.com/Netflix/chaosmonkey), mata servidores de forma aleatoria para comprobar si nuestro servicio aun puede dar servicio a los usuarios y asegurar que no depende de un solo servido (hay tambien una version de kubernetes, [kube-monkey](https://github.com/asobti/kube-monkey), que mata pods en vez de servidores. Todas estas herramientas funcionan a nivel de hosting/plataforma, pero ¿qué pasa si deseas probar y generar caos a nivel Node puramente como comprobar como tu proceso Node hace frente a errores no controlados, o a rejects de promesas no capturados, o sobrecarga de la memoeria de v8 por encima del máximo de 1.7GB o si la UX sigue siendo bueno si se satura el event loop? Para todo esto he escrito [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) que proporciona todo tipo de formas de crear el caos en Node +:white_check_mark: **Haz:** Extrañamente, la mayoría de los test de software son acerca de lógica y datos, pero los errores más grabes (y que son muy difíciles de resolver) son problemas de infraestructura. Por ejemplo, ¿alguna vez has testeado que pasa cuando se sobrecarga la memoria? ¿o cuando el servidor/proceso muere? ¿o tu sistema de monitorización es capaz de darse cuenta cuando la API es un 50% más lenta de lo normal? Para probar y evitar este tipo de problemas — [Chaos engineering](https://principlesofchaos.org/) fue creado por Netflix. Su objetivo es proporcionar conciencia, frameworks, y herramientas para testear la resiliencia de nuestras aplicaciones en problemas caóticos. Por ejemplo, una de las herramientas más conocidas [the chaos monkey](https://github.com/Netflix/chaosmonkey), mata servidores de forma aleatoria para comprobar si nuestro servicio aun puede dar servicio a los usuarios y asegurar que no depende de un solo servidor (hay también una versión para kubernetes, [kube-monkey](https://github.com/asobti/kube-monkey), que mata pods en vez de servidores. Todas estas herramientas funcionan a nivel de hosting/plataforma, pero ¿qué pasa si deseas probar y generar caos a nivel Node puramente como comprobar como tu proceso Node hace frente a errores no controlados, o a rejects de promesas no capturados, o sobrecarga de la memoria de v8 por encima del máximo de 1.7GB o si la UX sigue siendo buena si se satura el event loop? Para todo esto he escrito [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) que proporciona todo tipo de formas de crear el caos en Node
    ❌ **De lo contrario:** No hay escapatoria, la ley de Murphy afectará a producción sin piedad @@ -903,9 +903,9 @@ Crédito:
    @@ -913,11 +913,11 @@ Crédito: -❌ **De lo contrario:** FMuchos test fallaran, un despliegue se aborta, nuestro equipo perdera mucho tiempo, ¿tenemos un bug? vamos a investigar, ah no — parece que dos test estan mutando el mismo dato +❌ **De lo contrario:** Muchos test fallaran, un despliegue se aborta, nuestro equipo perderá mucho tiempo, ¿tenemos un bug? vamos a investigar, ah no — parece que dos test están mutando el mismo dato
    @@ -925,13 +925,13 @@ Crédito:
    { - //añadiendo datos de sites y admin a nuestra DB. ¿Donde estan los datos? fuera. En un json extermo o en un modelo da migración + //añadiendo datos de sites y admin a nuestra DB. ¿Donde están los datos? fuera. En un json externo o en un modelo da migración await DB.AddSeedDataFromJson('seed.json'); }); it("When updating site name, get successful confirmation", async () => { @@ -950,11 +950,11 @@ it("When querying by site name, get the right site", async () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Podemos permanecer dentro de nuestro test, cada test actua sobre su propio conjunto de datos +### :clap: Ejemplo de cómo hacerlo correctamente: Podemos permanecer dentro de nuestro test, cada test actúa sobre su propio conjunto de datos ```javascript it("When updating site name, get successful confirmation", async () => { - //el test esta añadiendo registors nuevos cada vez y actuando solo en esos registros + //el test esta añadiendo registros nuevos cada vez y actuando solo en esos registros const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); @@ -969,11 +969,11 @@ it("When updating site name, get successful confirmation", async () => {

    -# Section 3️⃣: Frontend Testing +# Sección 3️⃣: Frontend Testing -## ⚪ ️ 3.1 Separate UI from functionality +## ⚪ ️ 3.1 Separa la UI de la funcionalidad -:white_check_mark: **Haz:** Al centrarnos en testear la lógica del component, los detalles de la interfaz de usuario solo pueden entorpecernos, por lo qu debes abstraerte de ellos y que los test se centren en datos puros. En la practica, extrae los datos que necesites de una manera abstracta sin que este acoplada a la interfaz grafica, haz aserciones de los datos puros (vs detalles visuales en HTML/CSS) y desactiva las animaciones que pueden hacer lenta la interfaz. En este punto podrias pensar en desactivar la interfaz y solo hacer test de la parte back del UI (por ejemplo servicios, acciones, store) pero esto solo dará como resultado test ficticios, diferentes a la realidad y no desvelaran casos en los que los datos correctos no llegan a la interfaz de usuario +:white_check_mark: **Haz:** Al centrarnos en testear la lógica del componente, los detalles de la interfaz de usuario solo pueden entorpecernos, por lo qu debes abstraerte de ellos y que los test se centren en datos puros. En la practica, extrae los datos que necesites de una manera abstracta sin que este acoplada a la interfaz grafica, haz aserciones de los datos puros (vs detalles visuales en HTML/CSS) y desactiva las animaciones que pueden hacer lenta la interfaz. En este punto podrías pensar en desactivar la interfaz y solo hacer test de la parte back del UI (por ejemplo servicios, acciones, store) pero esto solo dará como resultado test ficticios, diferentes a la realidad y no desvelaran casos en los que los datos correctos no llegan a la interfaz de usuario
    @@ -1000,7 +1000,7 @@ test("When users-list is flagged to show only VIP, should display only VIP membe // Afirmar - Extrae los datos de la UI primero const allRenderedUsers = getAllByTestId("user").map(uiElement => uiElement.textContent); const allRealVIPUsers = allUsers.filter(user => user.vip).map(user => user.name); - expect(allRenderedUsers).toEqual(allRealVIPUsers); //compara datos con datos, aqui no hay UI + expect(allRenderedUsers).toEqual(allRealVIPUsers); //compara datos con datos, aquí no hay UI }); ``` @@ -1025,13 +1025,13 @@ test("When flagging to show only VIP, should display only VIP members", () => {

    -## ⚪ ️ 3.2 Consulta elementos HTML basandote en atributos que no deberian cambiar +## ⚪ ️ 3.2 Consulta elementos HTML basándote en atributos que no deberían cambiar -:white_check_mark: **Haz:** Consulta elementos HTML basandote en attributos que deberian permanecer intactos a cambios graficos al contrario que selectores CSS y etiquetas de los formularios. Si el elemento designado no tiene esos attributos, crea un atributo dedicado solamente a los test como 'test-id-submit-button'. Seguir este patron no solo asegura que tus test funcionales/logicos no se rompan nunca por cambios esteticos, sino que tambien queda claro a cualquier desarrollador que ese elemento y atributo estan ahi por y para los test y no deben eliminarse +:white_check_mark: **Haz:** Consulta elementos HTML basándote en atributos que deberían permanecer intactos a cambios gráficos al contrario que selectores CSS y etiquetas de los formularios. Si el elemento designado no tiene esos atributos, crea un atributo dedicado solamente a los test como 'test-id-submit-button'. Seguir este patrón no solo asegura que tus test funcionales/lógica no se rompan nunca por cambios estéticos, sino que también queda claro a cualquier desarrollador que ese elemento y atributo están ahí por y para los test y no deben eliminarse
    -❌ **De lo contrario:** Quieres testear el login de tu app que tiene muchos componentes, lógica, servicios, y todo esta bien configurado - stubs, spies, las llamadas Ajax estan aisladas. All seems perfect. Entonces el test falla porque el diseñador ha cambiado la clase de un div de 'thick-border' a 'thin-border' +❌ **De lo contrario:** Quieres testear el login de tu app que tiene muchos componentes, lógica, servicios, y todo esta bien configurado - stubs, spies, las llamadas Ajax están aisladas. Todo parece perfecto. Entonces el test falla porque el diseñador ha cambiado la clase de un div de 'thick-border' a 'thin-border'
    @@ -1068,7 +1068,7 @@ test("Whenever no data is passed to metric, show 0 as default", () => {
    -### :thumbsdown: Ejemplo Anti Patrón: Confiando en attributos CSS +### :thumbsdown: Ejemplo Anti Patrón: Confiando en atributos CSS ```html @@ -1093,11 +1093,11 @@ test("Whenever no data is passed, error metric shows zero", () => { :white_check_mark: **Haz:** Siempre que tenga un tamaño razonable, testea tu componente como lo hacen tus usuarios, renderiza completamente la interfaz de usuario, actúa sobre ella y comprueba que la interfaz se comporta como esperabas. Evita todo tipo de mocks, partials o shadow rendering - hacerlo puede provocar bugs no detectados debido a la falta de detalles y dificultará el mantenimiento a medida que los test interfieren con las partes internas (consulte la sección 'Acercarse al testing caja-negra'). Si uno de los componentes hijos ralentiza significativamente tu test (por ejemplo por una animación) o complica el setup, considera reemplazarlo explícitamente por un fake -Con todo esto tambien es necesario tener ciertas precauciones: esta técnica funciona para componentes pequeños / medianos que contienen un número razonable de componentes hijos. Renderizar completamente un componente con demasiados hijos hará que sea difícil analizar los fallos de los test (análisis de causa raíz) y puede ser demasiado lento. En estos casos, escribe los menos test que necesites contra ese componente principal y más test contra sus hijos +Con todo esto también es necesario tener ciertas precauciones: esta técnica funciona para componentes pequeños / medianos que contienen un número razonable de componentes hijos. Renderizar completamente un componente con demasiados hijos hará que sea difícil analizar los fallos de los test (análisis de causa raíz) y puede ser demasiado lento. En estos casos, escribe los menos test que necesites contra ese componente principal y más test contra sus hijos
    -❌ **De lo contrario:** Al hurgar en las partes internas de un componente, invocando sus metodos privados y verificando el estado interno - tendras que refactorizar todos los test siempre que refactorices los componentes ¿Realmente quieres dedicar ese tiempo en hacer este mantenimiento? +❌ **De lo contrario:** Al hurgar en las partes internas de un componente, invocando sus métodos privados y verificando el estado interno - tendrás que refactorizar todos los test siempre que refactorices los componentes ¿Realmente quieres dedicar ese tiempo en hacer este mantenimiento?
    @@ -1149,12 +1149,12 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display .find("filtersPanel") .instance() .showFilters(); - // Aprovecha las partes internas, saltandote la UI e invocando el metodo ditectamente. + // Aprovecha las partes internas, saltándote la UI e invocando el método directamente. // Aproximación caja-blanca // Afirmar expect(wrapper.find("Filter").props()).toEqual({ title: "Choose Filter" }); - // ¿Qué pasa si cambiamos el nombre de la prop o si no pasamos nada relevante? + // ¿Qué pasa si cambiamos el nombre de la propiedad o si no pasamos nada relevante? }); ``` @@ -1162,13 +1162,13 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -## ⚪ ️ 3.4 No pauses, usa soporte del framewok para eventos asincronos. Tambien intenta acelerar las cosas +## ⚪ ️ 3.4 No pauses, usa el soporte del framewok para eventos asincronos e intenta acelerar las cosas -:white_check_mark: **Haz:** En muchos casos, el tiempo que tarda una unidad bajo test es desconocido (por ejemplo, la animación elimina la visualización de un elemento) - en este caso, evita esperar (por ejemplo stTimeOut) y elige metodos mas deterministas que la mayoria de las plataformas proveen. Algunas librerias permiten esperar en operaciones (por ejemplo [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), otras proveen un API para esperar como [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). A veces, una forma más elegante es hacer stub del recurso lento, como una API por ejemplo, con lo que el momento de respuesta se vuelve determinista., y volvemos a poder renderizar el componente directamente. Cuando tengas dependencias con algun componente externo que espera, puede ser util modificar el tiempo con librerias como [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Esperar es algo que debemos evitar siempre, por que fuerza que tu test sea lento o tenga ciertos riesgos (cuando esperas un tiempo muy bajo). Siempre que sea inevitable esperar y hacer polling, y el framework de testing no tenga soporte, algunas librerias de npm pueden ayudar con soluciones semi-deterministas, como [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) +:white_check_mark: **Haz:** En muchos casos, el tiempo que tarda una unidad bajo test es desconocido (por ejemplo, la animación elimina la visualización de un elemento) - en este caso, evita esperar (por ejemplo stTimeOut) y elige métodos mas deterministas que la mayoría de las plataformas proveen. Algunas librerías permiten esperar en operaciones (por ejemplo [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), otras proveen un API para esperar como [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). A veces, una forma más elegante es hacer stub del recurso lento, como una API por ejemplo, con lo que el momento de respuesta se vuelve determinista, y volvemos a poder renderizar el componente directamente. Cuando tengas dependencias con algún componente externo que espera, puede ser útil modificar el tiempo con librerías como [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Esperar es algo que debemos evitar siempre, por que fuerza que tu test sea lento o tenga ciertos riesgos (cuando esperas un tiempo muy bajo). Siempre que sea inevitable esperar y hacer polling, y el framework de testing no nos da soporte, algunas librerías de npm pueden ayudar con soluciones semi-deterministas, como [wait-for-expect](https://www.npmjs.com/package/wait-for-expect)
    -❌ **De lo contrario:** Cuando se espera mucho tiempo, los test seran un orden de magnitud más lentos. Cuando intentes espera tiempos bajos, los test fallarán cuando la unidad bajo test no haya respondido a tiempo. Por tanto, se reduce a balancear entre puntos debiles y el rendimiento malo +❌ **De lo contrario:** Cuando se espera mucho tiempo, los test serán un orden de magnitud más lentos. Cuando intentes esperar tiempos bajos, los test fallarán cuando la unidad bajo test no haya respondido a tiempo. Por tanto, se reduce a balancear entre puntos débiles y el rendimiento malo
    @@ -1185,10 +1185,10 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display // usando Cypress cy.get("#show-products").click(); // navegar cy.wait("@products"); // esperar a que la ruta aparezca -// esta linea será ejecutada solo cuando la ruta haya teminado +// esta linea será ejecutada solo cuando la ruta haya terminado ``` -### :clap: Ejemplo de cómo hacerlo correctamente: Libreria de testing que espera a elementos del DOM +### :clap: Ejemplo de cómo hacerlo correctamente: Liberia de testing que espera a elementos del DOM ```javascript // @testing-library/dom @@ -1211,7 +1211,7 @@ test("movie title appears", async () => { test("movie title appears", async () => { // el elemento no esta presente al comenzar... - // espera con logica propia (precaución: simplista, sin tiempo de espera) + // espera con lógica propia (precaución: simplista, sin tiempo de espera) const interval = setInterval(() => { const found = getByText("the lion king"); if (found) { @@ -1233,7 +1233,7 @@ test("movie title appears", async () => { ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Ejemplos con Lighthouse") -✅ **Haz:** Usa un monitor que garantize que la carga de la página esté optimizada - esto incluye cualquier problema de UX como descarga lenta o un paquete no minimizado. El mercado de herramientas de este tipo no es pequeño: herramientas basicas como [pingdom](https://www.pingdom.com/), AWS CloudWatch, [gcp StackDriver](https://cloud.google.com/monitoring/uptime-checks/) se puede configurar para ver si el servidor esta corriendo y respondiendo dentro de un SLA razonable. Esto solo rallar la superficie, podria haber muchimas cosas mal, portante es mejor optar por herramientas especializadas en frontend, (por ejemplo [lighthouse](https://developers.google.com/web/tools/lighthouse/), [pagespeed](https://developers.google.com/speed/pagespeed/insights/)) y hagan un analis mucho más amplio. El foco debe ponerse en los sintomas y metricas que afecten directamente a UX, como el tiempo de carga, [render mínimo](https://scotch.io/courses/10-web-performance-audit-tips-for-your-next-billion-users-in-2018/fmp-first-meaningful-paint), [tiempo hasta que la pagina es manejable (TTI)](https://calibreapp.com/blog/time-to-interactive/). Sobre todo esto, tambien debes estar atento a posibles causas tecnicas, como garantizar que el contenido sea comprimido, tiempo hasta el primer byte, optimizar imagenes, asegurar un tamaño del DOM razonable, SSL, y muchos más. Es aconsejable tener toda esta monitorización durante el desarrollo, como parte del CI y mucho más importante - 24x7 en los servidores de producción / CDN +✅ **Haz:** Usa un monitor que garantice que la carga de la página esté optimizada - esto incluye cualquier problema de UX como descarga lenta o un paquete no minimizado. El mercado de herramientas de este tipo no es pequeño: herramientas básicas como [pingdom](https://www.pingdom.com/), AWS CloudWatch, [gcp StackDriver](https://cloud.google.com/monitoring/uptime-checks/) se puede configurar para ver si el servidor esta corriendo y respondiendo dentro de un SLA razonable. Esto tan solo ralla la superficie, podría haber muchísimas cosas mal, por tanto es mejor optar por herramientas especializadas en frontend, (por ejemplo [lighthouse](https://developers.google.com/web/tools/lighthouse/), [pagespeed](https://developers.google.com/speed/pagespeed/insights/)) y que hagan un análisis mucho más amplio. El foco debe ponerse en los síntomas y métricas que afecten directamente a UX, como el tiempo de carga, [render mínimo](https://scotch.io/courses/10-web-performance-audit-tips-for-your-next-billion-users-in-2018/fmp-first-meaningful-paint), [tiempo hasta que la pagina es manejable (TTI)](https://calibreapp.com/blog/time-to-interactive/). Sobre todo esto, también debes estar atento a posibles causas técnicas, como garantizar que el contenido sea comprimido, tiempo hasta el primer byte, optimizar imágenes, asegurar un tamaño del DOM razonable, SSL, y muchos más. Es aconsejable tener toda esta monitorización durante el desarrollo, como parte del CI y mucho más importante - 24x7 en los servidores de producción / CDN
    @@ -1253,11 +1253,11 @@ test("movie title appears", async () => { ## ⚪ ️ 3.6 Usa stubs para recursos lentos como el API de back-end -:white_check_mark: **Haz:** Cuando programas tus test principales (no los test E2E), evita interactuar con cualquier recurse que este fuera de tu responsabilidad y control, como las API de back-end y usar stubs en su lugar (es decir, un doble de test). De forma practica, en vez de hacer llamadas de red reales al API, utiliza alguna libreria (como [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) para stubear las repuestas de API. El principal beneficio es evitar la inestabilidad - testeando o suplantando APIs, por definición, no son muy estables y de vez en cuando fallarán los test, aunque TU componente se comporte bien (en producción generalmente se aceleran las respuestas, pero no esta pensado para hacer test). Hacerlo te permitirá simular varios compartamientos de API que deberían definir el comportamiento de nuestro componente para caminos no felices, por ejemplo cuando no se encuentran datos o cuando API devuelve un error. Por último, pero no por ello menos importante, las peticiones de red hacen más lentos nuestros test +:white_check_mark: **Haz:** Cuando programas tus test principales (no los test E2E), evita interactuar con cualquier recurso que este fuera de tu responsabilidad y control, como las API de back-end y usar stubs en su lugar (es decir, un doble). De forma practica, en vez de hacer llamadas de red reales al API, utiliza alguna librería (como [Sinon](https://sinonjs.org/), [Test dobles](https://www.npmjs.com/package/testdouble), etc) para stubear las repuestas de API. El principal beneficio es evitar la inestabilidad - testeando o suplantando APIs, por definición, no son muy estables y de vez en cuando fallarán los test, aunque TU componente se comporte bien (en producción generalmente se aceleran las respuestas, pero no esta pensado para hacer test). Hacerlo te permitirá simular varios comportamientos de API que deberían definir el comportamiento de nuestro componente para caminos no felices, por ejemplo cuando no se encuentran datos o cuando API devuelve un error. Por último, pero no por ello menos importante, las peticiones de red hacen más lentos nuestros test
    -❌ **De lo contrario:** La media de ejecución de los test no dura más de unos pocos ms, una llamada a API estandar dura 100ms>, lo que lo hace cada test ~20x más lento +❌ **De lo contrario:** La media de ejecución de los test no dura más de unos pocos ms, una llamada a API estándar dura 100ms>, lo que lo hace cada test ~20x más lento
    @@ -1265,7 +1265,7 @@ test("movie title appears", async () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Stubear or interceptar las llamadas a API +### :clap: Ejemplo de cómo hacerlo correctamente: Stubear o interceptar las llamadas a API ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Ejemplos con React") ![](https://img.shields.io/badge/🔧%20Example%20using%20React%20Testing%20Library-blue.svg "Ejemplos con react-testing-library") @@ -1307,21 +1307,21 @@ test("When no products exist, show the appropriate message", () => { ## ⚪ ️ 3.7 Haz muy pocos test end-to-end que abarquen todo el sistema -:white_check_mark: **Haz:** Aunque E2E (end-to-end) para algunos significa solo hacer test de UI en navegador de verdad (ver el punto 3.6), para otros significa que impliquen todo el sistema, incluido el backend real. Esto es muy valioso ya que te cubren errores de integracion entre el frontend y el backend que pueden ocurrir por diferencias de opinion en el esquema de datos. También son un método eficientes para sacar errores de integración entre backends (por ejemplo, microservicio A envia datos erroneso al microservicio B) e incluso para detectar fallos de despliegue - actualmente no hay herramientas para test E2E solo backend tan amigables y maduras como las de UI como [Cypress](https://www.cypress.io/) y [Pupeteer](https://github.com/GoogleChrome/puppeteer). La desventaja de estos test es su coste alto, tener un entorno configurado con todos los componentes, la fragilidad de los test - si tenemos 50 microservicios, solo con que falle uno, los test E2E fallan. Por estas razones tenmos que usar moderadamente esta tecnica y probablemente tener entre 1 y 10 test de este tipo. Dicho esto, incluso una cantidad pequeña de test E2E es probablemente que detecten el tipo de problemas a los que estan realmente dirigidos: despligue e integración. Es aconsejable que se ejecuten en un entorno lo mas parecido a produción +:white_check_mark: **Haz:** Aunque E2E (end-to-end) para algunos significa solo hacer test de UI en navegador de verdad (ver el punto 3.6), para otros significa que impliquen todo el sistema, incluido el backend real. Esto es muy valioso ya que te cubren errores de integración entre el frontend y el backend que pueden ocurrir por diferencias de opinión en el esquema de datos. También son un método eficiente para sacar errores de integración entre backends (por ejemplo, microservicio A envía datos erróneos al microservicio B) e incluso para detectar fallos de despliegue - actualmente no hay herramientas para test E2E solo backend tan amigables y maduras como las de UI como [Cypress](https://www.cypress.io/) y [Pupeteer](https://github.com/GoogleChrome/puppeteer). La desventaja de estos test es su alto coste, tener un entorno configurado con todos los componentes, la fragilidad de los test - si tenemos 50 microservicios, solo con que falle uno, los test E2E fallan. Por estas razones tenemos que usar moderadamente esta técnica y probablemente tener entre 1 y 10 test de este tipo. Dicho esto, incluso una cantidad pequeña de test E2E es probablemente que detecten el tipo de problemas a los que están realmente dirigidos: despligue e integración. Es aconsejable que se ejecuten en un entorno lo más parecido a producción
    -❌ **De lo contrario:** Pudes invertir mucho en testear la funcionalidad de la UI para darte cuenta demasiado tarde que el backend devuelve un contrato (el esquema de datos con el que la UI trabaja) muy diferente al esperado +❌ **De lo contrario:** Puedes invertir mucho en testear la funcionalidad de la UI para darte cuenta demasiado tarde que el backend devuelve un contrato (el esquema de datos con el que la UI trabaja) muy diferente al esperado
    ## ⚪ ️ 3.8 Acelera los test E2E reutilizando las credenciales de login -:white_check_mark: **Haz:** En los test E2E que involucren un backend real que usa un token para identicarse en las llamadas a API, no vale la pena aislat el test tanto como para que se cree un usuario y se haga login en cada test. En vez de esto, haz login una vez antes de ejecutar todos los test (en el before-all) guarda el token en de forma local y reutilizalo en cada petición. Esto parece violar unos de los principios básicos - manten los test autonomos sin acoplamiento de recursos. Lo que es cierto, pero en los test E2E el rendimiento es clave, y crear 1-3 peticiones a API antes de empezar cada test individual puede llevarnos a unos tiempos de ejecución horribles. Reutilizar las credenciales no significa que los test tengan que actuar sobre los mismos registros de usuario - si se basan en ellos (por ejemplo, testeando el historial de pagos), asegurate de crear los registro como parte del test y evita compartirlos con otros test. Y siempre recuerda que el backend puede ser sustituido - si tus test estan focalizado en el frontend puede ser mejor aislarlos y descomnectar las API de backend (consulta el punto 3.6) +:white_check_mark: **Haz:** En los test E2E que involucren un backend real que usa un token para identificarse en las llamadas a API, no vale la pena aislar el test tanto como para que se cree un usuario y se haga login en cada test. En vez de esto, haz login una vez antes de ejecutar todos los test (en el before-all) guarda el token de forma local y reutilizalo en cada petición. Esto parece violar unos de los principios básicos - mantén los test autónomos sin acoplamiento de recursos. Y es cierto, pero en los test E2E el rendimiento es clave, y crear 1-3 peticiones a API antes de empezar cada test individual puede llevarnos a unos tiempos de ejecución horribles. Reutilizar las credenciales no significa que los test tengan que actuar sobre los mismos registros de usuario - si se basan en ellos (por ejemplo, testeando el historial de pagos), asegurate de crear los registro como parte del test y evita compartirlos con otros test. Y siempre recuerda que el backend puede ser sustituido - si tus test están focalizados en el frontend puede ser mejor aislarlos y desconectar las API de backend (consulta el punto 3.6)
    -❌ **De lo contrario:** Dados 200 test y asumiendo que un login son 100ms = 20 segusdos solo para hacer el mismo login una y otra vez +❌ **De lo contrario:** Dados 200 test y asumiendo que un login son 100ms = 20 segundos solo para hacer el mismo login una y otra vez
    @@ -1365,7 +1365,7 @@ beforeEach(setUser => () { ## ⚪ ️ 3.9 Haz un test E2E que navegue toda la página (smoke test) -:white_check_mark: **Haz:** Para el monitoreo de producción y verificar que nada se rompe en tiempo de desarrollo (sanity check), ejecuta un único test E2E que visite todas o la mayoría de las páginas y se asegure que ninguna se rompe. Este tipo de test proporciona un gran retorno de la inversión ya que es un bastante sencillo de crear y mantener y puede detecta cualquier tipo de fallo, incluido funcionales, red y despliegue. Otras formas de hacer smoke y sanity checks no son tan confiables y exhaustivas - algunso equipos de operaciones simplemente hacen ping a la página de inicio (en producción) o desarrolladores que tiene muchos test de integración que no levanta errores de construcción o de navegador. No hace falta decir que este test no sustituye los test funcionales, solo siver como detector de humo rápido +:white_check_mark: **Haz:** Para el monitoreo de producción y verificar que nada se rompe en tiempo de desarrollo (sanity check), ejecuta un único test E2E que visite todas o la mayoría de las páginas y se asegure que ninguna se rompe. Este tipo de test proporciona un gran retorno de la inversión ya que es un bastante sencillo de crear y mantener y puede detecta cualquier tipo de fallo, incluido funcionales, red y despliegue. Otras formas de hacer smoke y sanity checks no son tan confiables y exhaustivas - algunos equipos de operaciones simplemente hacen ping a la página de inicio (en producción) o desarrolladores que tiene muchos test de integración que no levanta errores de construcción o de navegador. No hace falta decir que este test no sustituye los test funcionales, solo sirven como detector de humo rápido
    @@ -1398,11 +1398,11 @@ it("When doing smoke testing over all page, should load them all successfully",
    -## ⚪ ️ 3.10 Exponer los tes como un documento colaborativo vivo +## ⚪ ️ 3.10 Exponer los test como un documento colaborativo vivo -:white_check_mark: **Haz:** Además de aumentar la confiabilidad de la aplicación, los test te dan otra caracteristica muy atractiva - sirven de documentación viva. Dado que los test hablan en un leguaje menos tecnico y sobre el producto y UX, usar las herramientas correctas puede servir como un artefacto de comunicación que alina en gran medida a desarrolladores y su cliente. Por ejemplo, algunos frameworks permiten expresar el flujo y las expectativas (el test plan) utilizando un lenguaje legible para que cualquier stakeholder, incluyendo los product managers, pueden leer, aprobar y colaborar en los test convirtiendose en el documento de requerimentos vivo. Esta tecnica tambien se la conoce como 'test de aceptación', ya que permite al cliente definir sus criterios de aceptación en un leguaje sencillo. Esto es [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) en su forma más pura. Uno de los frameworks más populares para aesto es [Cucumber que tiene su sabor en JavaScript](https://github.com/cucumber/cucumber-js), ver el ejemplo más abajo. Otra forma similar pero diferente, [StoryBook](https://storybook.js.org/), permite exponer los componentes UI como un catalogo grafico, donde cualquiera puede recorrer los diferentes estados de cada componente (por ejemplo renderizar una cuadricula sin filtro, con multiples filas o con ninguna, etc), ver como queda y como se activa ese estado - esto también puede atraer a la gente de producto pero sobre todo siver como documentación viva para los desarrolladores que consumen esos componentes +:white_check_mark: **Haz:** Además de aumentar la confiabilidad de la aplicación, los test te dan otra característica muy atractiva - sirven de documentación viva. Dado que los test hablan en un lenguaje menos técnico y sobre el producto y UX, usar las herramientas correctas puede servir como un artefacto de comunicación que alinea en gran medida a desarrolladores y su cliente. Por ejemplo, algunos frameworks permiten expresar el flujo y las expectativas (el test plan) utilizando un lenguaje legible para que cualquier stakeholder, incluyendo los product managers, pueden leer, aprobar y colaborar en los test convirtiéndose en el documento de requerimientos vivo. Esta técnica también se la conoce como 'test de aceptación', ya que permite al cliente definir sus criterios de aceptación en un lenguaje sencillo. Esto es [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) en su forma más pura. Uno de los frameworks más populares para esto es [Cucumber que tiene su sabor en JavaScript](https://github.com/cucumber/cucumber-js), ver el ejemplo más abajo. Otra forma similar pero diferente, [StoryBook](https://storybook.js.org/), permite exponer los componentes UI como un catalogo grafico, donde cualquiera puede recorrer los diferentes estados de cada componente (por ejemplo renderizar una cuadricula sin filtro, con múltiples filas o con ninguna, etc), ver como queda y como se activa ese estado - esto también puede atraer a la gente de producto pero sobre todo sirve como documentación viva para los desarrolladores que consumen esos componentes -❌ **De lo contrario:** Despues de invertir los mejores recursos en los test, es una pena no aprovechar ese tiempo y ganar un gran valor como es la documentación +❌ **De lo contrario:** Después de invertir los mejores recursos en los test, es una pena no aprovechar ese tiempo y ganar un gran valor como es la documentación
    @@ -1443,11 +1443,11 @@ Feature: Twitter new tweet ## ⚪ ️ 3.11 Detecta problemas visuales con herramientas automatizadas -:white_check_mark: **Haz:** Configure herramientas automatizadas para capturar screenshoots de UI cuando se presenten cambios y detecte problemas visuales como contenido superpuesto o roto. Esto garantiza que no solo se muestren los datos correctos si no que el usuario los vea correctamente. Está tecnica no es ampliamente usada, nuestra mentalidad nos lleva a los test funcionales, pero es lo visual lo que el usuario experimenta y con la cantidad de dispositivos es relativamente facil pasar por alto algunos bugs en la UI. Algunas herramientas gratuítas pueden proporcionar lo básico - generar y guardar screenshots para la inspeccion manual por una persona. Mientras este enfoque podria ser suficiente para aplicaciones pequeñas, no es valido como cualquier otro test manual que exige trabajo de una persona cada vez que algo cambia. Por otro lado, es bastante dificil detectar problemas de UI automaticamente debido a que no está claramente deficini - aquí es donde interviene el campo de la 'Regresion Visual' a resolver este rompecabezas de comparar la UI antigua con los últimos cambios y detectar diferencias. Alguas erramientas OSS/gratuitas pueden proporcionar parte de esta funcionalidad (por ejemplo [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) pero podrian conllevar un tiempo de configuración muy alto. Algunas herramientas comerciales (por ejemplo [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) dan un paso más reducir la instalacion y contener funciones avanzadas como interfaces de administración, alertas, captura intelegiente que elimina el 'ruido visual' (por ejemplo, banners, animaciones) e incluso llegan a adelantar el analisis de la causa raiz de los cambios del DOM / css que han causado el problema +:white_check_mark: **Haz:** Configure herramientas automatizadas para capturar screenshoots de UI cuando se presenten cambios y detecte problemas visuales como contenido superpuesto o roto. Esto garantiza que no solo se muestren los datos correctos si no que el usuario los vea correctamente. Está técnica no es ampliamente usada, nuestra mentalidad nos lleva a los test funcionales, pero es lo visual lo que el usuario experimenta y con la cantidad de dispositivos es relativamente fácil pasar por alto algunos bugs en la UI. Algunas herramientas gratuitas pueden proporcionar lo básico - generar y guardar screenshots para la inspección manual por una persona. Mientras este enfoque podría ser suficiente para aplicaciones pequeñas, no es valido como cualquier otro test manual que exige trabajo de una persona cada vez que algo cambia. Por otro lado, es bastante difícil detectar problemas de UI automáticamente debido a que no está claramente definido - aquí es donde interviene el campo de la 'Regresión Visual' a resolver este rompecabezas de comparar la UI antigua con los últimos cambios y detectar diferencias. Algunas herramientas OSS/gratuitas pueden proporcionar parte de esta funcionalidad (por ejemplo [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) pero podrían conllevar un tiempo de configuración muy alto. Algunas herramientas comerciales (por ejemplo [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) dan un paso más reducir la instalación y contener funciones avanzadas como interfaces de administración, alertas, captura inteligente que elimina el 'ruido visual' (por ejemplo, banners, animaciones) e incluso llegan a adelantar el análisis de la causa raíz de los cambios del DOM / css que han causado el problema
    -❌ **De lo contrario:** ¿Como de bien hecha una pagina que muestra buen contendo (100% test ok), carga de forma instantanea pero la mitad del area de contenido está oculto? +❌ **De lo contrario:** ¿Como de bien está hecha una pagina que muestra buen contenido (100% test ok), carga de forma instantánea pero la mitad del área de contenido está oculto?
    @@ -1455,7 +1455,7 @@ Feature: Twitter new tweet
    -### :thumbsdown: Ejemplo Anti Patrón: Una regresion visual estandar - contenido correcto que se muestra mal +### :thumbsdown: Ejemplo Anti Patrón: Una regresión visual estándar - contenido correcto que se muestra mal ![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") @@ -1480,7 +1480,7 @@ screen_widths: - 1024​ - 1280​ -​# escribe las URL de la página a constinuación, por ejemplo +​# escribe las URL de la página a continuación, por ejemplo paths: about: path: /about @@ -1517,15 +1517,15 @@ describe("visual validation", () => {

    -# Section 4️⃣: Midiendo la efectividad de los Test +# Sección 4️⃣: Midiendo la efectividad de los Test

    ## ⚪ ️ 4.1 Completa una cobertura suficiente que de confianza, ~80% parece el numero de la suerte -:white_check_mark: **Haz:** El proposito de los test es tener suficiente confianza para moverse rápido, obviamente cuando más código se pruebe, más confianza tendremos en el equipo. La cobertura nos mide cuantas lineas de código (y ramas, declaraciones, etc) se alcanzan mediante los test. Entonces, ¿cuanta es suficiente? Obviamente 10-30% es demasiado bajo para tener alguna idea de que puedes tener que corregir, y por otro lado el 100% es muy caro y puede cambiar el foco de los caminos criticos a rincones apenas usados del código. La respuesta larga es que depende de muchos dactores como el tipo de aplicación - si estas construyendo la siguiente generación del Airbus A380 un 100% es obligatorio, pero para una web de dibujos animados, el 50% podria hasta ser demasiado. Aunque la mayoria de los entusiastas de los test dicen que el porcentaje de cobertura correcto es contextual, la mayoria de ellos comentan que el 80% como la regla correcta ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) que posiblemente debería satisfacer la mayotia de aplicaciones +:white_check_mark: **Haz:** El propósito de los test es tener suficiente confianza para moverse rápido, obviamente cuando más código se pruebe, más confianza tendremos en el equipo. La cobertura nos mide cuantas lineas de código (y ramas, declaraciones, etc) se alcanzan mediante los test. Entonces, ¿cuanta cobertura es suficiente? Obviamente 10-30% es demasiado bajo para tener alguna idea de que puedes tener que corregir, y por otro lado el 100% es muy caro y puede cambiar el foco de los caminos críticos a rincones apenas usados del código. La respuesta larga es que depende de muchos factores como el tipo de aplicación - si estas construyendo la siguiente generación del Airbus A380 un 100% es obligatorio, pero para una web de dibujos animados, el 50% podría hasta ser demasiado. Aunque la mayoría de los entusiastas de los test dicen que el porcentaje de cobertura correcto es contextual, la mayoría de ellos comentan que el 80% como la regla correcta ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) que posiblemente debería satisfacer la mayoría de aplicaciones -Consejos de implementación: es posible que quieras configurar la integracion continua (CI) para que tenga un umbral de cobertura ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) y que pare el pipeline cuando no cumpla el estandar (también es posible configurar el porcentaje por compoente, vease el ejemplo a continuación). Ademas de esto, deberias considerar detectar la bajada de cobertura (cuando un nuevo commit tiene menos cobertura que antes) - esto empujara a los desarrolladores a aumentar o al menos preservar la cantidad de código con test. Y también puede ser engañado como se puestra en los siguientes puntos +Consejos de implementación: es posible que quieras configurar la integración continua (CI) para que tenga un umbral de cobertura ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) y que pare el pipeline cuando no cumpla el estándar (también es posible configurar el porcentaje por componete, véase el ejemplo a continuación). Ademas de esto, deberías considerar detectar la bajada de cobertura (cuando un nuevo commit tiene menos cobertura que antes) - esto empujara a los desarrolladores a aumentar o al menos preservar la cantidad de código con test. Aunque también puede ser trampeado como se muestra en los siguientes puntos
    @@ -1537,7 +1537,7 @@ Consejos de implementación: es posible que quieras configurar la integracion co
    -### :clap: Example: Un reporte de cobertura típico +### :clap: Ejemplo: Un reporte de cobertura típico ![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "Un reporte de cobertura típico") @@ -1555,7 +1555,7 @@ Consejos de implementación: es posible que quieras configurar la integracion co ## ⚪ ️ 4.2 Inspecciona los reportes de cobertura para detectar áreas no testadas y otras cosas raras -:white_check_mark: **Haz:** Algunos problemas se ocultan por debajo del radar y son realmente difíciles de encontrar utilizando herramientas tradicionales. Estos no son realmente bugs sino más bien un comportamientos curiosos de la aplicación que podrían tener un gran impacto. Por ejemplo, a menudo algunas áreas de código no se invocan nunca o rara vez - puedes pensar que la clase 'PricingCalculator' siempre determina el precio del producto, pero resulta que en realidad nunca se invoca, aunque tenemos 10000 productos en DB y muchas ventas… Los reportes de nos ayudan a darnos cuenta de si la aplicación se comporta de la manera que esperamos. Aparte de eso, también podemos resaltar qué tipos de código no se testean: que el 80% del código se testea, no nos indica si las partes críticas están cubiertas. Generar reportes es fácil: simplemente ejecute su aplicación en producción o durante test con cobertura y luego revisa los reportes que resaltan la frecuencia con la que se invoca cada parte del código. Si le dedicas un tiempo para echar un vistazo a estos datos, puedes encontrar algunas errores +:white_check_mark: **Haz:** Algunos problemas se ocultan por debajo del radar y son realmente difíciles de encontrar utilizando herramientas tradicionales. Estos no son realmente bugs sino más bien comportamientos curiosos de la aplicación que podrían tener un gran impacto. Por ejemplo, a menudo algunas áreas de código no se invocan nunca o rara vez - puedes pensar que la clase 'PricingCalculator' siempre determina el precio del producto, pero resulta que en realidad nunca se invoca, aunque tenemos 10000 productos en base de datos y muchas ventas... Los reportes nos ayudan a darnos cuenta de si la aplicación se comporta de la manera que esperamos. Aparte de eso, también podemos resaltar qué tipos de código no se testean: que el 80% del código se testea, no nos indica si las partes críticas están cubiertas. Generar reportes es fácil: simplemente ejecute su aplicación en producción o durante test con cobertura y luego revisa los reportes que resaltan la frecuencia con la que se invoca cada parte del código. Si le dedicas un tiempo para echar un vistazo a estos datos, puedes encontrar algunas errores
    @@ -1579,15 +1579,15 @@ Basado en un escenario real en el que rastreamos el uso de nuestra aplicación e ## ⚪ ️ 4.3 Mide la cobertura lógica usando mutation testing -:white_check_mark: **Haz:** La métrica de cobertura tradicional a menudo miente: puede mostrarle una cobertura de código del 100%, pero ninguna de sus funciones, ni siquiera una, devuelve la respuesta correcta. ¿Cómo? simplemente mide sobre qué líneas de código se paso en los test, pero no verifica si laos test realmente han comprobado algo - asercionando la respuesta correcta. Como alguien que viaja por negocios y muestra su pasaporte, esto no te asegura que haya realizado ningun trabajo, solo que ha visitado ciertos aeropuertos y hoteles +:white_check_mark: **Haz:** La métrica de cobertura tradicional a menudo miente: puede mostrarle una cobertura de código del 100%, pero ninguna de sus funciones, ni siquiera una, devuelve la respuesta correcta. ¿Cómo? simplemente mide sobre qué líneas de código se paso en los test, pero no verifica si los test realmente han comprobado algo - asercionando la respuesta correcta. Como alguien que viaja por negocios y muestra su pasaporte, esto no te asegura que haya realizado ningún trabajo, solo que ha visitado ciertos aeropuertos y hoteles -Los test basados en mutaciones nos ayudan midiendo la cantidad de código que en realidad se TESTEÓ, no solo VISITADO. [Stryker] (https://stryker-mutator.io/) es una libreria JavaScript para test de mutación y la implementación es realmente clara: +Los test basados en mutaciones nos ayudan midiendo la cantidad de código que en realidad se TESTEÓ, no solo VISITADO. [Stryker] (https://stryker-mutator.io/) es una librería JavaScript para mutation testing y la implementación es realmente clara: (1) cambia intencionalmente el código y "planta bugs". Por ejemplo, el código newOrder.price === 0 se convierte en newOrder.price! = 0. Estos "bugs" se llaman mutaciones -(2) ejecuta los test, si todo va bien, entonces tenemos un problema - los test no cumplen su propósito de descubrir bugs, las mutaciones se denominan supervivientes. Si los test fallaron, entonces genial, las mutaciones fueron asesinadas +(2) ejecuta los test, si todo va bien, entonces tenemos un problema - los test no cumplen su propósito de descubrir bugs, las mutaciones se denominan supervivientes. Si los test fallaron, entonces genial, las mutaciones fueron destruidas -Saber que todas o la mayoría de las mutaciones fueron asesinadas da mucha más confianza que la cobertura tradicional y el tiempo de configuración es muy similar +Saber que todas o la mayoría de las mutaciones fueron destruidas da mucha más confianza que la cobertura tradicional y el tiempo de configuración es muy similar
    ❌ **De lo contrario:** Te engañas si crees que una cobertura del 85% significa que tus test detectarán errores en el 85% de tu código @@ -1618,9 +1618,9 @@ it("Test addNewOrder, don't use such test names", () => {
    -### :clap: Ejemplo de cómo hacerlo correctamente: Los reportes de Stryker, una herramienta para test de mutación, detecta y cuenta la cantidad de código que no se testea (Mutaciones) +### :clap: Ejemplo de cómo hacerlo correctamente: Los reportes de Stryker, una herramienta para mutation testing, detecta y cuenta la cantidad de código que no se testea (Mutaciones) -![alt text](assets/bp-20-yoni-goldberg-mutation-testing.jpeg "Los reportes de Stryker, una herramienta para test de mutación, detecta y cuenta la cantidad de código que no se testea (Mutaciones)") +![alt text](assets/bp-20-yoni-goldberg-mutation-testing.jpeg "Los reportes de Stryker, una herramienta para mutation testing, detecta y cuenta la cantidad de código que no se testea (Mutaciones)")
    @@ -1628,11 +1628,11 @@ it("Test addNewOrder, don't use such test names", () => { ## ⚪ ️4.4 Prevención de problemas de código de test con linters para test -:white_check_mark: **Haz:** ESLint tiene un conjunto de plugins específicos para inspeccionar patrones de código de test y descubrir problemas. Por ejemplo, [eslint-plugin-mocha] (https://www.npmjs.com/package/eslint-plugin-mocha) avisará cuando un test se escriba a nivel global (no es hijo de un describe () ) o cuando se omiten los test (https://mochajs.org/#inclusive-tests), lo que puede llevar a creer erroneamenre de que todas los test están ok. Del mismo modo, [eslint-plugin-jest] (https://github.com/jest-community/eslint-plugin-jest) puede, por ejemplo, advertir cuando un test no tiene aserciones (sin verificar nada) +:white_check_mark: **Haz:** ESLint tiene un conjunto de plugins específicos para inspeccionar patrones de código de test y descubrir problemas. Por ejemplo, [eslint-plugin-mocha] (https://www.npmjs.com/package/eslint-plugin-mocha) avisará cuando un test se escriba a nivel global (no es hijo de un describe () ) o cuando se omiten los test (https://mochajs.org/#inclusive-tests), lo que puede llevar a creer erróneamente de que todos los test están ok. Del mismo modo, [eslint-plugin-jest] (https://github.com/jest-community/eslint-plugin-jest) puede, por ejemplo, advertir cuando un test no tiene aserciones (sin verificar nada)
    -❌ **De lo contrario:** Ver un 90% de cobertura de código y 100% de test verdes te provocara una sonrisa hasta que te das cuenta de que muchos test no asercionan nada y muchos test simplemente se omitieron. Con suerte, no desplegó nada basado en esta falsa observación +❌ **De lo contrario:** Ver un 90% de cobertura de código y 100% de test verdes te provocara una sonrisa hasta que te das cuenta de que muchos test no asercionan nada y muchos test simplemente se omitieron. Con suerte, no desplegaste nada basándote en esta falsa observación
    Código de Ejemplo @@ -1659,17 +1659,17 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# Section 5️⃣: CI y otras medidas de calidad +# Sección 5️⃣: CI y otras medidas de calidad

    -## ⚪ ️ 5.1 Enriquece tus linters y cancela las construciones que tienen problemas de linter +## ⚪ ️ 5.1 Enriquece tus linters y cancela las construcciones que tienen problemas de linter -:white_check_mark: **Haz:** Los linters son comida gratis, con una configuración de 5 minutos, obtienes gratis un piloto automático que vigila tu código y detecta problemas importantes mientras escribes. Atrás quedaron los días en los que el linter era solo maquillaje (¡no hay punto y coma!). Hoy en día, los linters pueden detectar problemas graves como errores que no se lanzan correctamente y perder información. Además de su conjunto básico de reglas (como [estándar ESLint] (https://www.npmjs.com/package/eslint-plugin-standard) o [estilo Airbnb] (https://www.npmjs.com/package / eslint-config-airbnb)), considera incluir algunos conjuntos de reglas especializadas como [eslint-plugin-chai-expect] (https://www.npmjs.com/package/eslint-plugin-chai-expect) que pueden descubrir test sin aserciones, [eslint-plugin-promise] (https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) puede descubrir promesas sin resolución (tu código nunca continuará), [eslint-plugin -security] (https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) que puede descubrir expresiones regulares que podrían usarse para ataques DOS, y [eslint-plugin-you-dont- need-lodash-underscore] (https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) es capaz de avisar cuando el código utiliza métodos de librerias de utilidades que forman parte de V8, métodos básicos como Lodash.\_map (...) +:white_check_mark: **Haz:** Los linters son comida gratis, con una configuración de 5 minutos, obtienes gratis un piloto automático que vigila tu código y detecta problemas importantes mientras escribes. Atrás quedaron los días en los que el linter era solo maquillaje (¡no hay punto y coma!). Hoy en día, los linters pueden detectar problemas graves como errores que no se lanzan correctamente y perder información. Además de su conjunto básico de reglas (como [standar] (https://www.npmjs.com/package/eslint-plugin-standard) o [Airbnb] (https://www.npmjs.com/package / eslint-config-airbnb)), considera incluir algunos conjuntos de reglas especializadas como [eslint-plugin-chai-expect] (https://www.npmjs.com/package/eslint-plugin-chai-expect) que pueden descubrir test sin aserciones, [eslint-plugin-promise] (https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) puede descubrir promesas sin resolución (tu código nunca continuará), [eslint-plugin-security] (https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) que puede descubrir expresiones regulares que podrían usarse para ataques DOS, y [eslint-plugin-you-dont-need-lodash-underscore] (https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) es capaz de avisar cuando el código utiliza métodos de librerías de utilidades que forman parte de V8, métodos básicos como Lodash.\_map (...)
    -❌ **De lo contrario:** Considera un día lluvioso donde producción sigue fallando pero los registros no muestran el call stack de errores. ¿Que pasa? Tu código emite por error un objeto sin error y se perdió el trazado, una buena razón para golpearse la cabeza contra una pared de ladrillos. Una configuración de linter de 5 minutos podría detectar este GAZAPO y salvarte el día +❌ **De lo contrario:** Considera un día lluvioso donde producción sigue fallando pero los registros no muestran el call stack de errores. ¿Que pasa? Tu código emite por error un objeto sin error y se perdió el trazado, una buena razón para golpearse la cabeza contra una pared. Una configuración de linter de 5 minutos podría detectar este GAZAPO y salvarte el día
    @@ -1677,9 +1677,9 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    -### :thumbsdown: Ejemplo Anti Patrón: El objeto de Error es emitido, no parece ninguna traza pora este error. Por suerte, ESLint detecta el siguiente error de producción +### :thumbsdown: Ejemplo Anti Patrón: El objeto de Error es emitido, no aparece ninguna traza para este error. Por suerte, ESLint detecta el siguiente error de producción -![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "El objeto de Error es emitido, no parece ninguna traza pora este error. Por suerte, ESLint detecta el siguiente error de producción") +![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "El objeto de Error es emitido, no aparece ninguna traza para este error. Por suerte, ESLint detecta el siguiente error de producción")
    @@ -1689,7 +1689,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test :white_check_mark: **Haz:** ¿Tienes un pipeline de CI con test, linter, verificación de vulnerabilidades, etc? Ayuda a los desarrolladores a ejecutarlo también localmente para solicitar comentarios instantáneos y acortar el [ciclo de feedback] (https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2 -circuitos de retroalimentacion/). ¿Por qué? un proceso de testing eficiente constituye muchos bucles iterativos: (1) test -> (2) feedback -> (3) refactor. Cuanto más rápido sea el feedback, más iteraciones de mejora puede realizar un desarrollador por módulo y perfeccionar los resultados. Por otro lado, cuando el feedback tarda en llegar, se podrían realizar menos iteraciones de mejora en un solo día, el equipo podría estar ya haciendo otra cosa / tarea / módulo y podría no estar listo para refinar ese módulo -En la practica, algunos proveedores de CI (Ejemplo: [CircleCI CLI local] (https://circleci.com/docs/2.0/local-cli/)) permiten ejecutar el pipeline localmente. Algunas herramientas comerciales como [wallaby proporcionan información valiosa y de test] (https://wallabyjs.com/) para el desarrollador sin coste. Alternativamente, puedes agregar scripts npm en el package.json para ejecutar todos los comandos de calidad (por ejemplo, test, linter, vulnerabilidades) - usa herramientas como [concurrently] (https://www.npmjs.com/package/concurrently) para paralelizarlas y que código de salida sea distinto de cero si falla alguna de las herramientas. Ahora el desarrollador solo debe invocar un comando - por ejemplo "npm run quality": para obtener feedback en el acto. Considera también cancelar un commit si el control de calidad falla usando un githook ([husky puede ayudar] (https://github.com/typicode/husky)) +En la practica, algunos proveedores de CI (Ejemplo: [CircleCI CLI local] (https://circleci.com/docs/2.0/local-cli/)) permiten ejecutar el pipeline localmente. Algunas herramientas comerciales como [wallaby proporcionan información valiosa y de test] (https://wallabyjs.com/) para el desarrollador sin coste. Alternativamente, puedes agregar scripts npm en el package.json para ejecutar todos los comandos de calidad (por ejemplo, test, linter, vulnerabilidades) - usa herramientas como [concurrently] (https://www.npmjs.com/package/concurrently) para paralelizarlas y que el código de salida sea distinto de cero si falla alguna de las herramientas. Ahora el desarrollador solo debe invocar un comando - por ejemplo "npm run quality": para obtener feedback en el acto. Considera también cancelar un commit si el control de calidad falla usando un githook ([husky puede ayudar] (https://github.com/typicode/husky))
    @@ -1728,9 +1728,9 @@ En la practica, algunos proveedores de CI (Ejemplo: [CircleCI CLI local] (https: ## ⚪ ️5.3 Realiza test e2e sobre un espejo de producción -:white_check_mark: **Haz:** Los test End to end (e2e) testing son el principal desafio de un pipeline CI - crear un entorno espejo de produccion efimero que se genere sobre la marcha con todos los servicios necesarios puede ser tedioso y muy costoso. Buscando la mejor opcion: [Docker-compose](https://docs.docker.com/compose/) permite crear un entorno dockerizado aislado con mismos contenedores usando un unico fichero de texto plano, pero con la tecnologia por debajo (redes, despliegues) ser diferentes a las de produccion. Puedes combinarlo con [‘AWS Local’](https://github.com/localstack/localstack) para trabajr en hacer stubs de servicios AWS de verdad. Si usas [serverless](https://serverless.com/) existen multiple frameworks como serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) que te permiten invocar código Faas en local +:white_check_mark: **Haz:** Los test End to end (e2e) testing son el principal desafío de un pipeline CI - crear un entorno que sea un espejo de producción, efímero, que se genere sobre la marcha con todos los servicios necesarios puede ser tedioso y muy costoso. Buscando la mejor opción: [Docker-compose](https://docs.docker.com/compose/) permite crear un entorno dockerizado aislado con mismos contenedores usando un único fichero de texto plano, pero con la tecnología por debajo (redes, despliegues) ser diferentes a las de producción. Puedes combinarlo con [‘AWS Local’](https://github.com/localstack/localstack) para trabajar en hacer stubs de servicios AWS de verdad. Si usas [serverless](https://serverless.com/) existen multiple frameworks como serverless y [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) que te permiten invocar código Faas en local -El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para la duplicación en local y CI, aunque salen herramientas nuevas cada día. Un enfoque puede ser ejecutar implementaciones de 'kubernetes reducidos' con [Minikube](https://kubernetes.io/docs/setup/minikube/) o [MicroK8s](https://microk8s.io/) que es igual que el kubernetes completo pero con menos complejidad. Otro enfoque es tener un kubernetes de verdad remoto para test, algunso proveedores de CI (por ejemplo [Codefresh](https://codefresh.io/)) tiene integración nativa con entronos kubernetes y pueden facilmente ejecutar el pipeline CI sobre algo más real, otro permiten ejecutar scripts customizado contra kubernetes remotos +El enorme ecosistema de Kubernetes aún no tiene una herramienta estándar para la duplicación en local y CI, aunque salen herramientas nuevas cada día. Un enfoque puede ser ejecutar implementaciones de 'kubernetes reducidos' con [Minikube](https://kubernetes.io/docs/setup/minikube/) o [MicroK8s](https://microk8s.io/) que es igual que el kubernetes completo pero con menos complejidad. Otro enfoque es tener un kubernetes de verdad remoto para test, algunos proveedores de CI (por ejemplo [Codefresh](https://codefresh.io/)) tienen integración nativa con entornos kubernetes y pueden fácilmente ejecutar el pipeline CI sobre algo más real, otros permiten ejecutar scripts customizados contra kubernetes remotos
    @@ -1750,11 +1750,11 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l

    -## ⚪ ️5.4 Parallelizar la ejecución de los test +## ⚪ ️5.4 Paralelizar la ejecución de los test -:white_check_mark: **Haz:** Cuando se hace correctamente, los test son tu amigo 24/7 propocionando feedback instantaneo. En la practica, ejecutar 500 test unitarios en un solo proceso en CPU puede llevar demasiado tiempo. Afortunadamente, los test runner más modernos y las plataformas de CI (como [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) y [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) pueden paralelizar los test en multiples procesos y lograr una mejora importante en el tiempo en entregar feedback. Algunos proveedores de CI también paralelizan los test en contenedores (!) lo que acorta aún más la entrega de feedback. Ya sea localmente con multiples procesos, o sobre algun CLI en cloud usando multiples maquinas - pararelizar la demanda manteniendo los test autonomos para que cada uno pueda ejecutarse en diferentes procesos +:white_check_mark: **Haz:** Cuando se hace correctamente, los test son tu amigo 24/7 proporcionando feedback instantáneo. En la practica, ejecutar 500 test unitarios en un solo proceso en CPU puede llevar demasiado tiempo. Afortunadamente, los test runner más modernos y las plataformas de CI (como [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) y [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) pueden paralelizar los test en múltiples procesos y lograr una mejora importante en el tiempo en entregar feedback. Algunos proveedores de CI también paralelizan los test en contenedores (!) lo que acorta aún más la entrega de feedback. Ya sea localmente con múltiples procesos, o sobre algún CLI en cloud usando múltiples maquinas - parareliza manteniendo los test autónomos para que cada uno pueda ejecutarse en diferentes procesos -❌ **De lo contrario:** Obtener el resultado de los test en 1 hora despues de hacer push de código nuevo, mientras desarrollas nuevas funcionalidades, es la mejor receta para quitarle relevancia a los test +❌ **De lo contrario:** Obtener el resultado de los test en 1 hora después de hacer push de código nuevo, mientras desarrollas nuevas funcionalidades, es la mejor receta para quitarle relevancia a los test
    @@ -1762,9 +1762,9 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l
    -### :clap: Ejemplo de cómo hacerlo correctamente: Mocha parallel y Jest superan facilmente al Mocha tradicional gracias a la paralelización de la ejecución de los test ([Credito: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :clap: Ejemplo de cómo hacerlo correctamente: Mocha parallel y Jest superan fácilmente al Mocha tradicional gracias a la paralelización de la ejecución de los test ([Credito: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) -![alt text](assets/bp-24-yonigoldberg-jest-parallel.png "Mocha parallel y Jest superan facilmente al Mocha tradicional gracias a la paralelización de la ejecución de los test (Credito: JavaScript Test-Runners Benchmark)") +![alt text](assets/bp-24-yonigoldberg-jest-parallel.png "Mocha parallel y Jest superan fácilmente al Mocha tradicional gracias a la paralelización de la ejecución de los test (Credito: JavaScript Test-Runners Benchmark)")
    @@ -1772,9 +1772,9 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l ## ⚪ ️5.5 Mantente al margen de problemas legales usando la verificación de licencia y plagio -:white_check_mark: **Haz:** Los problemas de licencias y plagio seguramente no son tu prioridad ahora mismo, pero ¿por qué no hacer check en esta tarea en solo 10 minutos? Existen muchos paquetes npm como [license check](https://www.npmjs.com/package/license-checker) y [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial pero con plan gratuito) que puedes integrar facilmente en tu pipeline CI e inspeccionar en busca de problemas como dependencias con licencias restrictivas, o código que fue copiado y pegado de stackoverflow que aparentemente viola algunos copyrights +:white_check_mark: **Haz:** Los problemas de licencias y plagio seguramente no son tu prioridad ahora mismo, pero ¿por qué no hacer check en esta tarea en solo 10 minutos? Existen muchos paquetes npm como [license check](https://www.npmjs.com/package/license-checker) y [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial pero con plan gratuito) que puedes integrar fácilmente en tu pipeline CI e inspeccionar en busca de problemas como dependencias con licencias restrictivas, o código que fue copiado y pegado de stackoverflow que aparentemente viola algunos copyrights -❌ **De lo contrario:** Sin querer, los desarrolladores pueden usar paquete con licencias inapropiadas o copiar y pegar código comercial y encontrarse con problemas legales +❌ **De lo contrario:** Sin querer, los desarrolladores pueden usar paquetes con licencias inapropiadas o copiar y pegar código comercial y encontrarse con problemas legales
    @@ -1788,7 +1788,7 @@ El enorme ecosistema de Kubernetes aún no tiene una herramienta estandar para l //instala license-checker en tu entorno de CI o localmente npm install -g license-checker -//pidele que escanee todas las licencias y que termine con un código distindo de 0 si encuentra una licencia no autorizada. De esta forma el pipeline de CI puede detectarlo y detenerse +//pídele que escanee todas las licencias y que termine con un código distinto de 0 si encuentra una licencia no autorizada. De esta forma el pipeline de CI puede detectarlo y detenerse license-checker --summary --failOn BSD ``` @@ -1803,9 +1803,9 @@ license-checker --summary --failOn BSD ## ⚪ ️5.6 Verifica constantemente las dependencias vulnerables -:white_check_mark: **Haz:** Incluso las dependencias más conocidas como express, tiene vulnerabilidades conocidas. Puedes dominarlas facilmente usando herramientas libres como [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), o commerciales como [snyk](https://snyk.io/) (que tiene una version community gratuita). Ambas pueden ser ejecutadas desde tu CI en cada construcción +:white_check_mark: **Haz:** Incluso las dependencias más conocidas como express, tiene vulnerabilidades conocidas. Puedes dominarlas fácilmente usando herramientas libres como [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), o commerciales como [snyk](https://snyk.io/) (que tiene una versión community gratuita). Ambas pueden ser ejecutadas desde tu CI en cada construcción -❌ **De lo contrario:** Mantener tu código limpio de vulnerabilidades sin herramientas dedicadas requiere que estes revisando constantemente las nuevas versiones. Muy tedioso +❌ **De lo contrario:** Mantener tu código limpio de vulnerabilidades sin herramientas dedicadas requiere que estés revisando constantemente las nuevas versiones. Muy tedioso
    @@ -1823,17 +1823,17 @@ license-checker --summary --failOn BSD ## ⚪ ️5.7 Automatiza la actualización de dependencias -:white_check_mark: **Haz:** La introducción en yarn y del package-lock.json de npm introduce un desafio muy importante ( el camino al infierno esta lleno de buenas intenciones) — por defecto ahora, lo paquetes no se auto actualiza más. Incluso en un equipo que ejecute un 'npm install' limpio y 'npm update' no van a caer nuevas actualizaciones. Esto conduce a veriones de paquetes desactualizadas en el mejor de los casos, y en el peor, a código vulnerable. Los equipos pasan a depender de la buena voluntad y memoria de los desarrolladores para actualizar el package.json a mano o a utilizar herramientas como [ncu](https://www.npmjs.com/package/npm-check-updates) manualmente. Una formula mucho mejor seria automatizar el proceso de actualizar las versiones de las dependenecias en las que más confiamos, pero no hay una solucion perfecta, existen dos caminos para esta actualización: +:white_check_mark: **Haz:** La introducción en yarn y del package-lock.json de npm introduce un desafío muy importante (el camino al infierno esta lleno de buenas intenciones) — por defecto ahora los paquetes no se auto actualizan más. Incluso en un equipo que ejecute un 'npm install' limpio y 'npm update' no van a caer nuevas actualizaciones. Esto conduce a versiones de paquetes desactualizadas en el mejor de los casos, y en el peor, a código vulnerable. Los equipos pasan a depender de la buena voluntad y memoria de los desarrolladores para actualizar el package.json a mano o a utilizar herramientas como [ncu](https://www.npmjs.com/package/npm-check-updates) manualmente. Una formula mucho mejor seria automatizar el proceso de actualizar las versiones de las dependencias en las que más confiamos, pero no hay una solución perfecta, existen dos caminos para esta actualización: (1) Podemos hacer que el CI falle con dependencias obsoletas — usando herramientas como [‘npm outdated’](https://docs.npmjs.com/cli/outdated) o ‘npm-check-updates (ncu)’. Hacerlo obligará a los desarrolladores a actualizar las dependencias -(2) Usar alguna de las herramientas comerciales que escanean nuestro código y envian automaticamente pull-request con actualización de dependencias. Una pregunta interesante que nos queda es cual va a ser la politica de estas actualizaciones: si actualizamos cada parche se genera mucha sobrecarga, y hacerlo cuando haya una version major nos lleva directos a usar verisones inestables o incompatibles (mucho paquetes muestran vulnerabilidades justo despues de salir una version nueva [lee sobre](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) el incidente de eslint-scope) +(2) Usar alguna de las herramientas comerciales que escanean nuestro código y envían automáticamente pull-request con actualización de dependencias. Una pregunta interesante que nos queda es cual va a ser la política de estas actualizaciones: si actualizamos cada parche se genera mucha sobrecarga, y hacerlo cuando haya una versión major nos lleva directos a usar versiones inestables o incompatibles (mucho paquetes muestran vulnerabilidades justo después de salir una versión nueva [lee sobre](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) el incidente de eslint-scope) -Una politica de actualizaciones eficiente puede permitir cierto 'periodo de concesion' - deja pasar versiones quedandote por detras de @latest un tiempo antes de considerar que tu versión en local está obsoleta (por ejemplo, la versión que tienes en local es 1.3.1 y la versión del repositorio del paquete es 1.3.8) +Una política de actualizaciones eficiente puede permitir cierto 'periodo de concesión' - deja pasar versiones quedándote por detrás de @latest un tiempo antes de considerar que tu versión en local está obsoleta (por ejemplo, la versión que tienes en local es 1.3.1 y la versión del repositorio del paquete es 1.3.8)
    -❌ **De lo contrario:** Producción estara ejecutando versiones de paquetes que han sido marcadas por los propios autores como version con riesgos +❌ **De lo contrario:** Producción estará ejecutando versiones de paquetes que han sido marcadas por los propios autores como versiones con riesgos
    @@ -1851,18 +1851,18 @@ Una politica de actualizaciones eficiente puede permitir cierto 'periodo de conc ## ⚪ ️ 5.8 Otros consejos de CI no relacionados con Node -:white_check_mark: **Haz:** Esta publicación se centra en los consejos sobre testing que están relacionados, o al menos pueden ejemplificarse con el Node JS. Sin embargo, este punto agrupa algunos consejos no relacionados con Node que son bien conocidos +:white_check_mark: **Haz:** Esta publicación se centra en los consejos sobre testing que están relacionados, o al menos pueden ejemplificarse con Node JS. Sin embargo, este punto agrupa algunos consejos no relacionados con Node que son bien conocidos -
    1. Use una sintaxis declarativa. Esta es la única opción para la mayoría de los pipelines de CI, pero las versiones antiguas de Jenkins permiten usar código o incluos una interfaz de usuario
    2. Opte por un CI que tenga soporte nativo de Docker
    3. Falla antes, ejecuta tus test más rápidas primero. Crea un paso / hito de 'Smoke testing' que agrupe múltiples verificaciones rápidas (por ejemplo, linter, test unitarios) y que proporcione comentarios rápidamente al desarrollador que haya commiteado
    4. Facilite la exploración de todos las partes de construcción, incluidos reportes de test, informes de cobertura, informes de mutación, registros, etc.
    5. Crea múltiples pipelines / jobs para cada evento y reutilice los pasos entre ellos. Por ejemplo, configure un job para confirmaciones de commits a ramas de desarrollo y uno diferente para la rama master. Permita la reutilización de la logica usando pasos compartidos en los pipelines (la mayoría de los proveedores proporcionan algún mecanismo para la reutilización de código)
    6. Nunca uses secretos directamente en la declaración del job, traelos de un store de secretos o de la configuración del propio job
    7. Suba explícitamente la versión de una release o al menos asegúrate de que el desarrollador lo hizo
    8. Construye solo una vez y realiza todas las inspecciones sobre el artefacto construido único (por ejemplo, imagen Docker)
    9. Testea en un entorno efímero que no arrastre el estado entre las construcciones. El almacenamiento en caché de node_modules podría ser la única excepción
    +
    1. Usa una sintaxis declarativa. Esta es la única opción para la mayoría de los pipelines de CI, pero las versiones antiguas de Jenkins permiten usar código o incluso una interfaz de usuario
    2. Opta por un CI que tenga soporte nativo de Docker
    3. Falla antes, ejecuta tus test más rápidos primero. Crea un paso / hito de 'Smoke testing' que agrupe múltiples verificaciones rápidas (por ejemplo, linter, test unitarios) y que proporcione comentarios rápidamente al desarrollador que haya commiteado
    4. Facilita la exploración de todas las partes de construcción, incluidos reportes de test, informes de cobertura, informes de mutación, registros, etc.
    5. Crea múltiples pipelines / jobs para cada evento y reutiliza los pasos entre ellos. Por ejemplo, configura un job para confirmaciones de commits a ramas de desarrollo y uno diferente para la rama master. Permite la reutilización de la lógica usando pasos compartidos en los pipelines (la mayoría de los proveedores proporcionan algún mecanismo para la reutilización de código)
    6. Nunca uses secretos directamente en la declaración del job, traelos de un store de secretos o de la configuración del propio job
    7. Suba explícitamente la versión de una release o al menos asegúrate de que el desarrollador lo hizo
    8. Construye solo una vez y realiza todas las inspecciones sobre el artefacto construido único (por ejemplo, imagen Docker)
    9. Testea en un entorno efímero que no arrastra el estado entre las construcciones. El almacenamiento en caché de node_modules podría ser la única excepción

    ❌ **De lo contrario:** Echarás de menos años de sabiduría

    -## ⚪ ️ 5.9 Build matrix: Ejecuta los mismos pasos de CI usando multiples versiones de Node +## ⚪ ️ 5.9 Build matrix: Ejecuta los mismos pasos de CI usando múltiples versiones de Node -:white_check_mark: **Haz:** En control de calidad influye totalmente la casualidad, cuanto más terreno cubras, más suerte tendrás al detectar problemas temprano. Al desarrollar paquetes reutilizables o ejecutar un producto multicliente con varias configuraciones y versiones de Node, el CI debe ejecutar el pipeline de test sobre todas las combinaciones de configuraciones. Por ejemplo, suponiendo que usemos MySQL para algunos clientes y Postgres para otros, algunos proveedores de CI admiten una característica llamada 'Matrix' que permite ejecutar todos los test contra todas las combinaciones de MySQL, Postgres y versiones de Node múltiples como 8, 9 y 10. Esto se hace utilizando la configuración solo, sin ningún esfuerzo adicional (suponiendo que tenga test o cualquier otro control de calidad). Otros CI que no admiten Matrix pueden tener extensiones o ajustes para permitirlo +:white_check_mark: **Haz:** En control de calidad influye totalmente la casualidad, cuanto más terreno cubras, más suerte tendrás al detectar problemas temprano. Al desarrollar paquetes reutilizables o ejecutar un producto multicliente con varias configuraciones y versiones de Node, el CI debe ejecutar el pipeline de test sobre todas las combinaciones de configuraciones. Por ejemplo, suponiendo que usemos MySQL para algunos clientes y Postgres para otros, algunos proveedores de CI admiten una característica llamada 'Matrix' que permite ejecutar todos los test contra todas las combinaciones de MySQL, Postgres y versiones de Node múltiples como 8, 9 y 10. Esto se hace utilizando solo configuración, sin ningún esfuerzo adicional (suponiendo que tengas test o cualquier otro control de calidad). Otros CI que no admiten Matrix pueden tener extensiones o ajustes para permitirlo
    ❌ **De lo contrario:** Entonces, después de hacer todo ese arduo trabajo de escribir los test, ¿vamos a dejar que los errores se cuelen solo por problemas de configuración? @@ -1873,7 +1873,7 @@ Una politica de actualizaciones eficiente puede permitir cierto 'periodo de conc
    -### :clap: Ejemplo: Usando la definicion de construcción de Travis (proveedor de CI) para correr los mismos test en diferentes versiones de Node +### :clap: Ejemplo: Usando la definición de construcción de Travis (proveedor de CI) para correr los mismos test en diferentes versiones de Node
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    @@ -1890,7 +1890,7 @@ Una politica de actualizaciones eficiente puede permitir cierto 'periodo de conc **Papel:** Escritor -**Acerda de:** Soy un consultor independiente que trabaja con empresas de 500 fortune y startups en garajes para pulir sus aplicaciones JS & Node.js. Más que ningún otro tema me fascina y tengo como objetivo dominar el arte del testing. También soy el autor de [Node.js Buenas Practicas](https://github.com/goldbergyoni/nodebestpractices) +**Acerda de:** Soy un consultor independiente que trabaja con empresas de fortune 500 y startups en garajes para pulir sus aplicaciones JS & Node.js. Más que ningún otro tema me fascina y tengo como objetivo dominar el arte del testing. También soy el autor de [Node.js Buenas Practicas](https://github.com/goldbergyoni/nodebestpractices) **📗 Curso Online:** ¿Te gustó esta guía y deseas llevar tus habilidades de testing al máximo? Considera visitar mi curso completo [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) From a1a85cc77f9ec75df5cd9c6044ab02d361433892 Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Wed, 23 Sep 2020 00:38:47 +0200 Subject: [PATCH 273/502] merge contributors to spanish translation --- readme-es.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/readme-es.md b/readme-es.md index 353f2278..55653202 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1970,6 +1970,12 @@ Se encargó de revisar, mejorar, lintear y pulir todos los textos
    roywalker

    🖋
    dangen

    🖋
    biesiadamich

    🖋 +
    Yanlin Jiang

    🖋 +
    sanguino

    🖋 + + +
    Morgan

    🖋 +
    Lukas Bischof

    ⚠️ 🖋 From 99303c511541682a160601b270d6e734a6a39fff Mon Sep 17 00:00:00 2001 From: "Miguel G. Sanguino" Date: Wed, 23 Sep 2020 00:41:37 +0200 Subject: [PATCH 274/502] add spanish translation link to main readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 32e7886f..56b4765e 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇨🇳[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) - Want to translate to your own language? please open an issue 💜

    From 267f12c55c58759c5c7ef738041cdff34459beb1 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2020 15:52:11 +0000 Subject: [PATCH 275/502] docs: update readme.md [skip ci] From debfc3c25957f78b7f777f27a063fe617ab0cce3 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2020 15:52:12 +0000 Subject: [PATCH 276/502] docs: update .all-contributorsrc [skip ci] From 2cd56b045ab42984137763454231136d510358d4 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2020 15:57:44 +0000 Subject: [PATCH 277/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 56b4765e..1a1ba9d6 100644 --- a/readme.md +++ b/readme.md @@ -1958,6 +1958,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    Morgan

    🖋
    Lukas Bischof

    ⚠️ 🖋 +
    JuanMa Ruiz

    🖋 From 5763bd004b790699bfc24d52290da134f264ab88 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2020 15:57:45 +0000 Subject: [PATCH 278/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 48851b54..32808dae 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -277,6 +277,15 @@ "test", "content" ] + }, + { + "login": "JuanMaRuiz", + "name": "JuanMa Ruiz", + "avatar_url": "https://avatars2.githubusercontent.com/u/1837650?v=4", + "profile": "https://juanmaruiz.surge.sh", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 1b5485a9688105c39f06f5fed4a26ae0cf8d0c94 Mon Sep 17 00:00:00 2001 From: Alejandro Gutierrez Barcenilla <56408597+AlejandroGutierrezB@users.noreply.github.com> Date: Fri, 25 Sep 2020 19:22:49 +0200 Subject: [PATCH 279/502] Update readme-es.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just changed a small typo. Awesome guide and translation! 👏 --- readme-es.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-es.md b/readme-es.md index 55653202..71d84186 100644 --- a/readme-es.md +++ b/readme-es.md @@ -161,7 +161,7 @@ describe('Products Service', function() {
    -❌ **De lo contrario:** No solo emplearas horas comprendiendo el código principal, si no que lo que debería haber sido la parate más simple del día (testing) te ha estrujado el cerebro +❌ **De lo contrario:** No solo emplearas horas comprendiendo el código principal, si no que lo que debería haber sido la parte más simple del día (testing) te ha estrujado el cerebro
    From 8d50f6210cf631e92013dfdfd145fd29487dbfe5 Mon Sep 17 00:00:00 2001 From: jfernandezpe Date: Fri, 25 Sep 2020 22:38:08 +0200 Subject: [PATCH 280/502] fix(readme-es.md): anchors --- readme-es.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme-es.md b/readme-es.md index 55653202..a70b4130 100644 --- a/readme-es.md +++ b/readme-es.md @@ -40,27 +40,27 @@ Empieza por comprender las técnicas de testing ubicuas que son la base de cualq ## `Tabla de Contenidos` -#### [`Sección 0: La Regla de Oro`](#section-0️⃣-the-golden-rule) +#### [`Sección 0: La Regla de Oro`](#sección-0️⃣-la-regla-de-oro) Un solo consejo que inspira a todos los demás (1 apartado especial) -#### [`Sección 1: La Anatomía de un Test`](#section-1-the-test-anatomy-1) +#### [`Sección 1: La Anatomía de un Test`](#sección-1-la-anatomía-de-un-test-1) La base - estructurando test claros (12 apartados) -#### [`Sección 2: Backend`](#section-2️⃣-backend-testing) +#### [`Sección 2: Backend`](#sección-2️⃣-backend-testing) Escribiendo test de backend y microservicios eficientemente (8 apartados) -#### [`Sección 3: Frontend`](#section-3️⃣-frontend-testing) +#### [`Sección 3: Frontend`](#sección-3️⃣-frontend-testing) Escribiendo test para web UI incluyendo test de componente y E2E (11 apartados) -#### [`Sección 4: Midiendo la Efectividad de los Test`](#section-4️⃣-measuring-test-effectiveness) +#### [`Sección 4: Midiendo la Efectividad de los Test`](#sección-4️⃣-midiendo-la-efectividad-de-los-test) Vigilando al vigilante - midiendo la calidad de los test (4 apartados) -#### [`Sección 5: Integración Continua`](#section-5️⃣-ci-and-other-quality-measures) +#### [`Sección 5: Integración Continua`](#sección-5️⃣-ci-y-otras-medidas-de-calidad) Pautas para Integración Continua en el mundo de JS (9 apartados) From c04dd9dfb0794635052a888fb07f9263c5db863e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20=C3=82ngelo=20Rodrigues=20Jr?= Date: Thu, 8 Oct 2020 11:47:06 -0300 Subject: [PATCH 281/502] Add link for pt-BR translation on README.md Hi! I was going to start translating for pt-br, but I was notified in issue #149 this translation already exists. I am simply adding the link to README.md and listing the contributors : ) --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 1a1ba9d6..f0d8f2eb 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇰🇷[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) +- pt-BR[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) - Want to translate to your own language? please open an issue 💜

    From 7fb69079eb877b3077cd7a7c483076e39db79482 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2020 14:18:38 +0000 Subject: [PATCH 282/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index f0d8f2eb..c3b2f4c1 100644 --- a/readme.md +++ b/readme.md @@ -1960,6 +1960,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    Morgan

    🖋
    Lukas Bischof

    ⚠️ 🖋
    JuanMa Ruiz

    🖋 +
    Luís Ângelo Rodrigues Jr.

    🖋 From 513e311a9e311d906052760c0e1373a62cb7590c Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2020 14:18:39 +0000 Subject: [PATCH 283/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 32808dae..d3078982 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -286,6 +286,15 @@ "contributions": [ "content" ] + }, + { + "login": "luisangelorjr", + "name": "Luís Ângelo Rodrigues Jr.", + "avatar_url": "https://avatars3.githubusercontent.com/u/22268900?v=4", + "profile": "https://luisangelorjr.com.br", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From d67aedaec91f75ca2d31a49ff7c5fdef7b888f78 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Fri, 16 Oct 2020 17:34:20 +0300 Subject: [PATCH 284/502] Update readme.md --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index c3b2f4c1..497619e4 100644 --- a/readme.md +++ b/readme.md @@ -30,11 +30,11 @@ Start by understanding the ubiquitous testing practices that are the foundation ### Translations - read in your own language -- 🇨🇳[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) -- pt-BR[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) +- 🇨🇳[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) - Want to translate to your own language? please open an issue 💜

    From adec8358e68190f1a71ad8cddf24bf861ec6e5fb Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Fri, 16 Oct 2020 17:35:33 +0300 Subject: [PATCH 285/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 497619e4..5b54da42 100644 --- a/readme.md +++ b/readme.md @@ -34,7 +34,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇰🇷[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) +- 🇧🇷[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) - Want to translate to your own language? please open an issue 💜

    From deea13c2f091e4087da522880c3a42f2c34fb523 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Fri, 16 Oct 2020 17:36:08 +0300 Subject: [PATCH 286/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5b54da42..f38e2eca 100644 --- a/readme.md +++ b/readme.md @@ -34,7 +34,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇰🇷[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) +- 🇧🇷[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) - Want to translate to your own language? please open an issue 💜

    From 2df4b5da3ccae5aedc4f5f6d86bf40d09a2466e5 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2020 15:23:05 +0000 Subject: [PATCH 287/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index f38e2eca..3f79ef03 100644 --- a/readme.md +++ b/readme.md @@ -1961,6 +1961,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    Lukas Bischof

    ⚠️ 🖋
    JuanMa Ruiz

    🖋
    Luís Ângelo Rodrigues Jr.

    🖋 +
    José Fernández

    🖋 From 2718076f984e86cfb19efd905cfdc3de47728652 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2020 15:23:06 +0000 Subject: [PATCH 288/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index d3078982..1e666029 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -295,6 +295,15 @@ "contributions": [ "content" ] + }, + { + "login": "jfernandezpe", + "name": "José Fernández", + "avatar_url": "https://avatars0.githubusercontent.com/u/12046620?v=4", + "profile": "https://jfernandezpe.wordpress.com/", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 05eb919a4d43410685a06227ed7d7b11cac8ed2e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2020 19:09:05 +0000 Subject: [PATCH 289/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 3f79ef03..62b7048d 100644 --- a/readme.md +++ b/readme.md @@ -1962,6 +1962,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    JuanMa Ruiz

    🖋
    Luís Ângelo Rodrigues Jr.

    🖋
    José Fernández

    🖋 +
    Alejandro Gutierrez Barcenilla

    🖋 From d74ef9815bb281360d554755b63ed95329796057 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2020 19:09:06 +0000 Subject: [PATCH 290/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 1e666029..d00363f5 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -304,6 +304,15 @@ "contributions": [ "content" ] + }, + { + "login": "AlejandroGutierrezB", + "name": "Alejandro Gutierrez Barcenilla", + "avatar_url": "https://avatars3.githubusercontent.com/u/56408597?v=4", + "profile": "http://www.linkedin.com/in/AlejandroGutierrezB", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 761fe7abc63a2d2877904f1cba25d3c862a89c5d Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Fri, 13 Nov 2020 12:01:41 +0200 Subject: [PATCH 291/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 62b7048d..e667483c 100644 --- a/readme.md +++ b/readme.md @@ -794,7 +794,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,

    -## ⚪ ️2.3 Ensure new releases don’t break the API using +## ⚪ ️2.3 Ensure new releases don’t break the API using contract tests :white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration
    From dd53683b7216284240eb293bc63a12ad37b14cad Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 21 Dec 2020 09:20:02 -0800 Subject: [PATCH 292/502] Fix Sentence Wording --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index e667483c..73402f3e 100644 --- a/readme.md +++ b/readme.md @@ -315,7 +315,7 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response", Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. -For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that got nothing with the application functionality and are likely to change frequently +For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that have nothing to do with the application functionality and are likely to change frequently
    ❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend From d12d889c14ba65254a2eb07f0491e76139d20a25 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 1 Jan 2021 09:24:44 +0000 Subject: [PATCH 293/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 73402f3e..77166a54 100644 --- a/readme.md +++ b/readme.md @@ -1963,6 +1963,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    Luís Ângelo Rodrigues Jr.

    🖋
    José Fernández

    🖋
    Alejandro Gutierrez Barcenilla

    🖋 +
    Jason

    🖋 From 6e75f92fa4411fd0824213684623d3190c8f57bb Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 1 Jan 2021 09:24:45 +0000 Subject: [PATCH 294/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index d00363f5..8ee1279f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -313,6 +313,15 @@ "contributions": [ "content" ] + }, + { + "login": "jasonandmonte", + "name": "Jason", + "avatar_url": "https://avatars1.githubusercontent.com/u/30088000?v=4", + "profile": "https://github.com/jasonandmonte", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From e1911c083d82f1f4c7917505bd136e4e8cd83520 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sat, 27 Mar 2021 16:24:31 +0300 Subject: [PATCH 295/502] Add files via upload --- assets/uml.png | Bin 0 -> 34766 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/uml.png diff --git a/assets/uml.png b/assets/uml.png new file mode 100644 index 0000000000000000000000000000000000000000..088a9bd4f86cae0e4cc475fd5f5c4ed345e3d4e8 GIT binary patch literal 34766 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 ep-?DO%Kryn&Jp_NZk5*n0000 Date: Sat, 27 Mar 2021 16:24:46 +0300 Subject: [PATCH 296/502] Update readme-pl.md --- readme-pl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-pl.md b/readme-pl.md index 3d64312e..c5ea0165 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -6,7 +6,7 @@
    -## 📗 45+ najlepszych praktyk: super kompleksowe i wyczerpujące +## 📗 46+ 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 From c8fd9d5bbb75c13e87654d8bc4cd54b97b7983e9 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sat, 27 Mar 2021 16:25:22 +0300 Subject: [PATCH 297/502] Update readme.kr.md --- readme.kr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.kr.md b/readme.kr.md index fa1822a9..9bc63455 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/) - 인사이트와 오직 전략적인 문제에 대한 내용 From 82c572098b7c3e48a9d33a9c57198cf341854f09 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sat, 27 Mar 2021 16:25:32 +0300 Subject: [PATCH 298/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 77166a54..f75e1ab3 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@
    -## 📗 45+ best practices: Super-comprehensive and exhaustive +## 📗 46+ best practices: Super-comprehensive and exhaustive This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and curates for you dozens of the best blog posts, books and tools the market has to offer From ae85c40c9a068cc6fb9a46046b945cda5fb0e8d4 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sat, 27 Mar 2021 16:25:37 +0300 Subject: [PATCH 299/502] Update index.js --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index e6be5312..6164a32f 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -// 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 From f6a1a97f9a7bcb8a03f0320f32b85975aaa7ea36 Mon Sep 17 00:00:00 2001 From: Otavio Araujo Date: Wed, 5 May 2021 19:20:44 -0300 Subject: [PATCH 300/502] Update readme.md --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index f75e1ab3..c61b74d5 100644 --- a/readme.md +++ b/readme.md @@ -1007,7 +1007,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
  • ]'); }); ``` @@ -1037,8 +1037,8 @@ test("When flagging to show only VIP, should display only VIP members", () => { // the markup code (part of React component)

    - {value} - + {value} +

    ``` @@ -1271,7 +1271,7 @@ export default function ProductsList() { fetchProducts(); }, []); - return products ?
    {products}
    :
    No products
    ; + return products ?
    {products}
    :
    No products
    ; } // test From f90609ad42ef3875e352dd18e3391dea055f4713 Mon Sep 17 00:00:00 2001 From: Otavio Araujo Date: Wed, 5 May 2021 19:20:54 -0300 Subject: [PATCH 301/502] Update readme-es.md --- readme-es.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme-es.md b/readme-es.md index 2ec1dcfc..ed6f56ea 100644 --- a/readme-es.md +++ b/readme-es.md @@ -1017,7 +1017,7 @@ test("When flagging to show only VIP, should display only VIP members", () => { 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 +1047,8 @@ test("When flagging to show only VIP, should display only VIP members", () => { // the markup code (part of React component)

    - {value} - + {value} +

    ``` @@ -1283,7 +1283,7 @@ export default function ProductsList() { fetchProducts(); }, []); - return products ?
    {products}
    :
    No products
    ; + return products ?
    {products}
    :
    No products
    ; } // test From 868daac5d462e0f13ab3254db9b17f82e40e16c4 Mon Sep 17 00:00:00 2001 From: Otavio Araujo Date: Wed, 5 May 2021 19:21:07 -0300 Subject: [PATCH 302/502] Update readme-pl.md --- readme-pl.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme-pl.md b/readme-pl.md index c5ea0165..499f5e9d 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -1001,7 +1001,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 +1031,8 @@ test("When flagging to show only VIP, should display only VIP members", () => { // the markup code (part of React component)

    - {value} - + {value} +

    ``` @@ -1267,7 +1267,7 @@ export default function ProductsList() { fetchProducts(); }, []); - return products ?
    {products}
    :
    No products
    ; + return products ?
    {products}
    :
    No products
    ; } // test From f364ba5454c44f8edb82ab32f11064af1ae54aac Mon Sep 17 00:00:00 2001 From: Otavio Araujo Date: Wed, 5 May 2021 19:21:17 -0300 Subject: [PATCH 303/502] Update readme-pt-br.md --- readme-pt-br.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme-pt-br.md b/readme-pt-br.md index 894954dd..e127ab7d 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -1029,7 +1029,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 +1065,7 @@ test('When flagging to show only VIP, should display only VIP members', () => { // the markup code (part of React component)

    - {value} + {value}

    ``` @@ -1319,7 +1319,7 @@ export default function ProductsList() { fetchProducts(); }, []); - return products ?
    {products}
    :
    No products
    + return products ?
    {products}
    :
    No products
    } // test From d4594a5cc51c06621f7964479f9020d005a2b98c Mon Sep 17 00:00:00 2001 From: Otavio Araujo Date: Wed, 5 May 2021 19:21:32 -0300 Subject: [PATCH 304/502] Update readme-zh-CN.md --- readme-zh-CN.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme-zh-CN.md b/readme-zh-CN.md index 0b0e1ac4..0566c382 100644 --- a/readme-zh-CN.md +++ b/readme-zh-CN.md @@ -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 From 08aec91f4ddbbcdea4407f73a20c32481354ebdc Mon Sep 17 00:00:00 2001 From: Otavio Araujo Date: Wed, 5 May 2021 19:21:41 -0300 Subject: [PATCH 305/502] Update readme.kr.md --- readme.kr.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index 9bc63455..a4dd1383 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -974,7 +974,7 @@ test('오직 VIP를 보기위해 사용자목록을 표시했을때, 오직 VIP const { getAllByTestId } = render(); // Assert - 화면과 데이터를 섞어서 검증 - expect(getAllByTestId('user')).toEqual('[
  • John Doe
  • ]'); + expect(getAllByTestId('user')).toEqual('[
  • John Doe
  • ]'); }); ``` @@ -1010,7 +1010,7 @@ test('오직 VIP를 보기위해 사용자목록을 표시했을때, 오직 VIP // the markup code (part of React component)

    - {value} + {value}

    ``` @@ -1262,7 +1262,7 @@ export default function ProductsList() { fetchProducts(); }, []); - return products ?
    {products}
    :
    No products
    + return products ?
    {products}
    :
    No products
    } // test From 1d25ac96c8e89267ac01d356d7a259a4de98cea0 Mon Sep 17 00:00:00 2001 From: jfernandezpe Date: Mon, 10 May 2021 21:44:25 +0200 Subject: [PATCH 306/502] minor spanish changes --- readme-es.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/readme-es.md b/readme-es.md index 2ec1dcfc..5d2e49b4 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,7 @@ 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) - ¿Quieres traducir a tu propio lenguaje? por favor abre una issue 💜

    @@ -799,7 +800,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 @@ -1975,7 +1976,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

    🖋 From 5a8ae051ee096d30fa034c04d8c48904cecca79e Mon Sep 17 00:00:00 2001 From: Alex Ivanov Date: Wed, 19 May 2021 17:27:33 +0300 Subject: [PATCH 307/502] Init gitignore. Get rid of artifacts Signed-off-by: Alex Ivanov --- .DS_Store | Bin 10244 -> 0 bytes .gitignore | 1 + 2 files changed, 1 insertion(+) delete mode 100644 .DS_Store create mode 100644 .gitignore diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index d47f21efd3899ffa9945e311f1e52256ca15e0d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHMTWl0n82Sb z#S-;N0*M;Y7^5*HUO-<|9*j@=049)VY$Ez#OuRi96JLEm{m-1)va@X^@j(rlvzhas z^Izti^PO+z%sC4H*p<~<0U`h((8Z*@fU+49v$JbjD)BYdB$48w&$N>X(@s!bp5`4Y zLIgqtLIgqtLIgqt{tpPyn$40>#$}j?2!sfP2uvZs-VYJFn2h>zf=ln!L7BG&AX!Rk zFHzb1^d}i%3X)M@PH;&YO2|V=@=)}*80haDkcX2!>ibV{$-@D?mgzoz%joY=(BF7q z#B&FXavA0!0wDs^5ny}wQfP)@Fu*O^zi%R2e;8OQ(u)rzo z+AJ=Cj>24>Ix>rAVYk0Kf96-H^13~%_sv1v+B+~L{?eOA199l5({P-kxp!G@-j%aH-Y((KVLJ8NlVZ4MQ@K9#>jrN9vL^U4AOhQMqd zJv1=Q?&P{THYKO>*j&$JBT8k;w(UElxV$JwL*A2i4cl@K=oyD(T&{2kroNu>*)J*3NUhd z4~020iWe3vt-Y(han1ehn>#1V7gorUw0Oxt&Tx!`sUK9Gq;6@3HN1b+(A<$jgtFxt zM-4qAij}3U{g#^2C*_KT4-)xJ!ESvN)zM{ zMb8pZ>XgIG(w)&M*ZhEclm@Baq0S1j${klqtEK+OXu3t&;hJWtMcHqv3EdQBjknV# zty2ba95GSe$(0+V4rP#D1}W;YBUWM)S8q|aDvz?3cd3rMC#_qoS69eJqv)zrl!MIH zscEcFLVdK6(;+a5`e+N+iuA!yL+^kn?0_Tm$fV&ooQ5-S7G8n#a1k!S$8Z_0z-Mq3 zzJPDwd-xH4f}i0xxB-74F%KoI!fI4-IX2)5T#2i&4L9IM+=QF47kA<=+>Lv15Qk93 zBx;z#9FF1%9K)yad3*t1#B=yMzJnL=BEE~4@Ke0XyH=8C^@F^g&+!_Sg5-5_{)1a` zbS84v_HNt$ACdFx1&ooEixw|gdT0HL)h+8fwoc7x(HsUQsE}D6oj6}S9_4w$xl6pQ zsg;)1&Bpls#610&!002-pY*(Im)|WAV`RarO0^B5NVN8C4Xemj;#->UBa14};Wnvl ztw4;G=W?sGVWUW#mP>u>mIuhXQ=T_jQ>|1*r7lSZ_K1o9oP%$`1^9^g_a$71Z;5-q z5bu5`&Xr-M$GI9@hIbO{8i{wSaSg7=cH&+Kc4H55Z#(YAhjAbF;{lBO{L|4w8`GFU z7sv4=K8dIBDLjp5@GL&(@$psS;`>Dm?8|eHOZ=difrU%lwHrS?`zBxF0xV70Hy5_q zd;-gvF~ph++#?suSf*_alb$^t^I;eCK{xcmZiqo|aIr)7QBt_riL}P+v^Z*zBH2v2 zRuu6}Q3O@P6e18J5F!vF5F!vFFcSiExH7`d|9fZu|9_^?P!}Q)B5<1^fTcaLo-P_p ze&1R=E_T))p=*dPmYClJmtF{E-f=uh?>L?+>^R=rzmv}2v3qe*(zEypE~!N6U;i^8 S-2bPnTO3Y>`~TZ}|Nj?UYx;fw diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e43b0f98 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store From eed9826c965e34db49c3d5c3d8cb963171b73f2f Mon Sep 17 00:00:00 2001 From: Alex Ivanov Date: Wed, 19 May 2021 17:28:50 +0300 Subject: [PATCH 308/502] Get rid of artifacts Signed-off-by: Alex Ivanov --- assets/~$header.pptx | Bin 165 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/~$header.pptx diff --git a/assets/~$header.pptx b/assets/~$header.pptx deleted file mode 100644 index 1a939757c177c1d46a4b7381482e348dffe4e331..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165 ucmd;%%+JfrRB+GF&q-lWAR2HpL^9+v Date: Wed, 19 May 2021 17:40:33 +0300 Subject: [PATCH 309/502] Trailing spaces Signed-off-by: Alex Ivanov --- .operations/questions-answers.md | 2 +- index.js | 2 +- readme.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.operations/questions-answers.md b/.operations/questions-answers.md index 67b42d7e..47fcd2f4 100644 --- a/.operations/questions-answers.md +++ b/.operations/questions-answers.md @@ -2,7 +2,7 @@ ## 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 diff --git a/index.js b/index.js index 6164a32f..a068a34d 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -// This is a book for now, but code examples are coming soon +// This is a book for now, but code examples are coming soon diff --git a/readme.md b/readme.md index f75e1ab3..9bbab2bf 100644 --- a/readme.md +++ b/readme.md @@ -24,7 +24,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 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/) +- [Follow me on Twitter](https://twitter.com/goldbergyoni/)
    From 13f3fe6bc097e5b507643995829b46004c6679d7 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 20 May 2021 21:32:30 +0200 Subject: [PATCH 310/502] Create readme-fr file --- readme-fr.md | 1972 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1972 insertions(+) create mode 100644 readme-fr.md diff --git a/readme-fr.md b/readme-fr.md new file mode 100644 index 00000000..f75e1ab3 --- /dev/null +++ b/readme-fr.md @@ -0,0 +1,1972 @@ + + +
    + +# 👇 Why this guide can take your testing skills to the next level + +
    + +## 📗 46+ best practices: Super-comprehensive and exhaustive + +This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and curates for you dozens of the best blog posts, books and tools the market has to offer + +## 🚢 Advanced: Goes 10,000 miles beyond the basics + +Hop into a journey that travels way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average + +## 🌐 Full-stack: front, backend, CI, anything + +Start by understanding the ubiquitous testing practices that are the foundation for any application tier. Then, delve into your area of choice: frontend/UI, backend, CI or maybe all of them? + +
    + +### Written By 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/) + +
    + +### Translations - read in your own language + +- 🇨🇳[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) +- Want to translate to your own language? please open an issue 💜 + +

    + +## `Table of Contents` + +#### [`Section 0: The Golden Rule`](#section-0️⃣-the-golden-rule) + +A single advice that inspires all the others (1 special bullet) + +#### [`Section 1: The Test Anatomy`](#section-1-the-test-anatomy-1) + +The foundation - structuring clean tests (12 bullets) + +#### [`Section 2: Backend`](#section-2️⃣-backend-testing) + +Writing backend and Microservices tests efficiently (8 bullets) + +#### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) + +Writing tests for web UI including component and E2E tests (11 bullets) + +#### [`Section 4: Measuring Tests Effectiveness`](#section-4️⃣-measuring-test-effectiveness) + +Watching the watchman - measuring test quality (4 bullets) + +#### [`Section 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) + +Guidelines for CI in the JS world (9 bullets) + +

    + +# Section 0️⃣: The Golden Rule + +
    + +## ⚪️ 0 The Golden Rule: Design for lean testing + +:white_check_mark: **Do:** +Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. + +Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. + +The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). + +This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. + +![alt text](/assets/headspace.png "We have no head room for additional complexity") + +Most of the advice below are derivatives of this principle. + +### Ready to start? + +

    + +# Section 1: The Test Anatomy + +
    + +## ⚪ ️ 1.1 Include 3 parts in each test name + +:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: + +(1) What is being tested? For example, the ProductsService.addNewProduct method + +(2) Under what circumstances and scenario? For example, no price is passed to the method + +(3) What is the expected result? For example, the new product is not approved + +
    + +❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? + +
    + +**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: A test name that constitutes 3 parts + +![](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: Doing It Right Example: A test name that constitutes 3 parts + +![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 Structure tests by the AAA pattern + +:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: + +1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code + +2nd A - Act: Execute the unit under test. Usually 1 line of code + +3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code + +
    + +❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: A test structured with the AAA pattern + +![](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: Anti-Pattern Example: No separation, one bulk, harder to interpret + +```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 Describe expectations in a product language: use BDD-style assertions + +:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +
    + +❌ **Otherwise:** The team will write less tests and decorate the annoying ones with .skip() + +
    + +
    Code Examples
    + +![](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: Anti-Pattern Example: The reader must skim through not so short, and imperative code just to get the test story + +```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: Doing It Right Example: Skimming through the following declarative test is a breeze + +```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 Stick to black-box testing: Test only public methods + +:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden +
    + +❌ **Otherwise:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored… + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A test case is testing the internals for no good reason + +![](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 Choose the right test doubles: Avoid mocks in favor of stubs and spies + +:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). + +Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. + +For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that have nothing to do with the application functionality and are likely to change frequently +
    + +❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-pattern example: Mocks focus on the internals + +![](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:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals + +```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; +}); +``` + +
    + +

    + +## 📗 Want to learn all these practices with live video? + +### Visit my online course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +

    + +## ⚪ ️1.6 Don’t “foo”, use realistic input data + +:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +
    + +❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data + +![](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:Doing It Right Example: Randomizing realistic input + +```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 Test many input combinations using Property-based testing + +:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +
    + +❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Testing many input permutations with “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 If needed, use only short & inline snapshots + +:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. + +On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much + +It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +
    + +❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code + +![](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: Doing It Right Example: Expectations are visible and focused + +```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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](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: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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 Don’t catch errors, expect them + +:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations + +A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +
    + +❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with 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: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical 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 Tag your tests + +:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +
    + +❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) + +![](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 Categorize tests under at least 2 levels + +:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and print screen below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) + +
    + +❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in a hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below + +![](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: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests + +![](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 Other generic good testing hygiene + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satisfies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +
    + +❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades + +

    + +# Section 2️⃣: Backend Testing + +## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid + +:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? + +Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IoT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. + +It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks + +A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] + +
    + +❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing 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 Component testing might be your best affair + +:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. + +Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +
    + +❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) + +![](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 Ensure new releases don’t break the API using contract tests + +:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +
    + +❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: + +![](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 Test your middlewares in isolation + +:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +
    + +❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests + +
    + +
    Code Examples + +
    + +### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine + +![](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 Measure and refactor using static analysis tools + +:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [SonarQube](https://www.sonarqube.org/) (4,900+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (2,000+ [stars](https://github.com/codeclimate/codeclimate)) + +Credit: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + +❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: + +![](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: **Do:** Weirdly, most software testings are about logic & data only, but some of the worst things that happen (and are really hard to mitigate) are infrastructural issues. For example, did you ever test what happens when your process memory is overloaded, or when the server/process dies, or does your monitoring system realizes when the API becomes 50% slower?. To test and mitigate these type of bad things — [Chaos engineering](https://principlesofchaos.org/) was born by Netflix. It aims to provide awareness, frameworks and tools for testing our app resiliency for chaotic issues. For example, one of its famous tools, [the chaos monkey](https://github.com/Netflix/chaosmonkey), randomly kills servers to ensure that our service can still serve users and not relying on a single server (there is also a Kubernetes version, [kube-monkey](https://github.com/asobti/kube-monkey), that kills pods). All these tools work on the hosting/platform level, but what if you wish to test and generate pure Node chaos like check how your Node process copes with uncaught errors, unhandled promise rejection, v8 memory overloaded with the max allowed of 1.7GB or whether your UX remains satisfactory when the event loop gets blocked often? to address this I’ve written, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) which provides all sort of Node-related chaotic acts +
    + +❌ **Otherwise:** No escape here, Murphy’s law will hit your production without mercy + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: : Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to 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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](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: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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️⃣: Frontend Testing + +## ⚪ ️ 3.1 Separate UI from functionality + +:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI + +
    + +❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Separating out the UI details + +![](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: Anti-Pattern Example: Assertion mix UI details and data + +```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 HTML elements based on attributes that are unlikely to change + +:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed + +
    + +❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Querying an element using a dedicated attribute for testing + +![](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: Anti-Pattern Example: Relying on 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 Whenever possible, test with a realistic and fully rendered component + +:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake + +With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children + +
    + +❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Working realistically with a fully rendered component + +![](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: Anti-Pattern Example: Mocking the reality with 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 Don't sleep, use frameworks built-in support for async events. Also try to speed things up + +:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +
    + +❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (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: Doing It Right Example: Testing library that waits for DOM elements + +```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: Anti-Pattern Example: custom sleep code + +```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 Watch how the content is served over the network + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") + +✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 + +
    + +❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration + +
    + +
    Code Examples + +### :clap: Doing It Right Example: Lighthouse page load inspection report + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + +
    + +
    + +## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs + +:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests + +
    + +❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower + +
    + +
    Code Examples + +
    + +### :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 +// 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 Have very few end-to-end tests that spans the whole system + +:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment + +
    + +❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected + +
    + +## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials + +:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). + +
    + +❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Logging-in before-all and not 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 Have one E2E smoke test that just travels across the site map + +:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector + +
    + +❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Smoke travelling across all 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 Expose the tests as a live collaborative document + +:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. + +❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Describing tests in human-language using 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: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook + +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 Detect visual issues with automated tools + +:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge significant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by eliminating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/CSS changes that led to the issue + +
    + +❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A typical visual regression - right content that is served badly + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + +### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots + +![](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: Doing It Right Example: Using Applitools to get snapshot comparison and other advanced features + +![](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️⃣: Measuring Test Effectiveness + +

    + +## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number + +:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. + +Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets + +
    + +❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down + +
    + +
    Code Examples + +
    + +### :clap: Example: A typical coverage report + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: Doing It Right Example: Setting up coverage per component (using 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 Inspect coverage reports to detect untested areas and other oddities + +:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +
    + +❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: 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?") + +
    + +

    + +## ⚪ ️ 4.3 Measure logical coverage using mutation testing + +:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. + +Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: + +(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations + +(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. + +Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +
    + +❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: 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: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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 Preventing test code issues with Test linters + +:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) + +
    + +❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A test case full of errors, luckily all are caught by 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 and Other Quality Measures + +

    + +## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues + +:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +
    + +❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5 min linter setup could detect this TYPO and save your day + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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 Shorten the feedback loop with local developer-CI + +:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. + +Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +
    + +❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new 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 Perform e2e testing over a true production-mirror + +:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of FaaS code. + +The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +
    + +❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated + +
    + +
    Code Examples + +
    + +### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([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 Parallelize test execution + +:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes + +❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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 Stay away from legal issues using license and plagiarism check + +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stack Overflow and apparently violates some copyrights + +❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: + +```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 Constantly inspect for vulnerable dependencies + +:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build + +❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious + +
    + +
    Code Examples + +
    + +### :clap: Example: NPM Audit result + +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + +

    + +## ⚪ ️5.7 Automate dependency updates + +:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: + +(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. + +(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). + +An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +
    + +❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky + +
    + +
    Code Examples + +
    + +### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest 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 Other, non-Node related, CI tips + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +
    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
    +
    + +❌ **Otherwise:** You‘ll miss years of wisdom + +

    + +## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions + +:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +
    + +❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? + +
    + +
    Code Examples + +
    + +### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions + +
    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. + +## 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

    🖋
    + + + + From aba84d7c8eee67a509b2b5da5a251244cd931b18 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Fri, 21 May 2021 18:03:42 +0200 Subject: [PATCH 311/502] Introduction translation --- readme-fr.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index f75e1ab3..adbe555a 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -2,29 +2,29 @@
    -# 👇 Why this guide can take your testing skills to the next level +# 👇 Pourquoi ce guide peut faire passer vos compétences de test au niveau supérieur
    -## 📗 46+ best practices: Super-comprehensive and exhaustive +## 📗 46+ bonnes pratiques : complet et exhaustif -This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and curates for you dozens of the best blog posts, books and tools the market has to offer +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é -## 🚢 Advanced: Goes 10,000 miles beyond the basics +## 🚢 Avancé : va bien au-delà des bases -Hop into a journey that travels way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average +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, anything +## 🌐 Full-stack: front, backend, CI ... -Start by understanding the ubiquitous testing practices that are the foundation for any application tier. Then, delve into your area of choice: frontend/UI, backend, CI or maybe all of them? +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 cela ?
    -### Written By Yoni Goldberg +### Écrit par 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/) +- 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/)
    From 5cc16450fa53af7e0e0e23ff909b64041ff9bfb2 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 27 May 2021 08:28:01 +0200 Subject: [PATCH 312/502] Translation section + link towards french in the english version --- readme-fr.md | 16 ++++++++-------- readme.md | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index adbe555a..36e9d72e 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -28,14 +28,14 @@ Commence par comprendre les pratiques de tests omniprésentes qui sont à la bas
    -### Translations - read in your own language - -- 🇨🇳[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) -- Want to translate to your own language? please open an issue 💜 +### 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) +- Envie de traduire dans ta propre langue ? Ouvres une issue 💜

    diff --git a/readme.md b/readme.md index f75e1ab3..5268fbf9 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇵🇱[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) - Want to translate to your own language? please open an issue 💜

    From d9006c57ebd38512420ba36c9a1d70ac0e2d1f85 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 27 May 2021 17:11:56 +0200 Subject: [PATCH 313/502] Translate table of content --- readme-fr.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 36e9d72e..e0f644dd 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -16,7 +16,7 @@ Embarque pour un voyage qui va bien au-delà des bases et aborde des sujets avan ## 🌐 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 cela ? +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 ?
    @@ -39,31 +39,31 @@ Commence par comprendre les pratiques de tests omniprésentes qui sont à la bas

    -## `Table of Contents` +## `Table des matières` -#### [`Section 0: The Golden Rule`](#section-0️⃣-the-golden-rule) +#### [`Section 0: La règle d'or`](#section-0️⃣-the-golden-rule) -A single advice that inspires all the others (1 special bullet) +Un seul conseil qui inspire tout les autres (1 point spécial) -#### [`Section 1: The Test Anatomy`](#section-1-the-test-anatomy-1) +#### [`Section 1: Anatomie d'un test`](#section-1-the-test-anatomy-1) -The foundation - structuring clean tests (12 bullets) +La base - structurer des tests propre (12 points) #### [`Section 2: Backend`](#section-2️⃣-backend-testing) -Writing backend and Microservices tests efficiently (8 bullets) +Écrire efficacement des tests backend et de microservices (8 points) #### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) -Writing tests for web UI including component and E2E tests (11 bullets) +Écrire des tests pour l'interface utilisateur, y compris des tests de composants et des tests E2E (11 points) -#### [`Section 4: Measuring Tests Effectiveness`](#section-4️⃣-measuring-test-effectiveness) +#### [`Section 4: Mesurer l'efficacité des tests`](#section-4️⃣-measuring-test-effectiveness) -Watching the watchman - measuring test quality (4 bullets) +Surveiller le surveillant - mesurer la qualité des tests (4 points) -#### [`Section 5: Continuous Integration`](#section-5️⃣-ci-and-other-quality-measures) +#### [`Section 5: Intégration continue`](#section-5️⃣-ci-and-other-quality-measures) -Guidelines for CI in the JS world (9 bullets) +Lignes directrices pour l'intégration continue dans le monde du JS (9 points)

    From d821333fc93a4a4c90b45eb99564180a6514302e Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 27 May 2021 17:42:15 +0200 Subject: [PATCH 314/502] Section 0 --- readme-fr.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index e0f644dd..8808c2f7 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -67,26 +67,27 @@ Lignes directrices pour l'intégration continue dans le monde du JS (9 points)

    -# Section 0️⃣: The Golden Rule +# Section 0️⃣: La règle d'or
    -## ⚪️ 0 The Golden Rule: Design for lean testing +## ⚪️ 0 La règle d'or: Concevoir des tests minimalistes :white_check_mark: **Do:** -Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. +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. -Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. +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. -The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). +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 complexe 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). -This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. +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 "We have no head room for additional complexity") +![alt text](/assets/headspace.png "On a pas de place disponible pour une complexité supplémentaire") -Most of the advice below are derivatives of this principle. +La plupart des conseils ci-dessous sont des dérivés de ce principe. -### Ready to start? +### Prêt à commencer ?

    From 3351a1fcc44bf05817e40a64469a9c375693319e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 30 May 2021 07:50:06 +0000 Subject: [PATCH 315/502] docs: update readme.md [skip ci] --- readme.md | 76 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/readme.md b/readme.md index c61b74d5..c46b529a 100644 --- a/readme.md +++ b/readme.md @@ -1921,52 +1921,56 @@ 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

    🖋

    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

    🖋

    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

    🚧

    John Gee

    🖋

    Aurelijus Rožėnas

    🖋

    Aaron

    🖋

    Tom Nagle

    🖋

    Yves yao

    🖋

    Userbit

    🖋

    Glaucia Lemos

    🚧

    koooge

    🖋

    Michal

    🖋

    roywalker

    🖋

    dangen

    🖋

    biesiadamich

    🖋

    Yanlin Jiang

    🖋

    sanguino

    🖋

    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

    🖋

    Morgan

    🖋

    Lukas Bischof

    ⚠️ 🖋

    JuanMa Ruiz

    🖋

    Luís Ângelo Rodrigues Jr.

    🖋

    José Fernández

    🖋

    Alejandro Gutierrez Barcenilla

    🖋

    Jason

    🖋

    Otavio Araujo

    ⚠️ 🖋
    - + + From 64e6eba43aecb9c5fe24937448b4b7f156755599 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 30 May 2021 07:50:07 +0000 Subject: [PATCH 316/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8ee1279f..fe10f27c 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -322,6 +322,16 @@ "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" + ] } ], "projectName": "javascript-testing-best-practices", From fee3edea278e27df13260930d5e56abb90a2d596 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 30 May 2021 11:37:48 +0000 Subject: [PATCH 317/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 4433ccd7..55a639b7 100644 --- a/readme.md +++ b/readme.md @@ -1967,6 +1967,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    Otavio Araujo

    ⚠️ 🖋 +
    Alex Ivanov

    🖋 From d3e081a5ad0b23736d9385b3ee811b9e0b373d8b Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 30 May 2021 11:37:49 +0000 Subject: [PATCH 318/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index fe10f27c..19225d22 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -332,6 +332,15 @@ "test", "content" ] + }, + { + "login": "contributorpw", + "name": "Alex Ivanov", + "avatar_url": "https://avatars.githubusercontent.com/u/5027939?v=4", + "profile": "https://contributor.pw", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 3bdd161b6ee536bca309acd34d82a73021178358 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Wed, 16 Jun 2021 00:18:44 +0200 Subject: [PATCH 319/502] Translate 1.1 --- readme-fr.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 8808c2f7..b9755443 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -91,34 +91,34 @@ La plupart des conseils ci-dessous sont des dérivés de ce principe.

    -# Section 1: The Test Anatomy +# Section 1: Anatomie d'un test
    -## ⚪ ️ 1.1 Include 3 parts in each test name +## ⚪ ️ 1.1 Chaque nom devrait contenir 3 parties -:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: +: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 familier avec la base de code : le testeur, le dev ops qui deploie et toi dans 2 ans. Dans ce but, les noms des tests doivent expliciter les attentes et inclure 3 parties : -(1) What is being tested? For example, the ProductsService.addNewProduct method +(1) Qu'est-ce qui est testé ? Par exemple, la méthode ProductService.addNewProduct -(2) Under what circumstances and scenario? For example, no price is passed to the method +(2) Dans quel circonstance et scénario ? Par exemple, aucun prix n'est passé à la méthode -(3) What is the expected result? For example, the new product is not approved +(3) Quel est le résultat attendu ? Par exemple, le produit n'est pas approuvé
    -❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? +❌ **Autrement:** Un deploiement a échoué, un test appelé "Add product" à échoué. Est-ce que celà indique exactement ce qui ne fonctionne plus correctement ?
    -**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +**👇 Note:** Chaque point contient des exemples de codes et parfois une image d'illustration. Cliques pour agrandir.
    Code Examples
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :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") @@ -138,7 +138,7 @@ describe('Products Service', function() {
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :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") From cd7e5a3dc1b018a794bd452526fef1224c8b717b Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Wed, 16 Jun 2021 00:40:52 +0200 Subject: [PATCH 320/502] Translate 1.2 --- readme-fr.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index b9755443..4c0b31c4 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -151,19 +151,20 @@ describe('Products Service', function() {

    -## ⚪ ️ 1.2 Structure tests by the AAA pattern +## ⚪ ️ 1.2 Structurer les tests avec le pattern AAA -:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: +:white_check_mark: **À faire:** Structures tes tests avec 3 sections séparés: 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: -1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code +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 autre codes de préparation -2nd A - Act: Execute the unit under test. Usually 1 line of code +2ème A - Agir (Act): Exécute l'élément testé. En général 1 seule ligne de code -3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of 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
    -❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain +❌ **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. +
    @@ -171,7 +172,7 @@ describe('Products Service', function() {
    -### :clap: Doing It Right Example: A test structured with the AAA pattern +### :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") @@ -193,7 +194,7 @@ describe("Customer classifier", () => {
    -### :thumbsdown: Anti-Pattern Example: No separation, one bulk, harder to interpret +### :thumbsdown: Exemple d'anti pattern: Pas de séparation, un bloc, plus dur à interpréter ```javascript test("Should be classified as premium", () => { From 4874247a7d1c545ae6a738704445dde1dbc7632e Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Wed, 16 Jun 2021 00:50:44 +0200 Subject: [PATCH 321/502] Translate 1.3 --- readme-fr.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 4c0b31c4..5d93470f 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -209,20 +209,21 @@ test("Should be classified as premium", () => {

    -## ⚪ ️1.3 Describe expectations in a product language: use BDD-style assertions +## ⚪ ️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 language 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 remplit de logique conditionnelles, le lecteur est forcé d'utiliser plus de cycles de "CPU" de cerveau. Dans ce cas, codes les attentes dans un language similaire au language 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écéssaires 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/) -:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/)
    -❌ **Otherwise:** The team will write less tests and decorate the annoying ones with .skip() +❌ **Autrement:** L'équipe écrira moins de tests et décorera ceux qui sont ennuyeux avec .skip()
    -
    Code Examples
    +
    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: Anti-Pattern Example: The reader must skim through not so short, and imperative code just to get the test story +### :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", () => { @@ -252,7 +253,7 @@ test("When asking for an admin, ensure only ordered admins in results", () => {
    -### :clap: Doing It Right Example: Skimming through the following declarative test is a breeze +### :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", () => { From 781eac6ac2b199cf852507782d342d695e72230e Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Fri, 18 Jun 2021 16:24:46 +0200 Subject: [PATCH 322/502] Translate 1.4 --- readme-fr.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 5d93470f..dcc75dbd 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -168,7 +168,7 @@ describe('Products Service', function() {
    -
    Code Examples +
    Exemple de code
    @@ -270,19 +270,19 @@ it("When asking for an admin, ensure only ordered admins in results", () => {

    -## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods +## ⚪ ️ 1.4 S'en tenir aux tests des boites noires: Ne teste que les méthodes publiques -:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden +:white_check_mark: **À faire:** Tester les composants internes apporte beaucoup de complexité pour presque rien. Si ton code/API délivre les bon resultats, 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'implementation 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é `behavioral testing` (test de comportement). De l'autre coté, 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.
    -❌ **Otherwise:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored… +❌ **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é à été changé). Sans surprise, les gens vont rapidement ignorer les notifications, jusqu'à ce qu'un jour, un vrai beug soit ignoré
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-Pattern Example: A test case is testing the internals for no good reason +### :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") From fc62f52f393ecd7ee110261ff2fed54cf1b639e7 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Fri, 18 Jun 2021 16:46:41 +0200 Subject: [PATCH 323/502] Translate 1.5 --- readme-fr.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index dcc75dbd..5a99026a 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -312,24 +312,24 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response",

    -## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies +## ⚪ ️ ️1.5 Choisir les bons "test doubles": Éviter les mocks en faveur des stubs et spies -:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **À faire:** Les "test doubles" sont un mal nécéssaire parce qu'il sont couplé 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)). -Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. +Avant d'utiliser des "test doubles", pose toi une question trés simple: Est-ce que je l'utilise pour tester une fonctionnalité qui apparait, ou peut apparaitre, dans le document de spécification ? Si non, ça sent le test de boite blanche. -For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that have nothing to do with the application functionality and are likely to change frequently +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 apparaitre dans les spécification ("Envoyer un email si le paiement n'as pas pu être enregistré"). +D'un autre coté, si tu mock le service de paiement pour vérifié 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.
    -❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend - +❌ **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.
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-pattern example: Mocks focus on the internals +### :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") @@ -349,7 +349,7 @@ it("When a valid product is about to be deleted, ensure data access DAL was call
    -### :clap:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals +### :clap: Faire les choses bien, 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 () => { @@ -365,9 +365,9 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

    -## 📗 Want to learn all these practices with live video? +## 📗 Envie d'apprendre ces bonnes pratiques en vidéo ? -### Visit my online course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) +### Va voir mon cours en ligne [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com)

    From 287e634e40fa7cfe7bc6132c924991f2175e30dc Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Fri, 18 Jun 2021 17:01:26 +0200 Subject: [PATCH 324/502] Translate 1.7 --- readme-fr.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 5a99026a..254cd5dd 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -371,21 +371,19 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

    -## ⚪ ️1.6 Don’t “foo”, use realistic input data +## ⚪ ️1.6 Utilise des données réaliste -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +:white_check_mark: **À faire:** Souvent les beugs de production sont révélés par des entrées tres spécifiques et surprenantes. Plus les entrées de tests seront réalistes, plus il y a de chance de détecter les beugs 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 resemble 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éphones, 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 vrais données depuis ton environnement de production. Envie de passer au niveau supérieur ? Regarde le prochain point (property-based testing).
    -❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” - +❌ **Autrement:** Tout vos tests de développement vont montrer du vert à tord 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”
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data - +### :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 @@ -409,7 +407,7 @@ test("Wrong: When adding new product with valid properties, get successful confi
    -### :clap:Doing It Right Example: Randomizing realistic input +### :clap: Bien faire les choses, exemple : Données réalistes randomisés ```javascript it("Better: When adding new valid product, get successful confirmation", async () => { From bac23d5d8ebf47df0d984b32c460360e572833d8 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Wed, 23 Jun 2021 13:02:40 +0200 Subject: [PATCH 325/502] Translate 1.7 --- readme-fr.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 254cd5dd..20e1b829 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -371,7 +371,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

    -## ⚪ ️1.6 Utilise des données réaliste +## ⚪ ️1.6 Utilise des données réalistes :white_check_mark: **À faire:** Souvent les beugs de production sont révélés par des entrées tres spécifiques et surprenantes. Plus les entrées de tests seront réalistes, plus il y a de chance de détecter les beugs 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 resemble 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éphones, 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 vrais données depuis ton environnement de production. Envie de passer au niveau supérieur ? Regarde le prochain point (property-based testing).
    @@ -423,20 +423,19 @@ it("Better: When adding new valid product, get successful confirmation", async (

    -## ⚪ ️ 1.7 Test many input combinations using Property-based testing +## ⚪ ️ 1.7 Teste plusieurs combinaisons d'input avec le Property-based testing -:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +: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 'Utilise 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 toute les combinaisons d'entrées possible on augmente les chance de détecter un beug. Par exemple, prenom une méthode : addNewProduct(id, name, isDiscount), la librairie appelera 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.
    -❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs - +❌ **Autrement:** Inconsciemment, tu choisis des entrées de test qui ne couvrent que les cas qui fonctionnent correctement. Malheuresement, cela réduit l'efficacité tests et leur capacité a détecter des beugs.
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Testing many input permutations with “fast-check” +### :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") From 15138fa4d02466cef4cbf2e438afaf5cd9cd343e Mon Sep 17 00:00:00 2001 From: Yiqiao Xu Date: Thu, 8 Jul 2021 09:39:31 +0800 Subject: [PATCH 326/502] =?UTF-8?q?fix:=20typo=20'=E6=B1=87=E6=8A=A5'=20->?= =?UTF-8?q?=20'=E5=9B=9E=E6=8A=A5'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme-zh-CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-zh-CN.md b/readme-zh-CN.md index 0566c382..67e0bcbc 100644 --- a/readme-zh-CN.md +++ b/readme-zh-CN.md @@ -68,7 +68,7 @@ JS 领域的 CI 指南(9 条) 我们的思维空间被主体生产代码充满,因此无法腾出额外的“大脑空间”存放复杂的东西。如果向可怜的大脑中塞进其他复杂代码,将会使得整个部分变慢,而这个部分正是用来解决我们需要测试的问题的。这也是大部分团队放弃测试的原因。 -另一方面,测试是一个友好的助手,一个你乐于与之合作、投资小汇报大的助手。科学证明我们有两套大脑系统:系统 1 用于无需努力的活动如在一个空旷的路上开车;系统 2 用于复杂和繁琐的工作如算一道数学表达式。将你的测试为系统 1 设计,当你看一段测试代码时,需要像改 HTML 文档一样简单而不是像计算 2 × (17 × 24)。 +另一方面,测试是一个友好的助手,一个你乐于与之合作、投资小回报大的助手。科学证明我们有两套大脑系统:系统 1 用于无需努力的活动如在一个空旷的路上开车;系统 2 用于复杂和繁琐的工作如算一道数学表达式。将你的测试为系统 1 设计,当你看一段测试代码时,需要像改 HTML 文档一样简单而不是像计算 2 × (17 × 24)。 为了达到这个目的,我们可以通过选择性价比高、投入产出比(ROI)高的技术、工具以及测试对象。仅测试需要的内容,努力保持其灵活性,某些时候甚至值得去舍弃一些测试来换取灵活性和简洁性。 From eaa500bf5f060863df7af537b8a99c1e637b2b29 Mon Sep 17 00:00:00 2001 From: Alireza Rezania Date: Mon, 9 Aug 2021 15:49:53 +0430 Subject: [PATCH 327/502] hyperlink references to bullet --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 55a639b7..6003f5aa 100644 --- a/readme.md +++ b/readme.md @@ -424,7 +424,7 @@ it("Better: When adding new valid product, get successful confirmation", async ( ## ⚪ ️ 1.7 Test many input combinations using Property-based testing -:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet ‘Don’t foo’), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet [‘Don’t foo’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained
    ❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs @@ -1081,7 +1081,7 @@ test("Whenever no data is passed, error metric shows zero", () => { ## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component -:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet ['Favour blackbox testing'](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-14-stick-to-black-box-testing-test-only-public-methods)). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children @@ -1295,7 +1295,7 @@ test("When no products exist, show the appropriate message", () => { ## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system -:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment
    @@ -1305,7 +1305,7 @@ test("When no products exist, show the appropriate message", () => { ## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials -:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). +:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)).
    From 68a3661362e8a594412e11764e8f1d0ff359fe22 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 15 Aug 2021 11:51:39 +0000 Subject: [PATCH 328/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 6003f5aa..9edc8d90 100644 --- a/readme.md +++ b/readme.md @@ -1968,6 +1968,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    Otavio Araujo

    ⚠️ 🖋
    Alex Ivanov

    🖋 +
    Yiqiao Xu

    🖋 From 4cdb45045287b4659071026731a83b062255ff45 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 15 Aug 2021 11:51:39 +0000 Subject: [PATCH 329/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 19225d22..28eecb0d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -341,6 +341,15 @@ "contributions": [ "content" ] + }, + { + "login": "YeeJone", + "name": "Yiqiao Xu", + "avatar_url": "https://avatars.githubusercontent.com/u/20400822?v=4", + "profile": "https://github.com/YeeJone", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 85a88999f6d7e6e91da838e46e5a5971a464b1c7 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 15 Aug 2021 17:08:11 +0300 Subject: [PATCH 330/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 9edc8d90..30f800c2 100644 --- a/readme.md +++ b/readme.md @@ -424,7 +424,7 @@ it("Better: When adding new valid product, get successful confirmation", async ( ## ⚪ ️ 1.7 Test many input combinations using Property-based testing -:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet [‘Don’t foo’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false” , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet [‘Don’t foo’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained
    ❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs From 7130098ec5d52ad68316ca789f2bf5c086e75021 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Tue, 17 Aug 2021 16:11:20 +0200 Subject: [PATCH 331/502] Translate 1.8 --- readme-fr.md | 103 +++++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 20e1b829..d5fcfd6e 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -459,24 +459,24 @@ describe("Product service", () => {

    -## ⚪ ️ 1.8 If needed, use only short & inline snapshots +## ⚪ ️ 1.8 Si besoin, n'utilise que des snapshots courts et inline -:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. +: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. -On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much +D'un autre coté, 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 peux, 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 celà arrivera probablement souvent. A quelle frequence ? 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 success un long document qu'il ne pourra pas inspecter et vérifier. Tout ces points sont des symptomes d'un test obscure qui n'est pas ciblé et cherche à en faire trop. -It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +Il faut noter qu'il y a quelques cas ou de long snapshots externes sont acceptable - Pour valideer un schema et pas des données ou concernant des documents qui ne changent presque jamais
    -❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **Sinon:** Un test UI échoue. Le code semble bon, l'écran rends 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 à été ajouté dans le markdown...
    -
    Code Examples +
    Exemples de code
    -### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code +### :thumbsdown: Exemple d'anti pattern: Coupler nos test à 2000 lignes de code qu'on ne voit pas ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -498,7 +498,7 @@ it("TestJavaScript.com is renderd correctly", () => {
    -### :clap: Doing It Right Example: Expectations are visible and focused +### :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", () => { @@ -1007,7 +1007,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
  • ]'); }); ``` @@ -1037,8 +1037,8 @@ test("When flagging to show only VIP, should display only VIP members", () => { // the markup code (part of React component)

    - {value} - + {value} +

    ``` @@ -1271,7 +1271,7 @@ export default function ProductsList() { fetchProducts(); }, []); - return products ?
    {products}
    :
    No products
    ; + return products ?
    {products}
    :
    No products
    ; } // test @@ -1921,52 +1921,57 @@ 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

    🖋

    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

    🖋

    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

    🚧

    John Gee

    🖋

    Aurelijus Rožėnas

    🖋

    Aaron

    🖋

    Tom Nagle

    🖋

    Yves yao

    🖋

    Userbit

    🖋

    Glaucia Lemos

    🚧

    koooge

    🖋

    Michal

    🖋

    roywalker

    🖋

    dangen

    🖋

    biesiadamich

    🖋

    Yanlin Jiang

    🖋

    sanguino

    🖋

    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

    🖋

    Morgan

    🖋

    Lukas Bischof

    ⚠️ 🖋

    JuanMa Ruiz

    🖋

    Luís Ângelo Rodrigues Jr.

    🖋

    José Fernández

    🖋

    Alejandro Gutierrez Barcenilla

    🖋

    Jason

    🖋

    Otavio Araujo

    ⚠️ 🖋

    Alex Ivanov

    🖋
    - + - + + \ No newline at end of file From 538e55557648b2c9d27eb7c65c7f9300ada954dc Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Tue, 17 Aug 2021 16:23:56 +0200 Subject: [PATCH 332/502] Translate 1.10 --- readme-fr.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index d5fcfd6e..f764d376 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -526,20 +526,22 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {

    -## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test +## ⚪ ️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ée 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ée 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 compléxité 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ée en base de donnée 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). -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries)
    -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **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
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :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") @@ -564,7 +566,7 @@ it("When querying by site name, get the right site", async () => {
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :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 () => { From 1b15b3d32a890d880e6acb16254d18da8fa75629 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Tue, 17 Aug 2021 16:23:56 +0200 Subject: [PATCH 333/502] Translate 1.9 --- readme-fr.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index d5fcfd6e..f764d376 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -526,20 +526,22 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {

    -## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test +## ⚪ ️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ée 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ée 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 compléxité 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ée en base de donnée 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). -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries)
    -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **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
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :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") @@ -564,7 +566,7 @@ it("When querying by site name, get the right site", async () => {
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :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 () => { From 437179d2aea81147d3399c0fb1986809b3a737db Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Wed, 18 Aug 2021 13:40:58 +0200 Subject: [PATCH 334/502] Translate 1.10 --- readme-fr.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index f764d376..16947f61 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -585,22 +585,22 @@ it("When updating site name, get successful confirmation", async () => {
    -## ⚪ ️ 1.10 Don’t catch errors, expect them +## ⚪ ️ 1.10 Ne catch pas les erreurs, prevois les -:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations +:white_check_mark: **À faire:** Lorsqu'on essaye de detecter 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. -A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +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.
    -❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +❌ **Autrement:** Il sera compliqué de déduire du rapport de test ce qui s'est mal passé
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch +### :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") @@ -621,7 +621,7 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM +### :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 () => { From 566e890a3179ea4b15351f36af22dd27fcd6a598 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Wed, 18 Aug 2021 13:55:52 +0200 Subject: [PATCH 335/502] Translate 1.11 --- readme-fr.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 16947f61..ef9818b7 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -635,20 +635,20 @@ it("When no product name, it throws error 400", async () => {

    -## ⚪ ️ 1.11 Tag your tests +## ⚪ ️ 1.11 Tag tes tests -:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +: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 peux 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, voila comment invoquer uniquement le groupe de test sanity avec Mocha : mocha - grep 'sanity'
    -❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests - +❌ **Autrement:** Lancer tout les tests, y compris ceux qui éxecutent des dizaines de requetes DB, a chaque fois qu'un développeur fait un petit changement peut être extremement lent et pousser les développeurs a ne pas utiliser les tests.
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) +### :clap: Bien faire les choses, exemple: Tagger 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") From 5645dd8cfe52bda5f0d782196c5175a97664ec0d Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Wed, 18 Aug 2021 14:10:40 +0200 Subject: [PATCH 336/502] Translate 1.12 --- readme-fr.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index ef9818b7..bc4861e7 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -668,22 +668,21 @@ describe("Order service", function() {

    -## ⚪ ️ 1.12 Categorize tests under at least 2 levels +## ⚪ ️ 1.12 Catégorise tes tests sous au moins 2 niveaux -:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and print screen below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **À faire:** Applique une structure à ta suite de test 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 deuxieme 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 voulu 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)
    -❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in a hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause +❌ **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 majeur et comprendre les liens entre les tests qui échouent. Imagine le cas suivant : Quand 7/100 tests échouent, regarder une liste à plat nécéssitera d'aller lire le contenu des tests qui échouent pour comprendre le lien entre eux. En revanche, dans un rapport hiérarchique ils pourrait tous etre 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.
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below - +### :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 @@ -704,7 +703,7 @@ describe("Transfer service", () => {
    -### :thumbsdown: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests +### :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") From e080bb152f7a7ba2cff79f234cb686a5851d197b Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Wed, 18 Aug 2021 14:29:15 +0200 Subject: [PATCH 337/502] Translate 1.13 - Draft part 1 done --- readme-fr.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index bc4861e7..8ac7af2f 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -723,14 +723,14 @@ test("Then there should not be a new transfer record", () => {});

    -## ⚪ ️1.13 Other generic good testing hygiene +## ⚪ ️1.13 Autre bonnes pratiques génériques -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +: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. -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satisfies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +Apprend 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 valident le test - puis refactor graduellement et passe le code a un niveau de production, évite toute dépendance à l'environnement (paths, OS, etc)
    -❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades +❌ **Autrement:** Tu manqueras les perles de sagesses recueillies pendant des décennies.

    From 3323747cdcd2c3ea440819fdc9040a46b660851a Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 19 Aug 2021 16:29:56 +0200 Subject: [PATCH 338/502] Translate 2.1 --- readme-fr.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 8ac7af2f..9c0890b8 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -734,29 +734,29 @@ Apprend et pratique [les principes TDD](https://www.sm-cloud.com/book-review-tes

    -# Section 2️⃣: Backend Testing +# Section 2️⃣: Tests Backend -## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid +## ⚪ ️2.1 Enrichis ton portefeuille de test: Vois plus loin que les tests unitaire et la pyramide -:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +: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 model 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és 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 ? -Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IoT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. +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és 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ènement 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 unitaire sur une application qui est centrée sur l'intégration et n'as 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. -It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks +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), de 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 sont 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. -A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] +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 :]
    -❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes +❌ **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
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the same way’ +### :clap: Bien faire les choses, exemple: Cindy Sridharan propose un portefeuille de tests riche dans ton 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’") From e81b8f18490060c7aedc927ef51cac82c893f2da Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 19 Aug 2021 16:45:04 +0200 Subject: [PATCH 339/502] Translate 2.2 --- readme-fr.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 9c0890b8..8290c263 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -770,22 +770,23 @@ Un mot d'avertissement: l'argument du TDD dans le monde du développement à un

    -## ⚪ ️2.2 Component testing might be your best affair +## ⚪ ️2.2 Les tests de composant pourrait être ta meilleure aventure -:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +:white_check_mark: **À faire:** Chaque test unitaire couvre une petite portion de l'application et il est couteux 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 unitaire mais plus petit 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 correct et réaliste + +Les tests de composant se concentre 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 fasant ç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. -Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time.
    -❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage +❌ **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
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) +### :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") From 8d79ae924b93050966b2c19737fa099edd3e6592 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 23 Aug 2021 08:00:48 +0200 Subject: [PATCH 340/502] Translate 2.3 --- readme-fr.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 8290c263..017ba06d 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -796,20 +796,19 @@ Les tests de composant se concentre sur "l'unité" du microservice, ils fonction

    -## ⚪ ️2.3 Ensure new releases don’t break the API using contract tests +## ⚪ ️2.3 Vérifie que les nouvelles versions ne cassent pas l'API avec les tests de contrat -:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +: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 champs et 'boom!', un client important qui compte sur ce champs est en colère. C'est le Catch-22 du monde de l'intégration : Il est trés difficilé pour le coté serveur de considérer toute les attentes des clients. D'un autre coté, les clients ne peuvent pas réaliser de tests puisque le serveur controles 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éfini ses propres plan de test, mais le client qui défini 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'éxé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.
    -❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear - +❌ **Autrement:** L'alternative sont les tests manuels épuisants ou la peur du déploiement
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: +### :clap: Bien faire les choses, exemple: ![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") From fc52f1e3771e29696b7c1b13c6cc93ff5fdb7b40 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 23 Aug 2021 08:07:58 +0200 Subject: [PATCH 341/502] Translate 2.4 --- readme-fr.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 017ba06d..22b53eb5 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -818,20 +818,19 @@ Les tests de composant se concentre sur "l'unité" du microservice, ils fonction

    -## ⚪ ️ 2.4 Test your middlewares in isolation +## ⚪ ️ 2.4 Test tes middlewares de manière isolée -:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +:white_check_mark: **À faire:** Beaucoup évitent les tests de Middleware parce qu'ils représentent une petite portion du systeme et requièrent un serveur express live. Ce sont deux mauvaises raisons - les Middlewares sont petit mais affectent toute ou la plupart des requetes et peuveunt ê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'object {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'object {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)
    -❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests - +❌ **Autrement:** Un bug dans un middleware Express === un bug dans toutes ou la plupart des requêtes
    -
    Code Examples +
    Exemple de code
    -### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :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") From cd5a0c378d6f6deae6a969b6f11fffec0dbc60ca Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Tue, 24 Aug 2021 08:00:24 +0200 Subject: [PATCH 342/502] Fix word + Translate 2.5 --- readme-fr.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 22b53eb5..fb2c5c39 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -770,7 +770,7 @@ Un mot d'avertissement: l'argument du TDD dans le monde du développement à un

    -## ⚪ ️2.2 Les tests de composant pourrait être ta meilleure aventure +## ⚪ ️2.2 Les tests de composant pourrait être ta meilleure affaire :white_check_mark: **À faire:** Chaque test unitaire couvre une petite portion de l'application et il est couteux 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 unitaire mais plus petit 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 correct et réaliste @@ -857,23 +857,23 @@ test("A request without authentication header, should return http status 403", (

    -## ⚪ ️2.5 Measure and refactor using static analysis tools +## ⚪ ️2.5 Mesure et refactorise en utilisant des outils d'analyse statique -:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [SonarQube](https://www.sonarqube.org/) (4,900+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (2,000+ [stars](https://github.com/codeclimate/codeclimate)) +:white_check_mark: **À faire:** Utiliser des outils d'analye 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étécter des duplication), effectuer des analyses avancer (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)
    -❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix +❌ **Autrement:** Avec du code de mauvaise qualité, les beugs et la performance seront toujours un problème qu'aucune nouvelle librairie ou fonctionnalitée de pointe ne peux corriger
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: +### :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") From 10f408a333e6d2fe5b62ef2889e46c44ab97feb9 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Tue, 24 Aug 2021 08:18:13 +0200 Subject: [PATCH 343/502] Translate 2.6 --- readme-fr.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index fb2c5c39..7ecb4bec 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -883,20 +883,20 @@ Credit: Date: Thu, 26 Aug 2021 07:49:38 +0200 Subject: [PATCH 344/502] Translate 2.7 ( Duplicate of 1.9 ? ) --- readme-fr.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 7ecb4bec..63de077c 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -896,7 +896,7 @@ Il vise à fournir une sensibilisation, des frameworks et des outils pour tester
    -### :clap: Bien faire les choses, exemple: Le chaos-Node peut générer toute sortes de farces Node.js afin que tu puisses tester la résilience de votre application au chaos +### :clap: Bien faire les choses, exemple: Le chaos-Node peut générer toute sortes de farces 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") @@ -904,20 +904,20 @@ Il vise à fournir une sensibilisation, des frameworks et des outils pour tester
    -## ⚪ ️2.7 Avoid global test fixtures and seeds, add data per-test +## ⚪ ️2.7 Éviter les fixtures et seeds globals, ajouter les données par test -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +: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ée 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ée 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 compléxité 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ée en base de donnée 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).
    -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **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
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :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") @@ -942,7 +942,7 @@ it("When querying by site name, get the right site", async () => {
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :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 () => { From a4c237c1212e62a6122366b063ebd07f07aca369 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 26 Aug 2021 08:00:00 +0200 Subject: [PATCH 345/502] Translate 3.1 --- readme-fr.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 63de077c..32c9628c 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -959,23 +959,21 @@ it("When updating site name, get successful confirmation", async () => {

    -# Section 3️⃣: Frontend Testing +# Section 3️⃣: Tests Frontend -## ⚪ ️ 3.1 Separate UI from functionality - -:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI +## ⚪ ️ 3.1 Separer l'UI des fonctionnalités +:white_check_mark: **À faire:** Lorsqu'on veux tester la logique d'un composant, les détail UI deviennent du bruit qui devrait être extrait, afin que les tests se concentrent purement sur les données. En pratique, extrait les donnée désirés du markup d'un facon abstrait qui n'est pas torop complée avec l'iplémentation graphique, assert seulement les donnée (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 ils 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.
    -❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation - +❌ **Autrement:** Les donnée calculée de ton tests peuvent être prêtes en 10ms, mais l'ensemble du tests durera 500ms (100 tests = 1 min) à cause d'animation qui ne nous concerne pas dans le cadre du test.
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Separating out the UI details +### :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") @@ -996,7 +994,7 @@ test("When users-list is flagged to show only VIP, should display only VIP membe
    -### :thumbsdown: Anti-Pattern Example: Assertion mix UI details and data +### :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", () => { From 3897db32fc6ad3a2cc59679024a5ab4d2d650711 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 26 Aug 2021 08:09:39 +0200 Subject: [PATCH 346/502] Translate 3.2 --- readme-fr.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 32c9628c..9f9b61ca 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1013,21 +1013,20 @@ test("When flagging to show only VIP, should display only VIP members", () => {

    -## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change - -:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +## ⚪ ️ 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 grande chances de survivre à un changement graphique, contrairement aux selecteurs CSS ou aux labels des forms. Si l'élément n'as pas d'attribut de ce type, creer un attribut dédié au test comme 'test-id-submit-sutton'. Utiliser cette méthode permet non seulement d'être sur 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.
    -❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **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'
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Querying an element using a dedicated attribute for testing +### :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") @@ -1056,7 +1055,7 @@ test("Whenever no data is passed to metric, show 0 as default", () => {
    -### :thumbsdown: Anti-Pattern Example: Relying on CSS attributes +### :thumbsdown: Exemple d'anti pattern: Compter sur les attributs CSS ```html From 5185e079b7f399f800dfff74669e500696efde8f Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 26 Aug 2021 08:21:58 +0200 Subject: [PATCH 347/502] Translate 3.3 --- readme-fr.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 9f9b61ca..7c2a3174 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1076,23 +1076,24 @@ test("Whenever no data is passed, error metric shows zero", () => {
    -## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component +## ⚪ ️ 3.3 Lorsque c'est possible, test avec un composant réaliste et totalement rendu -:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +:white_check_mark: **Do:** Lorsqu'ils sont de taille raisonnable, tests tes composants de l'exterieur 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étéctés à cause du manque de détails et rendre plus difficile la maintenance des tests puisque les tests modifie les propiétés interne (voir le point 'Privilégier les tests blackbox'). Si l'un des composants enfants ralenti significativement (e.g animations) ou complique la configuration - considère de le remplacer explicitement avec un faux. -With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children +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 composants avec trop d'enfant 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.
    -❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? +❌ **Autrement:** Lorsque tu fouilles dans les détails interne du composants en invoquant ses méthodes privée, et en vérifiant l'état interne - tu devra refactorer tout les tests lorsque tu refactorera l'implémentation du composants. Est-ce que tu as vraiment la capacité de tenir ce niveau de maintenance ?
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Working realistically with a fully rendered component +### :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") @@ -1124,7 +1125,7 @@ test("Realistic approach: When clicked to show filters, filters are displayed", }); ``` -### :thumbsdown: Anti-Pattern Example: Mocking the reality with shallow rendering +### :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", () => { From 1a89d8a87b2afbd05112d788b968b35334027a1e Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 30 Aug 2021 12:54:57 +0200 Subject: [PATCH 348/502] Translate 3.4 --- readme-fr.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 7c2a3174..274ef3e3 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1149,20 +1149,22 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up +## ⚪ ️ 3.4 N'attend pas, utilise la gestion des évènements asynchrone implémenté dans les frameworks. Essayes aussi d'accélérer les choses -:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +:white_check_mark: **À faire:** Souvent, le temps de complétion de l'unité qu'on test est inconnu (e.g animation 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 attent, 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 ). A 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.
    -❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance +❌ **Autrement:** En attendant pour un long moment, les tests seront plus lent. En attendant trop peu, les tests échoueront si l'unité testé n'as pas répondu dans les temps. Cela se résume donc entre un compromis entre l'instabilité et les mauvaise performances.
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) +### :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") @@ -1174,7 +1176,7 @@ cy.wait("@products"); // wait for route to appear // this line will get executed only when the route is ready ``` -### :clap: Doing It Right Example: Testing library that waits for DOM elements +### :clap: Bien faire les choses, exemple: Librairie de tests qui attend les éléments du DOM ```javascript // @testing-library/dom @@ -1191,7 +1193,7 @@ test("movie title appears", async () => { }); ``` -### :thumbsdown: Anti-Pattern Example: custom sleep code +### :thumbsdown: Exemple d'anti pattern: Code custom qui attend ```javascript test("movie title appears", async () => { From 57bce8c850e2d894f423e368a8d1b6aab9501109 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 30 Aug 2021 13:07:14 +0200 Subject: [PATCH 349/502] Translate 3.5 --- readme-fr.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 274ef3e3..c1396020 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1217,21 +1217,20 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.5 Watch how the content is served over the network +## ⚪ ️ 3.5 Regarde comment le contenu est servi sur le réseau ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") -✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 - +✅ **À 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 basique 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 symptomes, les métriques qui affectent directement l'experience 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 cause 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 monitoring complet à la fois pendant le développement, dans le processus CI et surtout - 24h/24 7j/7 sur les serveurs/CDN de production
    -❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration +❌ **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.
    -
    Code Examples +
    Exemple de code -### :clap: Doing It Right Example: Lighthouse page load inspection report +### :clap: Bien faire les choses, exemple: Rapport d'inspection du temps de chargement avec Lighthouse ![](/assets/lighthouse2.png "Lighthouse page load inspection report") From fa0dbc0b465e1bd65313d2f1a4f43ae5613cdeeb Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 30 Aug 2021 13:22:49 +0200 Subject: [PATCH 350/502] Translate 3.6 --- readme-fr.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index c1396020..4709f876 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1238,22 +1238,19 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs - -:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +## ⚪ ️ 3.6 Stub les ressources lente ou incertaine comme l'API backend +:white_check_mark: **À faire:** Lorsque tu code tes tests mainstream ( pas les tests E2E ), évite d'impliquer toute ressources qui n'est pas sous ta responsabilité et sous ton controle 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é ou que l'API emet une erreur. Enfin et surtout, les appels réseau vont énormément ralentir les tests.
    -❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower - +❌ **Autrement:** Le test moyen ne tourne pas plus de quelques ms, un API call moyen dure environ 100ms. Çela rend les tests ~20x plus lent.
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Stubbing or intercepting API calls - +### :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 From d45845805677b9c826e7129b9797109525f0585b Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 30 Aug 2021 13:32:40 +0200 Subject: [PATCH 351/502] Translate 3.7 --- readme-fr.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 4709f876..72ed8a4b 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1289,14 +1289,13 @@ test("When no products exist, show the appropriate message", () => {
    -## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system +## ⚪ ️ 3.7 Avoir quelques tests end-to-end qui lancent le système entier -:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +:white_check_mark: **À faire:** Même si E2E (end-to-end) veux 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 ont 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.
    -❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected - +❌ **Autrement:** L'UI peut investir beaucoup en testant ces fonctionnalités seulement pour réaliser que les donnée retournée par le backend sont différentes de ce qui était attendu
    ## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials From 09c6d6bd9aff59a3437ee1b96acb69113880c363 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 30 Aug 2021 13:46:48 +0200 Subject: [PATCH 352/502] Translate 3.8 --- readme-fr.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 72ed8a4b..02e1581c 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1298,22 +1298,20 @@ test("When no products exist, show the appropriate message", () => { ❌ **Autrement:** L'UI peut investir beaucoup en testant ces fonctionnalités seulement pour réaliser que les donnée retournée par le backend sont différentes de ce qui était attendu
    -## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials - -:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see bullet 3.6). +## ⚪ ️ 3.8 Accélérer les tests E2E en réutilisants les informations d'authentification +:white_check_mark: **à faire:** Dans des tests E2E qui incluent un vrai backend et utilisent un token utilisteur 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. A la place, authentifie l'utilisateur une seule fois avant que l'execution des tests commencent (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 associers 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ête API avant chaque test peut mener a un temps d'execution horrible. Réutiliser les informations d'authentification ne veux pas dire que les tests doivent agir sur la même entrée utilisateur - si le test compte sur les entrée 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. Rappelles-toi aussi que le backend peut être simulé - Si les tests se concentrent sur le frontend, il vaux mieux les isoler et simuler l'API backend (voir point 3.6).
    -❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again +❌ **Autrement:** Si on prend 200 cas de tests et qu'on estime l'authentification à 100ms = 20 secondes simplement pour s'authentifier encore et encore
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Logging-in before-all and not before-each - +### :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 From b0c35f85b28a4b9b6e4326f3fe1a7de11657d6a1 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Tue, 31 Aug 2021 20:08:05 +0200 Subject: [PATCH 353/502] Translate 3.9 --- readme-fr.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 02e1581c..fc814112 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1344,21 +1344,21 @@ beforeEach(setUser => () {
    -## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map +## ⚪ ️ 3.9 Avoir un test E2E qui parcours juste les pages du site -:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector +: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 test fonctionnels, mais il sert à détecter rapidement les problèmes.
    -❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering +❌ **Autrement:** Tout peu sembler parfait, tout les tests passent, le health-check de production est également positif mais le composant de paiement a eu des erreurs de packaging et suel la route /payment ne s'affiche pas
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Smoke travelling across all pages +### :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") From dd61c74672f647802c6d7c4a16fa5687ce4d7c08 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Tue, 31 Aug 2021 20:18:56 +0200 Subject: [PATCH 354/502] Translate 3.10 --- readme-fr.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index fc814112..f28c681f 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1379,20 +1379,21 @@ it("When doing smoke testing over all page, should load them all successfully",
    -## ⚪ ️ 3.10 Expose the tests as a live collaborative document +## ⚪ ️ 3.10 Exposer les tests comme un document collaboratif -:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. +:white_check_mark: **À faire:** En plus d'améliorer la stabilité de l'application, les tests apportent une autre opportunitée intéressante - ils servent comment une documentation de l'app. +Puisque les tests parlent naturellement à un niveau moins technique avec un language 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 language 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 language 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 peux 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 -❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value +❌ **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
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js - +### :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 @@ -1412,8 +1413,7 @@ Feature: Twitter new tweet ``` -### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook - +### :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") From ce9c0323bd2efda191f2988fcd5dec2ca66eab28 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Tue, 31 Aug 2021 20:34:35 +0200 Subject: [PATCH 355/502] Translate 3.11 --- readme-fr.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index f28c681f..d2c3d894 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1422,27 +1422,27 @@ Feature: Twitter new tweet

    -## ⚪ ️ 3.11 Detect visual issues with automated tools +## ⚪ ️ 3.11 Détecter les problèmes visuels avec des outils automatisés -:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge significant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by eliminating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/CSS changes that led to the issue +: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 gratuit 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 coté, 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.
    -❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? +❌ **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é ?
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-Pattern Example: A typical visual regression - right content that is served badly +### :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: Doing It Right Example: Configuring wraith to capture and compare UI snapshots +### :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") @@ -1471,7 +1471,7 @@ paths: path: /subscribe ``` -### :clap: Doing It Right Example: Using Applitools to get snapshot comparison and other advanced features +### :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") From 13fcf8b600552247852ac38b140b5cd46e15ae84 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Tue, 31 Aug 2021 20:45:27 +0200 Subject: [PATCH 356/502] Translate 4.1 --- readme-fr.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index d2c3d894..f0751d63 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1498,33 +1498,33 @@ describe("visual validation", () => {

    -# Section 4️⃣: Measuring Test Effectiveness +# Section 4️⃣: Mesurer l'éfficacité des tests

    -## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number +## ⚪ ️ 4.1 Avoir assez de couverture pour être confiant, ~80% semble être le nombre magique -:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. +: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 le moindre idée de la validité du build, d'un autre coté 100% est vraiment couteux et peut dévier votre intêret des parties importantes pour des coints 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. -Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets +Conseil d'implémentation: Tu peux vouloir configurer ton intégration continu pour qu'elle ai 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 baisse 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
    -❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down +❌ **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
    -
    Code Examples +
    Exemple de code
    -### :clap: Example: A typical coverage report +### :clap: Example: Un rapport de couverture classique ![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report")
    -### :clap: Doing It Right Example: Setting up coverage per component (using Jest) +### :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") From aa4cf6e198b62b6d7e23c5960d72726998a6b2ab Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Wed, 1 Sep 2021 17:08:12 +0200 Subject: [PATCH 357/502] Translate 4.2 --- readme-fr.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index f0751d63..98f8756c 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1518,7 +1518,7 @@ Conseil d'implémentation: Tu peux vouloir configurer ton intégration continu p
    -### :clap: Example: Un rapport de couverture classique +### :clap: Exemple: Un rapport de couverture classique ![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") @@ -1534,22 +1534,22 @@ Conseil d'implémentation: Tu peux vouloir configurer ton intégration continu p

    -## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities +## ⚪ ️ 4.2 Inspecter les rapports de couverture pour détecter les sections qui ne sont pas testées et autres bizarreries -:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +:white_check_mark: **À faire:** Certains problèmes passent juste sous le radar et sont difficile à détecter en utilisants des outils traditionnels. Ce ne sont pas vraiment des beugs mais plutot 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ée et de nombreuses ventes ... Les rapports de couvertures t'aide à 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 à quel fréquence chaque partie du code est invoquée. Si tu prend ton temps pour regarder ces donnée, tu pourras trouver des pièges
    -❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from +❌ **Autrement:** Si tu ne sais pas quelles parties du code ne sont pas couvertes par les tests, tu ne sais pas d'ou peuvent venir les problèmes
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-Pattern Example: What’s wrong with this coverage report? +### :thumbsdown: Exemple d'anti pattern: Qu'est-ce qui ne va pas dans ce rapport de couverture ? -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) +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?") From f97438c87dcf4cb8c7609a820531476197b42ed4 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 2 Sep 2021 13:58:32 +0200 Subject: [PATCH 358/502] Translate 4.3 --- readme-fr.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 98f8756c..8c9a1b19 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1557,28 +1557,28 @@ Basé sur un scénario réel, où nous avont tracké l'usage de notre applicatio

    -## ⚪ ️ 4.3 Measure logical coverage using mutation testing +## ⚪ ️ 4.3 Mesurer la couverture logique en utilisant les tests de mutations -:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +: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 ligne de code que les tests ont visités, mais il ne vérifie pas si les tests ont effectivement testé quelque chose et vérifié la réponse. Comme quelqu'un qui effectuerai un voyage d'affaire 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 hotels. -Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: +Les tests de mutations sont la 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 teests de mutation et son implémentation est très soignée : -(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations +(1) Il change volontairement le code et "implante des beugs". Par exemple, le code newOrder.price === 0 devient newOrder.price != 0. Ces "beugs" sont appelés des mutations -(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. +(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 beugs, les mutations sont dites survivantes. Si les tests échouent, c'est bon, les mutations ont été tués. -Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +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.
    -❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code +❌ **Autrement:** Tu seras dupé en croyant que 85% de couverture de code signifie que tes tests détecteront les beugs dans 85% du code
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-Pattern Example: 100% coverage, 0% testing +### :thumbsdown: Exemple d'anti pattern: 100% de couverture, 0% testé ![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") @@ -1598,7 +1598,7 @@ it("Test addNewOrder, don't use such test names", () => {
    -### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :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)") From c0c8fd6fcda2dfaa68dbe4d8d54bfe7133f32bfe Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Thu, 2 Sep 2021 14:04:29 +0200 Subject: [PATCH 359/502] Translate 4.4 --- readme-fr.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 8c9a1b19..450c6bdb 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1606,20 +1606,19 @@ it("Test addNewOrder, don't use such test names", () => {

    -## ⚪ ️4.4 Preventing test code issues with Test linters - -:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +## ⚪ ️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'avertira 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'as pas d'assertion (ne vérifie rien)
    -❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation +❌ **Autrement:** Voir 90% de couverture de code et 100% de tests verts fera apparaitre 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
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-Pattern Example: A test case full of errors, luckily all are caught by Linters +### :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", () => { From ff271c059b869374fcefe7113ea01ac55ddefc50 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Fri, 3 Sep 2021 14:48:20 +0200 Subject: [PATCH 360/502] Translate 5.1 --- readme-fr.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 450c6bdb..186bc76a 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1638,24 +1638,25 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -# Section 5️⃣: CI and Other Quality Measures +# Section 5️⃣: CI et autres mesures de qualité

    -## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues +## ⚪ ️ 5.1 Enrichir ses linter et annuler les builds qui ont des problèmes de lint + +:white_check_mark: **À faire:** Les linters sont un repas 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 ou les linters était reservés a l'esthetique sont terminés (pas de point-virgules!). De nos jours, les linters peuvent détecté des problèmes serieux 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 coeur V8 comme Lodash.\_map(...) -:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…)
    -❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5 min linter setup could detect this TYPO and save your day +❌ **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 émit 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 configurations d'un linter pourrait permettre de détecter cette erreur et sauver la journée
    -
    Code Examples +
    Exemple de code
    -### :thumbsdown: Anti-Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug +### :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") From 8916c9ab1b87df0b23ce918b97bc5b7228054d53 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 6 Sep 2021 09:49:09 +0200 Subject: [PATCH 361/502] Translate 5.2 --- readme-fr.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 186bc76a..8c5cc34b 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1664,22 +1664,21 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

    -## ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI +## ⚪ ️ 5.2 Raccourcir la boucle de retours avec du CI local pour les développeurs -:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +: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) refactor. Plus le retour est rapide, plus le développeur peut effectuer d'itérations d'améliorations par modules et perfonctionner le résultat. D'un autre coté, lorsque les retours sont lent à arriver, moins d'améliorations peuvent être effectuée au sein d'une journée, l'équipe peut être déjà passé à un autre sujet/tache/module et peut ne pas être prête a affiner ce module. -Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +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 controle de qualité ne passe pas en utilisant githook ([husky can help](https://github.com/typicode/husky))
    -❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact - +❌ **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 plutot une étape formelle aprés coup
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new 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": { From 68215922ef890186ee77827616b0ea9862898b50 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 6 Sep 2021 10:01:17 +0200 Subject: [PATCH 362/502] Translate 5.3 --- readme-fr.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 8c5cc34b..021dd6a3 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1703,22 +1703,21 @@ En pratique, certains fournisseurs de CI (Exemple: [CircleCI local CLI](https://

    -## ⚪ ️5.3 Perform e2e testing over a true production-mirror +## ⚪ ️5.3 Effectue des tests e2e sur un vrai mirroir de production -:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of FaaS code. +:white_check_mark: **À faire:** Les tests end to end (E2E) sont le défi principal de chaque pipeline CI - créer un mirroir éphémère de la production à la volée avec toute les services clouds lié peut être fastidieux et couteux. 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 deploiement) est différents 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. -The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +Le large écosystème de Kubernetes doit encore formaliser un outils standard pour la mise en mirroir locale et CI, bien que de nombreux nouveaux outils soient lancés fréquemment. Une des approche 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 testé 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'executer des scripts custom sur le Kubernetes distant.
    -❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated - +❌ **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 dév et l'équipe ops.
    -
    Code Examples +
    Exemple de code
    -### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([Credit: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-kubernetes/)) +### :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
    From 9023c9989e9467e7490dd753921a5f6a6fe5081e Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 6 Sep 2021 10:08:41 +0200 Subject: [PATCH 363/502] Translate 5.4 --- readme-fr.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 021dd6a3..4d248047 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1725,19 +1725,18 @@ Le large écosystème de Kubernetes doit encore formaliser un outils standard po

    -## ⚪ ️5.4 Parallelize test execution +## ⚪ ️5.4 Paralléliser l'exécution des tests -:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes - -❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant +:white_check_mark: **À faire:** Lorsque c'est fait correctement, les tests sont ton ami 24/7 en fournissant un retour quasi instantanné. En pratique, éxécuter 500 tests unitaire liés au processeur sur un seul thread peut prendre trop longtemps. Heuresement, 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 locallement 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
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :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)") From 0345595930e5a2c8e79ceaa7ab2309c4ada1fe8e Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 6 Sep 2021 10:12:21 +0200 Subject: [PATCH 364/502] Translate 5.5 --- readme-fr.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 4d248047..0eefaa1c 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1744,19 +1744,18 @@ Le large écosystème de Kubernetes doit encore formaliser un outils standard po

    -## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check +## ⚪ ️5.5 Reste loin des problèmes légaux en utilisants des vérification de license et de plagiat -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stack Overflow and apparently violates some copyrights - -❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues +: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 packages avec une license inaproprié, ou copier/coller du code commercial et tomber sur des problèmes légaux
    -
    Code Examples +
    Exemple de code
    -### :clap: Doing It Right Example: +### :clap: Bien faire les choses, exemple: ```javascript //install license-checker in your CI environment or also locally From 3f26b0c00021a845fcb70baaffe545513cb66ee7 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 6 Sep 2021 10:23:02 +0200 Subject: [PATCH 365/502] Translate 5.6 --- readme-fr.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 0eefaa1c..358dcd6b 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1774,19 +1774,19 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.6 Constantly inspect for vulnerable dependencies +## ⚪ ️5.6 Inspecter constamment les dépendences vulnérables -:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +:white_check_mark: **À faire:** Même les dépendances les plus réputés 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é depuis ton CI à chaque build -❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious +❌ **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. Plutot fastidieux.
    -
    Code Examples +
    Exemple de code
    -### :clap: Example: NPM Audit result +### :clap: Exemple: Résultat d'audit Npm ![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") From 02a10cb9837a078f247d695d10f1af7a7fe39e8e Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 6 Sep 2021 10:44:58 +0200 Subject: [PATCH 366/502] Translate 5.7 --- readme-fr.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 358dcd6b..c7ad8605 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1794,26 +1794,26 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.7 Automate dependency updates +## ⚪ ️5.7 Automatiser les mises-à-jour de dépendences -:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: +: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 bonne intentions) - par défault maintenant, les packages ne sont pas mis à jour. Même une équipe qui lance plusieurs nouveaux déploiement avec 'npm install' & 'npm update' n'aura pas de nouvelles mise à jour. Cela conduit au mieux, à des dépendances à des packages de qualité infèrieur, 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 ai pas de solutions miracle, il y a deux possibilités d'automatisation: -(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. +(1) Le CI peut faire échouer les buils qui ont des dépendances obsolètes - en utilisants 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) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). +(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). -An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +Une politique de mise à jour efficace peut autoriser une 'pèriode d'acquisition' - laisser le code en retard par rapport à @latest pour quelques 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)
    -❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky +❌ **Autrement:** Ta production utilisera des packages qui ont été taggé explicitement par leur auteurs comme risquées
    -
    Code Examples +
    Exemple de code
    -### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions +### :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") @@ -1821,7 +1821,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    -## ⚪ ️ 5.8 Other, non-Node related, CI tips +## ⚪ ️ 5.8 Autre conseils CI, sans rapports avec Node :white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known From 76035d599f6c022540a866caa5cf0fe87aad5813 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 6 Sep 2021 10:55:41 +0200 Subject: [PATCH 367/502] Translate 5.8 --- readme-fr.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index c7ad8605..790c946a 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1823,12 +1823,12 @@ Une politique de mise à jour efficace peut autoriser une 'pèriode d'acquisitio ## ⚪ ️ 5.8 Autre conseils CI, sans rapports avec Node -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +: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. 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
    +
    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. Choisit un fournisseur qui à une intégration Docker native
    3. Échoue rapidement, lance les tests les plus rapide 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 rapide à 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 utilisants des étapes partagés (la plupart des fournisseurs ont des mécanisme pour réutiliser le code)
    6. Ne met 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 efféctue toute les inspections sur l'artefact de build (e.g. Docker image)
    9. Test dans un environnement ephémère qui ne change pas d'état entre les builds. Le cache des nodes peut être la seule exception

    -❌ **Otherwise:** You‘ll miss years of wisdom +❌ **Autrement:** Tu rateras des années de sagesse

    From 58a9d392d740621f99f85effc245f24afcd10e50 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 6 Sep 2021 11:03:44 +0200 Subject: [PATCH 368/502] Translate 5.9 --- readme-fr.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 790c946a..127ba1b5 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1832,20 +1832,20 @@ Une politique de mise à jour efficace peut autoriser une 'pèriode d'acquisitio

    -## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions +## ⚪ ️ 5.9 Structure de build: Lance les mêmes étapes CI sur plusieurs versions de Node -:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +:white_check_mark: **À faire:** Le controle 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 lance une production avec plusieurs clients qui ont différentes configurations et versions de Node, le CI doit lancer la pipeline de trests 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 toute 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 controles de qualités). D'autres CIs qui ne supportent pas Matrix peuvent avoir des extensions qui permettent ça
    -❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? +❌ **Autrement:** Aprés avoir fait tout le travail d'écrire des tests, vas-t-on laisser passer des bugs seulement à cause de problèmes de configurations ?
    -
    Code Examples +
    Exemple de code
    -### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +### :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
    From 494d02a4ace9958e1e4997a207bcf1457cda0a95 Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 6 Sep 2021 11:10:58 +0200 Subject: [PATCH 369/502] Translate team --- readme-fr.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 127ba1b5..347db426 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -1852,7 +1852,7 @@ Une politique de mise à jour efficace peut autoriser une 'pèriode d'acquisitio

    -# Team +# L'équipe ## Yoni Goldberg @@ -1860,15 +1860,15 @@ Une politique de mise à jour efficace peut autoriser une 'pèriode d'acquisitio
    -**Role:** Writer +**Rôle:** Auteur -**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) +**À propos:** Je suis un consultatn indépendant qui travaille avec des entreprises Fortune 500 et des startup pour peaufiner leurs applications JS et Node.JS. Plus qu'aucun autre sujet, je suis fasciné par et vise à maitriser l'art du test. Je suis aussi l'auteur de [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) +**📗 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)
    -**Follow:** +**Me suivre:** - [🐦 Twitter](https://twitter.com/goldbergyoni/) - [📞 Contact](https://testjavascript.com/contact-2/) @@ -1880,30 +1880,30 @@ Une politique de mise à jour efficace peut autoriser une 'pèriode d'acquisitio ## [Bruno Scheufler](https://github.com/BrunoScheufler) -**Role:** Tech reviewer and advisor +**Rôle:** Réviseur et conseiller technique -Took care to revise, improve, lint and polish all the texts +À pris soin de revoir, améliorer, linter et peaufiner tout les textes -**About:** full-stack web engineer, Node.js & GraphQL enthusiast +**À propos:** Ingénieur web full-stack, passioné par Node.js et GraphQL

    ## [Ido Richter](https://github.com/idori) -**Role:** Concept, design and great advice +**Rôle:** Concept, design et bons conseils -**About:** A savvy frontend developer, CSS expert and emojis freak +**À propos:** Un développeur front-end averti, expert CSS et maniaque des émojis ## [Kyle Martin](https://github.com/js-kyle) -**Role:** Helps keep this project running, and reviews security related practices +**Rôle:** Aide à faire tourner ce projet, et revois les pratiques liés à la sécurité -**About:** Loves working on Node.js projects and web application security. +**À propos:** Aime travailler sur des projets Node.JS et la sécurité des applications web. ## Contributors ✨ -Thanks goes to these wonderful people who have contributed to this repository! +Merci à ces merveilleuses personnes qui ont contribué à ce repo! From 976351fafa2ed742bd94a1ad8e8d8b6de16291ac Mon Sep 17 00:00:00 2001 From: mel-mouk Date: Mon, 6 Sep 2021 12:27:30 +0200 Subject: [PATCH 370/502] Global proofreading --- readme-fr.md | 274 +++++++++++++++++++++++++-------------------------- 1 file changed, 137 insertions(+), 137 deletions(-) diff --git a/readme-fr.md b/readme-fr.md index 347db426..fb8011e8 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -2,7 +2,7 @@
    -# 👇 Pourquoi ce guide peut faire passer vos compétences de test au niveau supérieur +# 👇 Comment ce guide peut faire passer vos compétences de test au niveau supérieur
    @@ -16,7 +16,7 @@ Embarque pour un voyage qui va bien au-delà des bases et aborde des sujets avan ## 🌐 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 ? +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 ?
    @@ -73,13 +73,13 @@ Lignes directrices pour l'intégration continue dans le monde du JS (9 points) ## ⚪️ 0 La règle d'or: Concevoir des tests minimalistes -:white_check_mark: **Do:** +: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 complexe 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). +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é. @@ -97,28 +97,28 @@ La plupart des conseils ci-dessous sont des dérivés de ce principe. ## ⚪ ️ 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 familier avec la base de code : le testeur, le dev ops qui deploie et toi dans 2 ans. Dans ce but, les noms des tests doivent expliciter les attentes et inclure 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 quel circonstance et scénario ? Par exemple, aucun prix n'est passé à la méthode +(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 deploiement a échoué, un test appelé "Add product" à échoué. Est-ce que celà indique exactement ce qui ne fonctionne plus correctement ? +❌ **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.
    -
    Code Examples +
    Exemple de code
    -### :clap: Bien faire les choses Exemple: Un nom de test constitué de 3 parties +### :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") @@ -138,7 +138,7 @@ describe('Products Service', function() {
    -### :clap: Bien faire les choses Exemple: Un nom de test constitué de 3 parties +### :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") @@ -153,9 +153,9 @@ describe('Products Service', function() { ## ⚪ ️ 1.2 Structurer les tests avec le pattern AAA -:white_check_mark: **À faire:** Structures tes tests avec 3 sections séparés: 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: +: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 autre codes de préparation +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 @@ -163,7 +163,7 @@ describe('Products Service', function() {
    -❌ **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. +❌ **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.
    @@ -172,7 +172,7 @@ describe('Products Service', function() {
    -### :clap: Bien faire les choses Exemple: Un test structuré avec le pattern AAA +### :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") @@ -211,7 +211,7 @@ test("Should be classified as 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 language 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 remplit de logique conditionnelles, le lecteur est forcé d'utiliser plus de cycles de "CPU" de cerveau. Dans ce cas, codes les attentes dans un language similaire au language 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écéssaires 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/) +: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/)
    @@ -253,7 +253,7 @@ test("When asking for an admin, ensure only ordered admins in results", () => {
    -### :clap: Bien faire les choses Exemple: Parcourir le test déclaratif suivant est un jeu d'enfant +### :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", () => { @@ -270,12 +270,12 @@ it("When asking for an admin, ensure only ordered admins in results", () => {

    -## ⚪ ️ 1.4 S'en tenir aux tests des boites noires: Ne teste que les méthodes publiques +## ⚪ ️ 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 resultats, 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'implementation 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é `behavioral testing` (test de comportement). De l'autre coté, 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. +: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é à été changé). Sans surprise, les gens vont rapidement ignorer les notifications, jusqu'à ce qu'un jour, un vrai beug soit ignoré +❌ **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 @@ -314,12 +314,12 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response", ## ⚪ ️ ️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écéssaire parce qu'il sont couplé 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)). +: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 apparait, ou peut apparaitre, dans le document de spécification ? Si non, ça sent le test de boite blanche. +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 apparaitre dans les spécification ("Envoyer un email si le paiement n'as pas pu être enregistré"). -D'un autre coté, si tu mock le service de paiement pour vérifié 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. +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. @@ -349,7 +349,7 @@ it("When a valid product is about to be deleted, ensure data access DAL was call
    -### :clap: Faire les choses bien, exemple : Les spies se concentrent sur les fonctionnalités requises mais touchent les composants internes par effet de bord +### :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 () => { @@ -371,12 +371,12 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

    -## ⚪ ️1.6 Utilise des données réalistes +## ⚪ ️1.6 Utiliser des données réalistes -:white_check_mark: **À faire:** Souvent les beugs de production sont révélés par des entrées tres spécifiques et surprenantes. Plus les entrées de tests seront réalistes, plus il y a de chance de détecter les beugs 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 resemble 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éphones, 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 vrais données depuis ton environnement de production. Envie de passer au niveau supérieur ? Regarde le prochain point (property-based testing). +: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 à tord 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” +❌ **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 @@ -423,12 +423,12 @@ it("Better: When adding new valid product, get successful confirmation", async (

    -## ⚪ ️ 1.7 Teste plusieurs combinaisons d'input avec le Property-based testing +## ⚪ ️ 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 'Utilise 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 toute les combinaisons d'entrées possible on augmente les chance de détecter un beug. Par exemple, prenom une méthode : addNewProduct(id, name, isDiscount), la librairie appelera 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. +: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. Malheuresement, cela réduit l'efficacité tests et leur capacité a détecter des beugs. +❌ **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 @@ -459,24 +459,24 @@ describe("Product service", () => {

    -## ⚪ ️ 1.8 Si besoin, n'utilise que des snapshots courts et inline +## ⚪ ️ 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. +: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 coté, 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 peux, 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 celà arrivera probablement souvent. A quelle frequence ? 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 success un long document qu'il ne pourra pas inspecter et vérifier. Tout ces points sont des symptomes d'un test obscure qui n'est pas ciblé et cherche à en faire trop. +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 valideer un schema et pas des données ou concernant des documents qui ne changent presque jamais +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 rends 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 à été ajouté dans le markdown... +❌ **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...
    -
    Exemples de code +
    Exemple de code
    -### :thumbsdown: Exemple d'anti pattern: Coupler nos test à 2000 lignes de code qu'on ne voit pas +### :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") @@ -528,8 +528,8 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => { ## ⚪ ️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ée 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ée 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 compléxité 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ée en base de donnée 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). +: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).
    @@ -585,9 +585,9 @@ it("When updating site name, get successful confirmation", async () => {
    -## ⚪ ️ 1.10 Ne catch pas les erreurs, prevois les +## ⚪ ️ 1.10 Ne pas catcher les erreurs, les prévoir -:white_check_mark: **À faire:** Lorsqu'on essaye de detecter 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. +: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.
    @@ -635,20 +635,20 @@ it("When no product name, it throws error 400", async () => {

    -## ⚪ ️ 1.11 Tag tes tests +## ⚪ ️ 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 peux 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, voila comment invoquer uniquement le groupe de test sanity avec Mocha : mocha - grep 'sanity' +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 tout les tests, y compris ceux qui éxecutent des dizaines de requetes DB, a chaque fois qu'un développeur fait un petit changement peut être extremement lent et pousser les développeurs a ne pas utiliser les tests. +❌ **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: Tagger 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) +### :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") @@ -668,13 +668,13 @@ describe("Order service", function() {

    -## ⚪ ️ 1.12 Catégorise tes tests sous au moins 2 niveaux +## ⚪ ️ 1.12 Catégoriser tes tests sous au moins 2 niveaux -:white_check_mark: **À faire:** Applique une structure à ta suite de test 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 deuxieme 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 voulu 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) +: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 majeur et comprendre les liens entre les tests qui échouent. Imagine le cas suivant : Quand 7/100 tests échouent, regarder une liste à plat nécéssitera d'aller lire le contenu des tests qui échouent pour comprendre le lien entre eux. En revanche, dans un rapport hiérarchique ils pourrait tous etre 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. +❌ **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.
    @@ -725,9 +725,9 @@ test("Then there should not be a new transfer record", () => {}); ## ⚪ ️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. +: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. -Apprend 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 valident le test - puis refactor graduellement et passe le code a un niveau de production, évite toute dépendance à l'environnement (paths, OS, etc) +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. @@ -738,11 +738,11 @@ Apprend et pratique [les principes TDD](https://www.sm-cloud.com/book-review-tes ## ⚪ ️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 model 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és 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 ? +: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és 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ènement 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 unitaire sur une application qui est centrée sur l'intégration et n'as 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. +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), de 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 sont 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. +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 :] @@ -756,7 +756,7 @@ Un mot d'avertissement: l'argument du TDD dans le monde du développement à un
    -### :clap: Bien faire les choses, exemple: Cindy Sridharan propose un portefeuille de tests riche dans ton excellent post 'Testing Microservices - the same way' +### :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’") @@ -770,11 +770,11 @@ Un mot d'avertissement: l'argument du TDD dans le monde du développement à un

    -## ⚪ ️2.2 Les tests de composant pourrait être ta meilleure affaire +## ⚪ ️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 couteux 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 unitaire mais plus petit 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 correct et réaliste +: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 concentre 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 fasant ç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. +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.
    @@ -796,9 +796,9 @@ Les tests de composant se concentre sur "l'unité" du microservice, ils fonction

    -## ⚪ ️2.3 Vérifie que les nouvelles versions ne cassent pas l'API avec les tests de contrat +## ⚪ ️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 champs et 'boom!', un client important qui compte sur ce champs est en colère. C'est le Catch-22 du monde de l'intégration : Il est trés difficilé pour le coté serveur de considérer toute les attentes des clients. D'un autre coté, les clients ne peuvent pas réaliser de tests puisque le serveur controles 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éfini ses propres plan de test, mais le client qui défini 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'éxé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. +: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 @@ -818,9 +818,9 @@ Les tests de composant se concentre sur "l'unité" du microservice, ils fonction

    -## ⚪ ️ 2.4 Test tes middlewares de manière isolée +## ⚪ ️ 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 systeme et requièrent un serveur express live. Ce sont deux mauvaises raisons - les Middlewares sont petit mais affectent toute ou la plupart des requetes et peuveunt ê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'object {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'object {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) +: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 @@ -857,15 +857,15 @@ test("A request without authentication header, should return http status 403", (

    -## ⚪ ️2.5 Mesure et refactorise en utilisant des outils d'analyse statique +## ⚪ ️2.5 Mesurer et refactoriser en utilisant des outils d'analyse statique -:white_check_mark: **À faire:** Utiliser des outils d'analye 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étécter des duplication), effectuer des analyses avancer (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)) +: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 beugs et la performance seront toujours un problème qu'aucune nouvelle librairie ou fonctionnalitée de pointe ne peux corriger +❌ **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
    @@ -883,10 +883,10 @@ Credit: ❌ **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 @@ -963,10 +963,10 @@ it("When updating site name, get successful confirmation", async () => { ## ⚪ ️ 3.1 Separer l'UI des fonctionnalités -:white_check_mark: **À faire:** Lorsqu'on veux tester la logique d'un composant, les détail UI deviennent du bruit qui devrait être extrait, afin que les tests se concentrent purement sur les données. En pratique, extrait les donnée désirés du markup d'un facon abstrait qui n'est pas torop complée avec l'iplémentation graphique, assert seulement les donnée (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 ils 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. +: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ée calculée de ton tests peuvent être prêtes en 10ms, mais l'ensemble du tests durera 500ms (100 tests = 1 min) à cause d'animation qui ne nous concerne pas dans le cadre du test. +❌ **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 @@ -1015,7 +1015,7 @@ test("When flagging to show only VIP, should display only VIP members", () => { ## ⚪ ️ 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 grande chances de survivre à un changement graphique, contrairement aux selecteurs CSS ou aux labels des forms. Si l'élément n'as pas d'attribut de ce type, creer un attribut dédié au test comme 'test-id-submit-sutton'. Utiliser cette méthode permet non seulement d'être sur 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. +: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' @@ -1076,16 +1076,16 @@ test("Whenever no data is passed, error metric shows zero", () => {
    -## ⚪ ️ 3.3 Lorsque c'est possible, test avec un composant réaliste et totalement rendu +## ⚪ ️ 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'exterieur 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étéctés à cause du manque de détails et rendre plus difficile la maintenance des tests puisque les tests modifie les propiétés interne (voir le point 'Privilégier les tests blackbox'). Si l'un des composants enfants ralenti significativement (e.g animations) ou complique la configuration - considère de le remplacer explicitement avec un faux. +: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 composants avec trop d'enfant 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. +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 interne du composants en invoquant ses méthodes privée, et en vérifiant l'état interne - tu devra refactorer tout les tests lorsque tu refactorera l'implémentation du composants. Est-ce que tu as vraiment la capacité de tenir ce niveau de maintenance ? +❌ **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 ?
    @@ -1149,14 +1149,14 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -## ⚪ ️ 3.4 N'attend pas, utilise la gestion des évènements asynchrone implémenté dans les frameworks. Essayes aussi d'accélérer les choses +## ⚪ ️ 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 animation 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 attent, 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 ). A 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. +: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é n'as pas répondu dans les temps. Cela se résume donc entre un compromis entre l'instabilité et les mauvaise performances. +❌ **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.
    @@ -1217,11 +1217,11 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.5 Regarde comment le contenu est servi sur le réseau +## ⚪ ️ 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 basique 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 symptomes, les métriques qui affectent directement l'experience 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 cause 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 monitoring complet à la fois pendant le développement, dans le processus CI et surtout - 24h/24 7j/7 sur les serveurs/CDN de production +✅ **À 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. @@ -1240,10 +1240,10 @@ test("movie title appears", async () => { ## ⚪ ️ 3.6 Stub les ressources lente ou incertaine comme l'API backend -:white_check_mark: **À faire:** Lorsque tu code tes tests mainstream ( pas les tests E2E ), évite d'impliquer toute ressources qui n'est pas sous ta responsabilité et sous ton controle 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é ou que l'API emet une erreur. Enfin et surtout, les appels réseau vont énormément ralentir les tests. +: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. Çela rend les tests ~20x plus lent. +❌ **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 @@ -1291,16 +1291,16 @@ test("When no products exist, show the appropriate message", () => { ## ⚪ ️ 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) veux 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 ont 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. +: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ée retournée par le backend sont différentes de ce qui était attendu +❌ **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éutilisants les informations d'authentification +## ⚪ ️ 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 utilisteur 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. A la place, authentifie l'utilisateur une seule fois avant que l'execution des tests commencent (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 associers 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ête API avant chaque test peut mener a un temps d'execution horrible. Réutiliser les informations d'authentification ne veux pas dire que les tests doivent agir sur la même entrée utilisateur - si le test compte sur les entrée 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. Rappelles-toi aussi que le backend peut être simulé - Si les tests se concentrent sur le frontend, il vaux mieux les isoler et simuler l'API backend (voir point 3.6). +: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 @@ -1346,11 +1346,11 @@ beforeEach(setUser => () { ## ⚪ ️ 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 test fonctionnels, mais il sert à détecter rapidement les problèmes. +: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 peu sembler parfait, tout les tests passent, le health-check de production est également positif mais le composant de paiement a eu des erreurs de packaging et suel la route /payment ne s'affiche pas +❌ **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
    @@ -1381,11 +1381,11 @@ it("When doing smoke testing over all page, should load them all successfully", ## ⚪ ️ 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ée intéressante - ils servent comment une documentation de l'app. -Puisque les tests parlent naturellement à un niveau moins technique avec un language 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 language 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 language 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 peux 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 +: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 +❌ **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
    @@ -1424,11 +1424,11 @@ Feature: Twitter new tweet ## ⚪ ️ 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 gratuit 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 coté, 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. +: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é ? +❌ **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 ?
    @@ -1498,15 +1498,15 @@ describe("visual validation", () => {

    -# Section 4️⃣: Mesurer l'éfficacité des tests +# 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 le moindre idée de la validité du build, d'un autre coté 100% est vraiment couteux et peut dévier votre intêret des parties importantes pour des coints 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. +: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 continu pour qu'elle ai 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 baisse 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 +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
    @@ -1536,10 +1536,10 @@ Conseil d'implémentation: Tu peux vouloir configurer ton intégration continu p ## ⚪ ️ 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 difficile à détecter en utilisants des outils traditionnels. Ce ne sont pas vraiment des beugs mais plutot 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ée et de nombreuses ventes ... Les rapports de couvertures t'aide à 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 à quel fréquence chaque partie du code est invoquée. Si tu prend ton temps pour regarder ces donnée, tu pourras trouver des pièges +: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'ou peuvent venir les problèmes +❌ **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
    @@ -1559,18 +1559,18 @@ Basé sur un scénario réel, où nous avont tracké l'usage de notre applicatio ## ⚪ ️ 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 ligne de code que les tests ont visités, mais il ne vérifie pas si les tests ont effectivement testé quelque chose et vérifié la réponse. Comme quelqu'un qui effectuerai un voyage d'affaire 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 hotels. +: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 la 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 teests de mutation et son implémentation est très soignée : +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 beugs". Par exemple, le code newOrder.price === 0 devient newOrder.price != 0. Ces "beugs" sont appelés des mutations +(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 beugs, les mutations sont dites survivantes. Si les tests échouent, c'est bon, les mutations ont été tués. +(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 beugs dans 85% du code +❌ **Autrement:** Tu seras dupé en croyant que 85% de couverture de code signifie que tes tests détecteront les bugs dans 85% du code
    @@ -1608,10 +1608,10 @@ it("Test addNewOrder, don't use such test names", () => { ## ⚪ ️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'avertira 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'as pas d'assertion (ne vérifie rien) +: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 apparaitre 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 +❌ **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 @@ -1644,11 +1644,11 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 5.1 Enrichir ses linter et annuler les builds qui ont des problèmes de lint -:white_check_mark: **À faire:** Les linters sont un repas 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 ou les linters était reservés a l'esthetique sont terminés (pas de point-virgules!). De nos jours, les linters peuvent détecté des problèmes serieux 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 coeur V8 comme Lodash.\_map(...) +: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 émit 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 configurations d'un linter pourrait permettre de détecter cette erreur et sauver la journée +❌ **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
    @@ -1666,12 +1666,12 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ## ⚪ ️ 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) refactor. Plus le retour est rapide, plus le développeur peut effectuer d'itérations d'améliorations par modules et perfonctionner le résultat. D'un autre coté, lorsque les retours sont lent à arriver, moins d'améliorations peuvent être effectuée au sein d'une journée, l'équipe peut être déjà passé à un autre sujet/tache/module et peut ne pas être prête a affiner ce module. +: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 controle de qualité ne passe pas en utilisant githook ([husky can help](https://github.com/typicode/husky)) +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 plutot une étape formelle aprés coup +❌ **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 @@ -1703,14 +1703,14 @@ En pratique, certains fournisseurs de CI (Exemple: [CircleCI local CLI](https://

    -## ⚪ ️5.3 Effectue des tests e2e sur un vrai mirroir de production +## ⚪ ️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 mirroir éphémère de la production à la volée avec toute les services clouds lié peut être fastidieux et couteux. 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 deploiement) est différents 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. +: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 outils standard pour la mise en mirroir locale et CI, bien que de nombreux nouveaux outils soient lancés fréquemment. Une des approche 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 testé 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'executer des scripts custom sur le Kubernetes distant. +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 dév et l'équipe ops. +❌ **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 @@ -1727,9 +1727,9 @@ Le large écosystème de Kubernetes doit encore formaliser un outils standard po ## ⚪ ️5.4 Paralléliser l'exécution des tests -:white_check_mark: **À faire:** Lorsque c'est fait correctement, les tests sont ton ami 24/7 en fournissant un retour quasi instantanné. En pratique, éxécuter 500 tests unitaire liés au processeur sur un seul thread peut prendre trop longtemps. Heuresement, 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 locallement 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. +: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 +❌ **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 @@ -1744,11 +1744,11 @@ Le large écosystème de Kubernetes doit encore formaliser un outils standard po

    -## ⚪ ️5.5 Reste loin des problèmes légaux en utilisants des vérification de license et de plagiat +## ⚪ ️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 packages avec une license inaproprié, ou copier/coller du code commercial et tomber sur des problèmes légaux +❌ **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 @@ -1776,9 +1776,9 @@ license-checker --summary --failOn BSD ## ⚪ ️5.6 Inspecter constamment les dépendences vulnérables -:white_check_mark: **À faire:** Même les dépendances les plus réputés 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é depuis ton CI à chaque build +: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. Plutot fastidieux. +❌ **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.
    @@ -1796,16 +1796,16 @@ license-checker --summary --failOn BSD ## ⚪ ️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 bonne intentions) - par défault maintenant, les packages ne sont pas mis à jour. Même une équipe qui lance plusieurs nouveaux déploiement avec 'npm install' & 'npm update' n'aura pas de nouvelles mise à jour. Cela conduit au mieux, à des dépendances à des packages de qualité infèrieur, 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 ai pas de solutions miracle, il y a deux possibilités d'automatisation: +: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 buils qui ont des dépendances obsolètes - en utilisants 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. +(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). +(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 quelques 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) +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 leur auteurs comme risquées +❌ **Autrement:** Ta production utilisera des packages qui ont été taggé explicitement par leurs auteurs comme risquées
    @@ -1821,23 +1821,23 @@ Une politique de mise à jour efficace peut autoriser une 'pèriode d'acquisitio

    -## ⚪ ️ 5.8 Autre conseils CI, sans rapports avec Node +## ⚪ ️ 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. Choisit un fournisseur qui à une intégration Docker native
    3. Échoue rapidement, lance les tests les plus rapide 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 rapide à 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 utilisants des étapes partagés (la plupart des fournisseurs ont des mécanisme pour réutiliser le code)
    6. Ne met 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 efféctue toute les inspections sur l'artefact de build (e.g. Docker image)
    9. Test dans un environnement ephémère qui ne change pas d'état entre les builds. Le cache des nodes peut être la seule exception
    +
    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: Lance les mêmes étapes CI sur plusieurs versions de Node +## ⚪ ️ 5.9 Structure de build: Lancer les mêmes étapes CI sur plusieurs versions de Node -:white_check_mark: **À faire:** Le controle 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 lance une production avec plusieurs clients qui ont différentes configurations et versions de Node, le CI doit lancer la pipeline de trests 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 toute 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 controles de qualités). D'autres CIs qui ne supportent pas Matrix peuvent avoir des extensions qui permettent ça +: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, vas-t-on laisser passer des bugs seulement à cause de problèmes de configurations ? +❌ **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 ?
    @@ -1862,7 +1862,7 @@ Une politique de mise à jour efficace peut autoriser une 'pèriode d'acquisitio **Rôle:** Auteur -**À propos:** Je suis un consultatn indépendant qui travaille avec des entreprises Fortune 500 et des startup pour peaufiner leurs applications JS et Node.JS. Plus qu'aucun autre sujet, je suis fasciné par et vise à maitriser l'art du test. Je suis aussi l'auteur de [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices) +**À 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) @@ -1882,7 +1882,7 @@ Une politique de mise à jour efficace peut autoriser une 'pèriode d'acquisitio **Rôle:** Réviseur et conseiller technique -À pris soin de revoir, améliorer, linter et peaufiner tout les textes +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 From 08f3899ef1e501afdd37a5d7cc975d22427b4f59 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Wed, 22 Sep 2021 09:56:55 +0300 Subject: [PATCH 371/502] Update readme.md --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index 30f800c2..c7321516 100644 --- a/readme.md +++ b/readme.md @@ -26,6 +26,9 @@ Start by understanding the ubiquitous testing practices that are the foundation - 📗 [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/) + +👨‍🏫 [Nodeconf 2021](https://www.nodeconfremote.com/#workshops) is just around the corner, and will **host a 5 hours testing workshop with Yoni Goldberg**. [Register here](https://www.nodeconfremote.com/#workshops) +
    ### Translations - read in your own language From cbef97a0be8ec7db042ed3e42cec63680a7071e7 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 21 Nov 2021 16:35:57 +0200 Subject: [PATCH 372/502] Update readme.md --- readme.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/readme.md b/readme.md index 843e5d7e..1e7990b3 100644 --- a/readme.md +++ b/readme.md @@ -26,9 +26,6 @@ Start by understanding the ubiquitous testing practices that are the foundation - 📗 [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/) - -👨‍🏫 [Nodeconf 2021](https://www.nodeconfremote.com/#workshops) is just around the corner, and will **host a 5 hours testing workshop with Yoni Goldberg**. [Register here](https://www.nodeconfremote.com/#workshops) -
    ### Translations - read in your own language From 993c63cc9535c8f0e481e87fe98dfc9d6b60d51a Mon Sep 17 00:00:00 2001 From: yubintw Date: Mon, 22 Nov 2021 03:00:50 +0800 Subject: [PATCH 373/502] Translate the introduction --- readme-zh-TW.md | 1981 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1981 insertions(+) create mode 100644 readme-zh-TW.md diff --git a/readme-zh-TW.md b/readme-zh-TW.md new file mode 100644 index 00000000..10a70528 --- /dev/null +++ b/readme-zh-TW.md @@ -0,0 +1,1981 @@ + + +
    + +# 👇 為什麼本指南可以幫助你將測試能力提升到下一個等級 + +
    + +## 📗 46+ 個最佳實踐:非常全面且徹底 + +這是從 A 到 Z 的 JavaScript 及 Node.js 可靠的指南。它為你總結及規劃了市場上大量的部落格文章、書籍及工具。 + +## 🚢 進階:從基礎向前邁進 10,000 英里 Advanced: Goes 10,000 miles beyond the basics + +從基礎往前邁進的旅程,包括:在生產(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) +- Want to translate to your own language? please open an issue 💜 + +

    + +## `目錄` + +#### [`第 0 章:黃金原則`](#section-0️⃣-the-golden-rule) + +一個激發所有人的建議 (1個特殊項目) + +#### [`第 1 章:測試剖析`](#section-1-the-test-anatomy-1) + +基礎 - 建立乾淨的測試 (12項) + +#### [`第 2 章:後端`](#section-2️⃣-backend-testing) + +有效率地撰寫後端及微服務的測試 (8項) + +#### [`第 3 章:前端`](#section-3️⃣-frontend-testing) + +為網頁 UI (包括組件及E2E) 撰寫測試 (11項) + +#### [`第 4 章:測量測試的有效程度`](#section-4️⃣-measuring-test-effectiveness) + +測量測試的品質 (4項) + +#### [`第 5 章:持續整合 (Continuous Integration)`](#section-5️⃣-ci-and-other-quality-measures) + +JavaScript 世界的 CI 指南 (9項) + +

    + +# 第 0 章:黃金原則 + +
    + +## ⚪️ 0 The Golden Rule: Design for lean testing + +:white_check_mark: **Do:** +Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. + +Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. + +The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). + +This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. + +![alt text](/assets/headspace.png "We have no head room for additional complexity") + +Most of the advice below are derivatives of this principle. + +### Ready to start? + +

    + +# Section 1: The Test Anatomy + +
    + +## ⚪ ️ 1.1 Include 3 parts in each test name + +:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: + +(1) What is being tested? For example, the ProductsService.addNewProduct method + +(2) Under what circumstances and scenario? For example, no price is passed to the method + +(3) What is the expected result? For example, the new product is not approved + +
    + +❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? + +
    + +**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: A test name that constitutes 3 parts + +![](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: Doing It Right Example: A test name that constitutes 3 parts + +![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 Structure tests by the AAA pattern + +:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: + +1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code + +2nd A - Act: Execute the unit under test. Usually 1 line of code + +3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code + +
    + +❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: A test structured with the AAA pattern + +![](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: Anti-Pattern Example: No separation, one bulk, harder to interpret + +```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 Describe expectations in a product language: use BDD-style assertions + +:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/) +
    + +❌ **Otherwise:** The team will write less tests and decorate the annoying ones with .skip() + +
    + +
    Code Examples
    + +![](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: Anti-Pattern Example: The reader must skim through not so short, and imperative code just to get the test story + +```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: Doing It Right Example: Skimming through the following declarative test is a breeze + +```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 Stick to black-box testing: Test only public methods + +:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden +
    + +❌ **Otherwise:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored… + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A test case is testing the internals for no good reason + +![](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 Choose the right test doubles: Avoid mocks in favor of stubs and spies + +:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value (
    [Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). + +Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. + +For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that have nothing to do with the application functionality and are likely to change frequently +
    + +❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-pattern example: Mocks focus on the internals + +![](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:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals + +```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; +}); +``` + +
    + +

    + +## 📗 Want to learn all these practices with live video? + +### Visit my online course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) + +

    + +## ⚪ ️1.6 Don’t “foo”, use realistic input data + +:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +
    + +❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data + +![](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:Doing It Right Example: Randomizing realistic input + +```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 Test many input combinations using Property-based testing + +:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet [‘Don’t foo’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +
    + +❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Testing many input permutations with “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 If needed, use only short & inline snapshots + +:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. + +On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much + +It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +
    + +❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code + +![](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: Doing It Right Example: Expectations are visible and focused + +```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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](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: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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 Don’t catch errors, expect them + +:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations + +A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +
    + +❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with 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: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical 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 Tag your tests + +:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +
    + +❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) + +![](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 Categorize tests under at least 2 levels + +:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and print screen below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) + +
    + +❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in a hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below + +![](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: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests + +![](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 Other generic good testing hygiene + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satisfies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +
    + +❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades + +

    + +# Section 2️⃣: Backend Testing + +## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid + +:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? + +Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IoT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. + +It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks + +A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] + +
    + +❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing 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 Component testing might be your best affair + +:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. + +Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +
    + +❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) + +![](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 Ensure new releases don’t break the API using contract tests + +:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +
    + +❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: + +![](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 Test your middlewares in isolation + +:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +
    + +❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests + +
    + +
    Code Examples + +
    + +### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine + +![](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 Measure and refactor using static analysis tools + +:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [SonarQube](https://www.sonarqube.org/) (4,900+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (2,000+ [stars](https://github.com/codeclimate/codeclimate)) + +Credit:
    [Keith Holliday](https://github.com/TheHollidayInn) + +
    + +❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: + +![](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: **Do:** Weirdly, most software testings are about logic & data only, but some of the worst things that happen (and are really hard to mitigate) are infrastructural issues. For example, did you ever test what happens when your process memory is overloaded, or when the server/process dies, or does your monitoring system realizes when the API becomes 50% slower?. To test and mitigate these type of bad things — [Chaos engineering](https://principlesofchaos.org/) was born by Netflix. It aims to provide awareness, frameworks and tools for testing our app resiliency for chaotic issues. For example, one of its famous tools, [the chaos monkey](https://github.com/Netflix/chaosmonkey), randomly kills servers to ensure that our service can still serve users and not relying on a single server (there is also a Kubernetes version, [kube-monkey](https://github.com/asobti/kube-monkey), that kills pods). All these tools work on the hosting/platform level, but what if you wish to test and generate pure Node chaos like check how your Node process copes with uncaught errors, unhandled promise rejection, v8 memory overloaded with the max allowed of 1.7GB or whether your UX remains satisfactory when the event loop gets blocked often? to address this I’ve written, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) which provides all sort of Node-related chaotic acts +
    + +❌ **Otherwise:** No escape here, Murphy’s law will hit your production without mercy + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: : Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to 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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + +❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](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: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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️⃣: Frontend Testing + +## ⚪ ️ 3.1 Separate UI from functionality + +:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI + +
    + +❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Separating out the UI details + +![](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: Anti-Pattern Example: Assertion mix UI details and data + +```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 HTML elements based on attributes that are unlikely to change + +:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed + +
    + +❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Querying an element using a dedicated attribute for testing + +![](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: Anti-Pattern Example: Relying on 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 Whenever possible, test with a realistic and fully rendered component + +:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet ['Favour blackbox testing'](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-14-stick-to-black-box-testing-test-only-public-methods)). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake + +With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children + +
    + +❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Working realistically with a fully rendered component + +![](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: Anti-Pattern Example: Mocking the reality with 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 Don't sleep, use frameworks built-in support for async events. Also try to speed things up + +:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +
    + +❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (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: Doing It Right Example: Testing library that waits for DOM elements + +```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: Anti-Pattern Example: custom sleep code + +```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 Watch how the content is served over the network + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") + +✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 + +
    + +❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration + +
    + +
    Code Examples + +### :clap: Doing It Right Example: Lighthouse page load inspection report + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + +
    + +
    + +## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs + +:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests + +
    + +❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower + +
    + +
    Code Examples + +
    + +### :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 +// 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 Have very few end-to-end tests that spans the whole system + +:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment + +
    + +❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected + +
    + +## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials + +:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)). + +
    + +❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Logging-in before-all and not 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 Have one E2E smoke test that just travels across the site map + +:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector + +
    + +❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Smoke travelling across all 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 Expose the tests as a live collaborative document + +:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. + +❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Describing tests in human-language using 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: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook + +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 Detect visual issues with automated tools + +:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge significant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by eliminating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/CSS changes that led to the issue + +
    + +❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A typical visual regression - right content that is served badly + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + +### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots + +![](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: Doing It Right Example: Using Applitools to get snapshot comparison and other advanced features + +![](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️⃣: Measuring Test Effectiveness + +

    + +## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number + +:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. + +Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets + +
    + +❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down + +
    + +
    Code Examples + +
    + +### :clap: Example: A typical coverage report + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: Doing It Right Example: Setting up coverage per component (using 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 Inspect coverage reports to detect untested areas and other oddities + +:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +
    + +❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: 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?") + +
    + +

    + +## ⚪ ️ 4.3 Measure logical coverage using mutation testing + +:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. + +Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: + +(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations + +(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. + +Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +
    + +❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: 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: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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 Preventing test code issues with Test linters + +:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) + +
    + +❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A test case full of errors, luckily all are caught by 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 and Other Quality Measures + +

    + +## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues + +:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +
    + +❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5 min linter setup could detect this TYPO and save your day + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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 Shorten the feedback loop with local developer-CI + +:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. + +Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +
    + +❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new 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 Perform e2e testing over a true production-mirror + +:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of FaaS code. + +The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +
    + +❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated + +
    + +
    Code Examples + +
    + +### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([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 Parallelize test execution + +:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes + +❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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 Stay away from legal issues using license and plagiarism check + +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stack Overflow and apparently violates some copyrights + +❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: + +```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 Constantly inspect for vulnerable dependencies + +:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build + +❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious + +
    + +
    Code Examples + +
    + +### :clap: Example: NPM Audit result + +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + +

    + +## ⚪ ️5.7 Automate dependency updates + +:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: + +(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. + +(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). + +An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +
    + +❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky + +
    + +
    Code Examples + +
    + +### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest 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 Other, non-Node related, CI tips + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +
    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
    +
    + +❌ **Otherwise:** You‘ll miss years of wisdom + +

    + +## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions + +:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +
    + +❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? + +
    + +
    Code Examples + +
    + +### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions + +
    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. + +## 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

    🖋
    + + + + + From d1e81eeb9e340788ddc9d6d2e079db836daa06c5 Mon Sep 17 00:00:00 2001 From: yubintw Date: Sat, 27 Nov 2021 22:24:35 +0800 Subject: [PATCH 374/502] Translate Section 0 --- readme-zh-TW.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 10a70528..450db107 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -74,22 +74,22 @@ JavaScript 世界的 CI 指南 (9項)
    -## ⚪️ 0 The Golden Rule: Design for lean testing +## ⚪️ 0 黃金原則:Design for lean testing -:white_check_mark: **Do:** -Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. +:white_check_mark: **建議:** +測試程式與生產環境的程式不同,要把他設計的極其簡單、簡短、具體、扁平、使人愉悅的去使用及學習。一段測試程式應該要可以讓人一眼就看懂其目的。 -Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. +我們的思考空間被主要的程式邏輯所占滿,並沒有額外的腦容量去處理複雜的東西。如果把其他複雜的程式塞進我們可憐的大腦,將會使得整個團隊的運作變慢,而這個部分正是用來解決我們需要測試的問題。這也是大部分團隊放棄測試的原因。 -The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). +另一方面,測試是一個友好的助手,一個讓你樂意與他合作、投資小但回報大的助手。科學證明我們有兩套大腦系統:系統 1 用於無須努力的活動,如在空曠的路上開車;系統 2 用於複雜和繁瑣的工作,如計算一道數學式。把你的測試程式設計成如系統 1 一般,當你看著你的測試,要像修改 HTML 文件一樣的簡單,而不是像計算 2 x (17 x 24)。 -This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. +為了達到這個目標,我們可以選擇具有成本效益和高投資報酬率的的技術、工具和測試目標。只測試需要的內容,努力保持他的靈活性,某些時候甚至得捨棄一些測試來換取靈活性和簡潔性。 ![alt text](/assets/headspace.png "We have no head room for additional complexity") -Most of the advice below are derivatives of this principle. +以下大部分的建議衍生自這一原則。 -### Ready to start? +### 準備好了嗎?

    From e28ea8378fb966016b9a28fb08e129443e27428c Mon Sep 17 00:00:00 2001 From: yubintw Date: Sat, 27 Nov 2021 22:29:26 +0800 Subject: [PATCH 375/502] Improve the terms --- readme-zh-TW.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 450db107..568faf1d 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -77,11 +77,11 @@ JavaScript 世界的 CI 指南 (9項) ## ⚪️ 0 黃金原則:Design for lean testing :white_check_mark: **建議:** -測試程式與生產環境的程式不同,要把他設計的極其簡單、簡短、具體、扁平、使人愉悅的去使用及學習。一段測試程式應該要可以讓人一眼就看懂其目的。 +測試程式與主要生產環境的程式不同,要把他設計的極其簡單、簡短、具體、扁平、使人愉悅的去使用及學習。一段測試程式應該要可以讓人一眼就看懂其目的。 -我們的思考空間被主要的程式邏輯所占滿,並沒有額外的腦容量去處理複雜的東西。如果把其他複雜的程式塞進我們可憐的大腦,將會使得整個團隊的運作變慢,而這個部分正是用來解決我們需要測試的問題。這也是大部分團隊放棄測試的原因。 +我們的思考空間被主要的程式邏輯所占滿,並沒有額外的腦容量去處理複雜的東西。如果把其他複雜的程式塞進我們可憐的大腦,將會使得整個團隊的運作變慢,而這些複雜的程式正是用來解決我們需要測試的問題。這也是許多團隊放棄測試的原因。 -另一方面,測試是一個友好的助手,一個讓你樂意與他合作、投資小但回報大的助手。科學證明我們有兩套大腦系統:系統 1 用於無須努力的活動,如在空曠的路上開車;系統 2 用於複雜和繁瑣的工作,如計算一道數學式。把你的測試程式設計成如系統 1 一般,當你看著你的測試,要像修改 HTML 文件一樣的簡單,而不是像計算 2 x (17 x 24)。 +另一方面,測試是一個友好的助手,一個讓你樂意與他合作、投資小且回報大的助手。科學證明我們有兩套大腦系統:系統 1 用於無須努力的活動,如在空曠的路上開車;系統 2 用於複雜和繁瑣的工作,如計算一道數學式。把你的測試程式設計成如系統 1 一般,當你看著你的測試,要像修改 HTML 文件一樣的簡單,而不是像計算 2 x (17 x 24)。 為了達到這個目標,我們可以選擇具有成本效益和高投資報酬率的的技術、工具和測試目標。只測試需要的內容,努力保持他的靈活性,某些時候甚至得捨棄一些測試來換取靈活性和簡潔性。 From f8009d219594b5c3116ea0514e3b3f1f58a5cdcb Mon Sep 17 00:00:00 2001 From: yubintw Date: Wed, 1 Dec 2021 23:51:59 +0800 Subject: [PATCH 376/502] Translate Section 1.1 ~ 1.3 --- readme-zh-TW.md | 66 ++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 568faf1d..590b835f 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -97,50 +97,49 @@ JavaScript 世界的 CI 指南 (9項)
    -## ⚪ ️ 1.1 Include 3 parts in each test name +## ⚪ ️ 1.1 每個測試的名稱要包含的三個部分 -:white_check_mark: **Do:** A test report should tell whether the current application revision satisfies the requirements for the people who are not necessarily familiar with the code: the tester, the DevOps engineer who is deploying and the future you two years from now. This can be achieved best if the tests speak at the requirements level and include 3 parts: +:white_check_mark: **建議:** 一份測試報告應該告訴那些不一定熟悉程式的人,目前應用程式的修訂版本是否符合他們的要求,包括:測試人員、DevOps 工程師和兩年後的你。如果測試能包含這三個需求面的描述,就能很好的實現這一點: -(1) What is being tested? For example, the ProductsService.addNewProduct method +(1) 測試的對象是什麼? 例如,ProductsService.addNewProduct 這個方法。 -(2) Under what circumstances and scenario? For example, no price is passed to the method +(2) 在什麼情況或場景下? 例如,價格沒有傳給該方法。 -(3) What is the expected result? For example, the new product is not approved +(3) 預期的結果是什麼? 例如,新的產品沒有被批准。
    -❌ **Otherwise:** A deployment just failed, a test named “Add product” failed. Does this tell you what exactly is malfunctioning? +❌ **否則:** 一個名叫"新增產品"的測試失敗了。這有確切地告訴你到底是什麼地方出問題嗎?
    -**👇 Note:** Each bullet has code examples and sometime also an image illustration. Click to expand +**👇 Note:** 每個項目都會有一個程式範例,有時候還會搭配圖片。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: A test name that constitutes 3 parts +### :clap: 正例:一個包含這三部分的測試名稱 ![](https://img.shields.io/badge/🔨%20Example%20using%20Mocha-blue.svg "Using Mocha to illustrate the idea") ```javascript -//1. unit under test +// 1. unit under test describe('Products Service', function() { describe('Add new product', function() { - //2. scenario and 3. expectation + // 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: Doing It Right Example: A test name that constitutes 3 parts +### :clap: 正例:一個包含這三部分的測試名稱 ![alt text](/assets/bp-1-3-parts.jpeg "A test name that constitutes 3 parts") @@ -153,41 +152,41 @@ describe('Products Service', function() {

    -## ⚪ ️ 1.2 Structure tests by the AAA pattern +## ⚪ ️ 1.2 以 AAA 模式來建構測試 -:white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: +:white_check_mark: **建議:** 用三個部分來組織你的測試:Arrange 安排、Act 執行、Assert 斷言 (AAA)。依照這個結構,可以確保讀者不用花費腦力去理解你的測試。 -1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code +第一個 A - Arrange 安排:所有使系統達到測試所要模擬的情境的程式。這可能包含實體化某個待測單元的建構子、新增 DB 的資料、mocking/stubbing 物件和其他準備程式。 -2nd A - Act: Execute the unit under test. Usually 1 line of code +第二個 A - Act 執行:執行測試單元。通常為一行程式。 -3rd A - Assert: Ensure that the received value satisfies the expectation. Usually 1 line of code +第三個 A - Assert 斷言:確保得到的值符合期待。通常為一行程式。
    -❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain +❌ **否則:** 你不僅需要花很多時間去理解主要程式,而且本應是最簡單的部分 - 測試,也會讓你腦力耗盡。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: A test structured with the AAA pattern +### :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 + // Arrange const customerToClassify = { spent: 505, joined: new Date(), id: 1 }; const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" }); - //Act + // Act const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); - //Assert + // Assert expect(receivedClassification).toMatch("premium"); }); }); @@ -195,7 +194,7 @@ describe("Customer classifier", () => {
    -### :thumbsdown: Anti-Pattern Example: No separation, one bulk, harder to interpret +### :thumbsdown: 反例:沒有分隔、一大坨、難以理解 ```javascript test("Should be classified as premium", () => { @@ -210,24 +209,25 @@ test("Should be classified as premium", () => {

    -## ⚪ ️1.3 Describe expectations in a product language: use BDD-style assertions +## ⚪ ️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/)。 -:white_check_mark: **Do:** Coding your tests in a declarative-style allows the reader to get the grab instantly without spending even a single brain-CPU cycle. When you write imperative code that is packed with conditional logic, the reader is forced to exert more brain-CPU cycles. In that case, code the expectation in a human-like language, declarative BDD style using `expect` or `should` and not using custom code. If Chai & Jest doesn't include the desired assertion and it’s highly repeatable, consider [extending Jest matcher (Jest)](https://jestjs.io/docs/en/expect#expectextendmatchers) or writing a [custom Chai plugin](https://www.chaijs.com/guide/plugins/)
    -❌ **Otherwise:** The team will write less tests and decorate the annoying ones with .skip() +❌ **否則:** 團隊的測試會越寫越少,且會用 .skip() 把討厭的測試略過。
    -
    Code Examples
    +
    程式範例
    ![](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: Anti-Pattern Example: The reader must skim through not so short, and imperative code just to get the test story +### :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" + // assuming we've added here two admins "admin1", "admin2" and "user1" const allAdmins = getUsers({ adminOnly: true }); let admin1Found, @@ -253,11 +253,11 @@ test("When asking for an admin, ensure only ordered admins in results", () => {
    -### :clap: Doing It Right Example: Skimming through the following declarative test is a breeze +### :clap: 正例:快速瀏覽以下的聲明式測試非常輕鬆 ```javascript it("When asking for an admin, ensure only ordered admins in results", () => { - //assuming we've added here two admins + // assuming we've added here two admins const allAdmins = getUsers({ adminOnly: true }); expect(allAdmins) From 726cd4bb9ec46304e8f09f046075a9ba54b50710 Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 2 Dec 2021 22:04:36 +0800 Subject: [PATCH 377/502] Translate Section 1.4 ~ 1.6 --- readme-zh-TW.md | 77 ++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 590b835f..ad5be727 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -270,31 +270,31 @@ it("When asking for an admin, ensure only ordered admins in results", () => {

    -## ⚪ ️ 1.4 Stick to black-box testing: Test only public methods +## ⚪ ️ 1.4 堅持黑箱測試:只測試公開方法 -:white_check_mark: **Do:** Testing the internals brings huge overhead for almost nothing. If your code/API delivers the right results, should you really invest your next 3 hours in testing HOW it worked internally and then maintain these fragile tests? Whenever a public behavior is checked, the private implementation is also implicitly tested and your tests will break only if there is a certain problem (e.g. wrong output). This approach is also referred to as `behavioral testing`. On the other side, should you test the internals (white box approach) — your focus shifts from planning the component outcome to nitty-gritty details and your test might break because of minor code refactors although the results are fine - this dramatically increases the maintenance burden +:white_check_mark: **建議:** 測試內部邏輯是無意義且浪費時間的。如果你的程式/API 回傳了正確的結果,你真的需要花三個小時的時間去測試它內部究竟如何實現的,並且在之後維護這一堆脆弱的測試嗎?每當測試一個公開方法時,其私有方法的實作也會被隱性地測試,只有當存在某個問題(例如錯誤的輸出)時測試才會中斷。這種方法也稱為 ```行為測試```。另一方面,如果你測試內部方法 (白箱方法) — 你的關注點將從組件的輸出結果轉移到具體的實作細節上,如果某天內部邏輯改變了,即使結果依然正確,你也要花精力去維護之前的測試邏輯,這無形中增加了維護成本。
    -❌ **Otherwise:** Your tests behave like the [boy who cried wolf](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf): shouting false-positive cries (e.g., A test fails because a private variable name was changed). Unsurprisingly, people will soon start to ignore the CI notifications until someday, a real bug gets ignored… +❌ **否則:** 你的測試會像[狼來了](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf)一樣,總是叫喚著出問題了 (例如一個因為內部變數名稱改變而導致的測試失敗)。不出所料,人們很快就會開始忽視 CI 的通知,直到某天,一個真正的 bug 被忽視...
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-Pattern Example: A test case is testing the internals for no good reason +### :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 + // 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 + // Change the result format or key name above will make the tests fail } - //public method + // public method getPrice(productId) { const desiredProduct = DB.getProduct(productId); finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice; @@ -303,7 +303,7 @@ class ProductService { } 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 + // 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); }); ``` @@ -312,32 +312,31 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response",

    -## ⚪ ️ ️1.5 Choose the right test doubles: Avoid mocks in favor of stubs and spies +## ⚪ ️ ️1.5 使用正確的測試替身 (Test Double):避免總是使用 stub 和 spy -:white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value ([Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). +:white_check_mark: **建議:** 測試替身是把雙刃劍,他們在提供巨大價值的同時,耦合了應用的內部邏輯 ([一篇關於測試替身的文章: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)) 在使用測試替身前,問自己一個很簡單的問題:我是用它來測試需求文件中定義的可見的功能或者可能可見的功能嗎?如果不是,那就可能是白盒測試了。 -Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. +舉例來說,如果你想測試你的應用程式在支付服務當機時的預期行為,你可以 stub 支付服務並觸發一些"沒有回應"的回傳行為,以確保被測試的單元回傳正確的值。這可以測試特定場景下應用程式的行為、回應及輸出結果。你也可以使用一個 spy 來斷言當服務當機時是否有發送電子郵件 - 這又是一個針對可能出現在需求文件中行為的檢查 ("如果無法儲存付款資訊,發送電子郵件")。反過來說,如果你 mock 的支付服務,能確保它被正確呼叫並傳入正確的 JavaScript 型別,那麼你的測試重點是內部的邏輯,它與應用程式的功能關係不大,而且可能會經常變化。 -For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that have nothing to do with the application functionality and are likely to change frequently
    -❌ **Otherwise:** Any refactoring of code mandates searching for all the mocks in the code and updating accordingly. Tests become a burden rather than a helpful friend +❌ **否則:** 任何程式的重構都會需要程式中所有的 mock 進行相對應的更新。測試變成了一種負擔,而不是一個助力。
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-pattern example: Mocks focus on the internals +### :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 + // 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 + // hmmm BAD: testing the internals is actually our main goal here, not just a side-effect dataAccessMock .expects("deleteProduct") .once() @@ -349,14 +348,14 @@ it("When a valid product is about to be deleted, ensure data access DAL was call
    -### :clap:Doing It Right Example: spies are focused on testing the requirements but as a side-effect are unavoidably touching to the internals +### :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 + // 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) + // 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; }); ``` @@ -365,59 +364,59 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

    -## 📗 Want to learn all these practices with live video? +## 📗 想要透過影片來學習這些做法嗎? -### Visit my online course [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) +### 歡迎來我的線上課程網站 [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com)

    -## ⚪ ️1.6 Don’t “foo”, use realistic input data +## ⚪ ️1.6 不要 "foo", 使用真實的資料 -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +:white_check_mark: **建議:** 生產環境中的 bug 通常是在一些特殊或者意外的輸入下出現的 — 所以測試的輸入資料越真實,越容易在早期抓住問題。使用現有的一些函式庫(比如 [Faker](https://www.npmjs.com/package/faker))去造"假"真實數據來模擬生產環境數據的多樣性和形式。比如,這些函示庫可以產生真實的電話號碼、用戶名稱、信用卡、公司名稱等等。你還可以創建一些測試(在單元測試之上,而不是替代)生產隨機 fakers 數據來擴充你的測試單元,甚至從生產環境中導入真實的資料。如果想要更進階的話,請看下一個項目:基於屬性的測試 (property-based testing)。
    -❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **否則:** 你要部屬的程式都在 "foo" 之類的輸入值中正確的通過測試,結果上線之後收到像是 ```@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA``` 之類的輸入值後掛掉了。
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data +### :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 + const productNameRegexNoSpace = /^\S*$/; // no white-space allowed - if (!productNameRegexNoSpace.test(name)) return false; //this path never reached due to dull input + if (!productNameRegexNoSpace.test(name)) return false; // this path never reached due to dull input - //some logic here + // 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 + // 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 + // Positive-false: the operation succeeded because we never tried with long + // product name including spaces }); ```
    -### :clap:Doing It Right Example: Randomizing realistic input +### :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} + // 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! + // Test failed, the random input triggered some path we never planned for. + // We discovered a bug early! }); ``` From 2d3bfdce228e9d2d7e07e7d3e417f90a6a57e7d7 Mon Sep 17 00:00:00 2001 From: yubintw Date: Fri, 3 Dec 2021 23:41:18 +0800 Subject: [PATCH 378/502] Translate Section 1.7 --- readme-zh-TW.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index ad5be727..bca73f44 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -424,20 +424,27 @@ it("Better: When adding new valid product, get successful confirmation", async (

    -## ⚪ ️ 1.7 Test many input combinations using Property-based testing +## ⚪ ️ 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),它似乎提供了更多的功能,且有被積極維護。 -:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet [‘Don’t foo’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained
    -❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs +❌ **否則:** 你無意中選擇的測試輸入只涵蓋到運作正常的程式片段。不幸的是,他沒有發現真正的錯誤,這也降低了把測試當作發現錯誤的工具的成效。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Testing many input permutations with “fast-check” +### :clap: 正例: 使用 fast-check 來測試許多的輸入組合 ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") @@ -446,7 +453,7 @@ import fc from "fast-check"; describe("Product service", () => { describe("Adding new", () => { - //this will run 100 times with different random properties + // 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) => { From 93f420533a3305cd845373055201009852c6bd3a Mon Sep 17 00:00:00 2001 From: yubintw Date: Sun, 5 Dec 2021 13:33:50 +0800 Subject: [PATCH 379/502] Translate Section 1.8 --- readme-zh-TW.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index bca73f44..a5e93bd9 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -468,57 +468,58 @@ describe("Product service", () => {

    -## ⚪ ️ 1.8 If needed, use only short & inline snapshots +## ⚪ ️ 1.8 如果需要,只使用簡短的行內快照 (inline snapshots) -:white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. +:white_check_mark: **建議:** 如果你需要進行 快照測試 ([snapshot testing](https://jestjs.io/docs/en/snapshot-testing)),只使用短而集中的快照 (如3~7行),該快照是測試程式的一部份,而不是在外部文件中。保持好這一原則,將會確保你的測試的自我解釋性且不會那麼脆弱。 -On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much +另一方面,"classic snapshots"的教學和工具鼓勵將大文件 (如組件的渲染結果、API 的 JSON 結果) 存儲在一些外部媒介上,並確保每次測試運行時,將收到的結果與保存的版本進行比較。舉個例子,這將會隱性地將我們的測試與包含3000個數值的1000行內容耦合在一起,而測試者從未閱讀和推理過這些數據。為什麼這樣是不對的? 這樣做,將會有1000個原因讓你的測試失敗 - 只要有一行改變,快照比對就會 fail,而這可能會經常發生。多頻繁?當有每一個空格、註解或一點 CSS/HTML 的變化。不僅如此,測試名稱也不會提供關於失敗的線索,因為它只是檢查這1000行是否有變化,而且它還鼓勵測試者去接受一個他無法檢查和驗證的大文件作為期望的結果。所有這些都是測試目標不明確、測試目標過多的症狀。 + +值得注意的是,在少數情況下,大型的外部快照是可以接受的 - 當斷言的對象是 schema 而不是所有內容時 (提取出要的值並專注在某個欄位上),或者當收到的文件內容幾乎不會改變時。 -It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes
    -❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **否則:** 一個 UI 的測試失敗了。程式看起來是對的,畫面上也完美渲染了每個像素,但怎麼了? 你的測試程式發現收到的內容與期望的不同,或許只是多了一個空格...
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code +### :thumbsdown: 反例: 將看不到的 2000 行程式耦合進我們的測試案例中 ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ```javascript it("TestJavaScript.com is renderd correctly", () => { - //Arrange + // Arrange - //Act + // Act const receivedPage = renderer .create( Test JavaScript ) .toJSON(); - //Assert + // Assert expect(receivedPage).toMatchSnapshot(); - //We now implicitly maintain a 2000 lines long document - //every additional line break or comment - will break this test + // We now implicitly maintain a 2000 lines long document + // every additional line break or comment - will break this test }); ```
    -### :clap: Doing It Right Example: Expectations are visible and focused +### :clap: 正例:期望是可見且集中的 ```javascript it("When visiting TestJavaScript.com home page, a menu is displayed", () => { - //Arrange + // Arrange - //Act + // Act const receivedPage = renderer .create( Test JavaScript ) .toJSON(); - //Assert + // Assert const menu = receivedPage.content.menu; expect(menu).toMatchInlineSnapshot(` From 91fa65721f695294b2cdf0f6558be883c15f03ac Mon Sep 17 00:00:00 2001 From: yubintw Date: Sun, 5 Dec 2021 23:23:11 +0800 Subject: [PATCH 380/502] Translate Section 1.9 --- readme-zh-TW.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index a5e93bd9..ee237cc0 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -536,49 +536,50 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {

    -## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test +## ⚪ ️1.9 避免使用全域的 test fixtures 或 seeds,而是放進每個測試中 + +:white_check_mark: **建議:** 參照黃金原則,每個測試需要在它自己的 DB 中進行操作避免互相污染。但現實中,這條規則經常被打破:為了性能的提升而在執行測試前初始化全域資料庫 (也被稱為"[test fixture](https://en.wikipedia.org/wiki/Test_fixture)")。儘管性能很重要,但是它可以通過後面講的「組件測試」來做取捨。為了減輕複雜度,我們可以在每個測試中只初始化自己需要的數據。除非性能問題真的非常嚴重,那還是可以做一定程度的妥協 - 僅在全域放不會改變的數據 (比如 query)。 -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries)
    -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **否則:** 有一些測試 fail 了,團隊花了許多時間後發現,只是因為兩個測試同時改變了同一個 seed。
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :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 + // 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 + // 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 + // 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 :[ + expect(siteToCheck.name).to.be.equal("Portal"); // Failure! The previous test change the name :[ }); ```
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: 正例:每個測試案例只操作他自己的資料 ```javascript it("When updating site name, get successful confirmation", async () => { - //test is adding a fresh new records and acting on the records only + // test is adding a fresh new records and acting on the records only const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); From 31efe9e7deae8d3e45507e1cfdb5e2123c07b039 Mon Sep 17 00:00:00 2001 From: yubintw Date: Mon, 6 Dec 2021 22:03:21 +0800 Subject: [PATCH 381/502] Translate Section 1.10 --- readme-zh-TW.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index ee237cc0..87f34443 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -594,22 +594,23 @@ it("When updating site name, get successful confirmation", async () => {
    -## ⚪ ️ 1.10 Don’t catch errors, expect them +## ⚪ ️ 1.10 不要 catch 錯誤,expect 他們 -:white_check_mark: **Do:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations +:white_check_mark: **建議:** 當你要測試一些輸入是否有觸發錯誤時,使用 ```try-catch-finally``` 來檢查他是否會進入到 catch 區塊,看起來沒什麼問題。但會變成一個笨拙且冗長的測試案例 (如下面程式範例),他會隱藏簡單的測試意圖和預期的結果。 + +一個更為優雅的作法是使用專用的單行斷言:如 Chai 中的 ```expect(method).to.throw``` 或是 Jest 中的 ```expect(method).toThrow()```。必須要確保這個 expection 包含某個預期的 error type,如果只得到一個通用的錯誤型態,那應用程式將無法表明更多訊息給使用者。 -A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user
    -❌ **Otherwise:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +❌ **否則:** 從測試報告 (如 CI 報告) 中要看出哪裡有錯會非常困難。
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch +### :thumbsdown: 反例:一個很長的測試案例,嘗試使用 ```try-catch``` 來斷言錯誤 ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") @@ -623,14 +624,14 @@ it("When no product name, it throws error 400", async () => { 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 + // 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: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM +### :clap: 正例:一個容易閱讀及被了解的 expection,甚至能被 QA 或 PM 理解 ```javascript it("When no product name, it throws error 400", async () => { From 08db05bc29ec51760b2a0a19bab22a7817031505 Mon Sep 17 00:00:00 2001 From: yubintw Date: Tue, 7 Dec 2021 23:56:20 +0800 Subject: [PATCH 382/502] Translate Section 1.11 --- readme-zh-TW.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 87f34443..3e1c17dd 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -645,30 +645,30 @@ it("When no product name, it throws error 400", async () => {

    -## ⚪ ️ 1.11 Tag your tests +## ⚪ ️ 1.11 為測試案例打上標籤 -:white_check_mark: **Do:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +:white_check_mark: **建議:** 不同的測試需要在不同的情境下執行:快速冒煙測試、無 IO 的測試、開發者儲存或提交檔案的測試、送出一個 PR 後的 end-to-end 測試等等。 可以用一些 ```#cold``` ```#api``` ```#sanity``` 之類的標籤來標註這些測試,這樣就可以在測試時只執行特定的子集合。例如在 Mocha 中可以這樣來執行:```mocha -- grep 'sanity'```。
    -❌ **Otherwise:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests +❌ **否則:** 執行所有測試案例,包括執行大量查詢 DB 的測試,開發者做的任何微小的變更都需要花很長的時間去跑完所有的測試,將會導致開發者不想再執行測試。
    -
    Code Examples +
    程式範例:
    -### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) +### :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 +// 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 + // code logic here }); }); }); From f8c457546e95e25cdc806b47bc8f3cf0198b212b Mon Sep 17 00:00:00 2001 From: yubintw Date: Wed, 8 Dec 2021 23:55:39 +0800 Subject: [PATCH 383/502] Translate Section 1.12 --- readme-zh-TW.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 3e1c17dd..9fd054ca 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -678,33 +678,33 @@ describe("Order service", function() {

    -## ⚪ ️ 1.12 Categorize tests under at least 2 levels +## ⚪ ️ 1.12 把測試案例進行至少兩個層次的分類 -:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and print screen below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **建議:** 對測試案例套用一些結構,讓每個看到這個測試案例的人都可以很容易得理解需求 (測試是最好的文件) 和正在測試的各種情境。一個常見的方法是在測試上方寫至少兩個用來"描述"的區塊:第一個是測試單元的名稱,第二個是額外的分類名稱,如情境或自定義的類別 (參考下面的程式範例和畫面輸出)。這樣的做法也會大幅的改善測試報告的呈現。讀者將會很容易的推斷出測試的類別,讀懂該測試的內容並與失敗的測試關聯起來。此外,對開發者來說,瀏覽這一連串的測試也變得更加容易。有許多額外的結構也是可以考慮使用的,像是 [given-when-then](https://github.com/searls/jasmine-given) 或 [RITE](https://github.com/ericelliott/riteway)。
    -❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in a hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause +❌ **否則** 當看到一份毫無結構且數量眾多的測試報告時,讀者只能透過粗略地閱讀整份報告來總結,並將失敗的錯誤案例關聯起來。思考一個情況,當100個測試案例中有7個失敗時,看一個分層結構良好的測試報告與看一個扁平的測試結果清單相比,那些錯誤的測試案例很有可能都在同一個流程或分類底下,讀者將可以很快的推斷出錯誤的地方或看出哪部分是他們失敗的原因。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below +### :clap: 正例:利用測試案例的名稱和情境來組織,可以產生良好的測試報告,如下所示 ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ```javascript // Unit under test describe("Transfer service", () => { - //Scenario + // Scenario describe("When no credit", () => { - //Expectation + // Expectation test("Then the response status should decline", () => {}); - //Expectation + // Expectation test("Then it should send email to admin", () => {}); }); }); @@ -714,7 +714,7 @@ describe("Transfer service", () => {
    -### :thumbsdown: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests +### :thumbsdown: 反例:扁平的測試列表會使讀者很難去看懂 user story 和失敗的測試之間的關係 ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Mocha") From a73ae88fb488bebcf2e9c9abb9574c5a121a67df Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 9 Dec 2021 23:01:38 +0800 Subject: [PATCH 384/502] Translate Section 1.13 --- readme-zh-TW.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 9fd054ca..3bcd751a 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -734,14 +734,15 @@ test("Then there should not be a new transfer record", () => {});

    -## ⚪ ️1.13 Other generic good testing hygiene +## ⚪ ️1.13 其他通用且良好的測試習慣 -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +: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 的水準,避免對環境 (如路徑或作業系統等) 有任何相依性。 -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satisfies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc)
    -❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades +❌ **否則:** 你會錯過這數十年來的智慧結晶

    From c55ae81ea9d5115066fcc5a4afe514521a17beef Mon Sep 17 00:00:00 2001 From: yubintw Date: Fri, 10 Dec 2021 23:59:26 +0800 Subject: [PATCH 385/502] Translate Section 2.1 --- readme-zh-TW.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 3bcd751a..d726fa05 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -10,7 +10,7 @@ 這是從 A 到 Z 的 JavaScript 及 Node.js 可靠的指南。它為你總結及規劃了市場上大量的部落格文章、書籍及工具。 -## 🚢 進階:從基礎向前邁進 10,000 英里 Advanced: Goes 10,000 miles beyond the basics +## 🚢 進階:從基礎向前邁進 10,000 英里 從基礎往前邁進的旅程,包括:在生產(production)環境中測試、變異測試(mutation testing)、以屬性為基礎(property-based)的測試以及許多策略和專業工具。如果你認真閱讀本指南書,你的測試技能可能會高於平均水準。 @@ -93,7 +93,7 @@ JavaScript 世界的 CI 指南 (9項)

    -# Section 1: The Test Anatomy +# 第 1 章:測試剖析
    @@ -746,29 +746,29 @@ test("Then there should not be a new transfer record", () => {});

    -# Section 2️⃣: Backend Testing +# 第 2 章:後端測試 -## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid +## ⚪ ️2.1 豐富您的測試組合:不局限於單元測試和測試金字塔 -:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **建議:** 雖然 [測試金字塔](https://martinfowler.com/bliki/TestPyramid.html) 已經有超過十年的歷史了,但他仍然是個很好的模型,他提出了三種測試類型,並影響了大多數開發者的測試策略。與此同時,大量閃亮的新測試技術出現了,並隱藏在測試金字塔的陰影下。考慮到近十年來我們所看到的所有巨變 (Microservices, cloud, serverless),這個非常老的模型是否仍能適用於所有類型的應用?測試界不應該考慮新的測試技術嗎? -Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IoT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. +不要誤會,在 2019 年,測試金字塔、TDD、單元測試仍然是強大的技術,且對於大多數應用仍是最佳選擇。但是像其他模型一樣,儘管它有用,但是一定[會在某些時候出問題](https://en.wikipedia.org/wiki/All_models_are_wrong)。例如,我們有一個 IoT 的應用程式,將許多事件傳入一個 Kafka/RabbitMQ 這樣的 message-bus 中,然後這些事件流入資料庫並被經由 UI 來做查詢。我們真的需要花費 50% 的測試預算去為這個幾乎沒有邏輯的中心化的整合應用程式寫單元測試嗎?隨著應用類型 (bots, crypto, Alexa-skills) 的多樣增長,測試金字塔可能將不再是某些場景的最佳選擇了。 -It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks +是時候豐富你的測試組合並了解更多的測試類型了(下一節會給你一些小建議),這些類似於測試金字塔的思維模型與你所面臨的現實問題會更加匹配("嘿,我們的 API 掛了,我們來寫 consumer-driven contract testing 吧!")。讓您的測試多樣化,比如建立基於風險分析的檢查模型 — 評估可能出現問題的地方,並提供一些預防措施以減輕這些潛在風險。 -A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] +需要注意的是:軟體世界中的 TDD 模型面臨兩個極端的態度,一些人鼓吹到處使用它,另一些人則認為它是魔鬼。每個說絕對的人都是錯的 :]
    -❌ **Otherwise:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes +❌ **否則:** 你將錯過一些超高 CP 值的工具,比如 Fuzz、lint、mutation,這些工具只需 10 分鐘設定就能為你提供許多好處。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the same way’ +### :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’") From 06a869734bc47b537aa2b832bbcd1f8b2d72b681 Mon Sep 17 00:00:00 2001 From: yubintw Date: Sat, 11 Dec 2021 10:05:56 +0800 Subject: [PATCH 386/502] Translate Section 2.2 ~ 2.4 --- readme-zh-TW.md | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index d726fa05..182d862c 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -782,22 +782,23 @@ test("Then there should not be a new transfer record", () => {});

    -## ⚪ ️2.2 Component testing might be your best affair +## ⚪ ️2.2 組件化測試可能是最有效的利器 -:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +:white_check_mark: **建議:** 應用程式中的每個單元測試僅能覆蓋整個程式的一小部分,要覆蓋全部會非常麻煩,而端到端測試可以很輕鬆地覆蓋大量區域,但是比較脆弱而且很慢。何不找一個平衡點:寫一些比單元測試大,但是比端到端測試小的測試。組件測試是測試世界的一顆遺珠 — 它找到了兩個模式的最佳平衡點:不錯的性能和使用 TDD 模式的可能性與真實且強大的覆蓋率。 + +組件測試關注於微服務"單元",他們針對 API 來做事,不 mock 任何屬於微服務本身的東西(像是真實的 DB,甚至是該 DB 的 in-memory 版本)但是 stub 所有外部的東西,像是呼叫其他的微服務。藉由這種方式,我們可以測試我們部署的部分,由外而內地覆蓋應用程式,可以節省大量時間並獲得信心。 -Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time.
    -❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage +❌ **否則:** 你可能花了好幾天來寫單元測試,卻發現只得到了 20% 的覆蓋率。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) +### :clap: 正例:使用 Supertest 來測試 Express API (快速且覆蓋多個層次) ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") @@ -807,20 +808,22 @@ Component tests focus on the Microservice ‘unit’, they work against the API,

    -## ⚪ ️2.3 Ensure new releases don’t break the API using contract tests +## ⚪ ️2.3 利用 contract tests 來確保新的 release 不會破壞 API 的使用 -:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +: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 階段被發現,從而減少你的煩惱。
    -❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear +❌ **否則:** 所有的變更都將會造成繁瑣的人工測試,導致開發者害怕部屬
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: +### :clap: 正例: ![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") @@ -830,28 +833,28 @@ Component tests focus on the Microservice ‘unit’, they work against the API,

    -## ⚪ ️ 2.4 Test your middlewares in isolation +## ⚪ ️2.4 單獨測試你的 middlewares -:white_check_mark: **Do:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +: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 狀態是否符合預期。(看下面的程式範例)
    -❌ **Otherwise:** A bug in Express middleware === a bug in all or most requests +❌ **否則:** Express middlewares 的 bug === 所有或大部分 request 的 bug
    -
    Code Examples +
    程式範例
    -### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :clap: 正例:單獨測試 middleware,不發出網路請求或啟動整個 Express 伺服器 ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") ```javascript -//the middleware we want to test +// the middleware we want to test const unitUnderTest = require("./middleware"); const httpMocks = require("node-mocks-http"); -//Jest syntax, equivelant to describe() & it() in Mocha +// 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", From 7f94c393bfb564a7fe015a7324edb1a9b0d840fe Mon Sep 17 00:00:00 2001 From: yubintw Date: Sun, 12 Dec 2021 21:08:06 +0800 Subject: [PATCH 387/502] Translate Section 2.5 --- readme-zh-TW.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 182d862c..f839cbcf 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -873,23 +873,23 @@ test("A request without authentication header, should return http status 403", (

    -## ⚪ ️2.5 Measure and refactor using static analysis tools +## ⚪ ️2.5 使用靜態分析工具來測量與重構 -:white_check_mark: **Do:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [SonarQube](https://www.sonarqube.org/) (4,900+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (2,000+ [stars](https://github.com/codeclimate/codeclimate)) +: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)
    -❌ **Otherwise:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix +❌ **否則;** 程式碼的品質過差,再新的函式庫或功能都無法拯救你的 bug 和性能
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: +### :clap: 正例:CodeClimate,一個可以發現複雜方法的商業工具 ![](https://img.shields.io/badge/🔧%20Example%20using%20Code%20Climate-blue.svg "Examples with CodeClimate") From 146725bd88cc7c54a64894b7c08f7511ee6f54d0 Mon Sep 17 00:00:00 2001 From: yubintw Date: Mon, 13 Dec 2021 23:12:22 +0800 Subject: [PATCH 388/502] Translate Section 2.6 --- readme-zh-TW.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index f839cbcf..f712eef3 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -899,20 +899,21 @@ Credit: Date: Tue, 14 Dec 2021 00:08:43 +0800 Subject: [PATCH 389/502] Remove Section 2.7 (duplicate with Section 1.9) --- readme-zh-TW.md | 55 ------------------------------------------------- 1 file changed, 55 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index f712eef3..feadd24c 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -919,61 +919,6 @@ Credit: { - //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: Doing It Right Example: We can stay within the test, each test acts on its own set of data - -```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️⃣: Frontend Testing From 6304e5f70fc6be0cda400672959257fabe57f0cf Mon Sep 17 00:00:00 2001 From: yubintw Date: Wed, 15 Dec 2021 23:30:47 +0800 Subject: [PATCH 390/502] Translate Section 3.1 --- readme-zh-TW.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index feadd24c..62a07a3a 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -164,7 +164,7 @@ describe('Products Service', function() {
    -❌ **否則:** 你不僅需要花很多時間去理解主要程式,而且本應是最簡單的部分 - 測試,也會讓你腦力耗盡。 +❌ **否則:** 你不僅需要花很多時間去理解主要程式,而且本應是最簡單的部分 - 測試,也會讓你腦力耗盡。
    @@ -684,7 +684,7 @@ describe("Order service", function() {
    -❌ **否則** 當看到一份毫無結構且數量眾多的測試報告時,讀者只能透過粗略地閱讀整份報告來總結,並將失敗的錯誤案例關聯起來。思考一個情況,當100個測試案例中有7個失敗時,看一個分層結構良好的測試報告與看一個扁平的測試結果清單相比,那些錯誤的測試案例很有可能都在同一個流程或分類底下,讀者將可以很快的推斷出錯誤的地方或看出哪部分是他們失敗的原因。 +❌ **否則:** 當看到一份毫無結構且數量眾多的測試報告時,讀者只能透過粗略地閱讀整份報告來總結,並將失敗的錯誤案例關聯起來。思考一個情況,當100個測試案例中有7個失敗時,看一個分層結構良好的測試報告與看一個扁平的測試結果清單相比,那些錯誤的測試案例很有可能都在同一個流程或分類底下,讀者將可以很快的推斷出錯誤的地方或看出哪部分是他們失敗的原因。
    @@ -921,23 +921,23 @@ Credit:
    uiElement.textContent); const allRealVIPUsers = allUsers.filter(user => user.vip).map(user => user.name); - expect(allRenderedUsers).toEqual(allRealVIPUsers); //compare data with data, no UI here + expect(allRenderedUsers).toEqual(allRealVIPUsers); // compare data with data, no UI here }); ```
    -### :thumbsdown: Anti-Pattern Example: Assertion mix UI details and data +### :thumbsdown: 反例:混雜了 UI 與資料的斷言 ```javascript test("When flagging to show only VIP, should display only VIP members", () => { From 80746a5fc804355652269df1460b756658c2625d Mon Sep 17 00:00:00 2001 From: yubintw Date: Sat, 18 Dec 2021 21:12:55 +0800 Subject: [PATCH 391/502] Translate Section 3.2 --- readme-zh-TW.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 62a07a3a..73f02f4e 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -977,21 +977,21 @@ test("When flagging to show only VIP, should display only VIP members", () => {

    -## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change +## ⚪ ️ 3.2 使用不易改變的屬性來查询 HTML 元素 -:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +:white_check_mark: **建議:** 使用不太容易受畫面變更而影響的屬性來查詢 HTML 元素 (例如 form label,而不是 CSS selector)。如果指定的元素沒有這樣的屬性,則創建一個專用的測試屬性,如 `test-id-submit-button`。這樣做不僅可以確保您的功能/邏輯測試不會因為外觀變化而中斷,而且整個團隊可以清楚地看到,測試案例使用了這個元素和屬性,不應該刪除它。
    -❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **否則:** 假設你想要測試一個跨越許多組件、邏輯和服務的登入功能,一切都設置得很完美 - stub、spy、Ajax 的呼叫都是隔離的。看似一切都很完美,但卻發現測試失敗了,因為開發者將 div 的 class 從 `thick-border` 改為 `thin-border`。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Querying an element using a dedicated attribute for testing +### :clap: 正例: 使用專用的 attribute 來查詢元素來進行測試 ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") @@ -1020,7 +1020,7 @@ test("Whenever no data is passed to metric, show 0 as default", () => {
    -### :thumbsdown: Anti-Pattern Example: Relying on CSS attributes +### :thumbsdown: 反例: 依靠於 CSS attributes ```html From e1c6f15361601df618ebe4e5397b0928228aab94 Mon Sep 17 00:00:00 2001 From: yubintw Date: Mon, 20 Dec 2021 22:15:35 +0800 Subject: [PATCH 392/502] Translate Section 3.3 --- readme-zh-TW.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 73f02f4e..b9f76e82 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1041,23 +1041,23 @@ test("Whenever no data is passed, error metric shows zero", () => {
    -## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component +## ⚪ ️ 3.3 如果可以,使用真實且完全渲染的組件來進行測試 -:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet ['Favour blackbox testing'](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-14-stick-to-black-box-testing-test-only-public-methods)). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +:white_check_mark: **建議:** 只要尺寸合適,像使用者那樣從外部測試你的組件,完全渲染 UI,對其進行操作,並斷言對那些 UI 的行為是否符合預期。避免各種 mock、partial 和 shallow rendering - 這樣做可能會因為缺乏細節而導致有未捕捉到的 bug,而且由於測試會擾亂內部的結構而使得維護變得更加困難 (參考 堅持黑箱測試)。如果其中一個子組件明顯拖慢速度 (如 動畫) 或很難去設定,可以考慮使用假的組件去替換它。 -With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children +綜上所說,需要注意的是:這種技術適用於包含合理大小子組件的中小型組件。完全渲染一個有太多子組件的組件會讓他很難被看出失敗的原因 (root cause analysis),而且可能會非常慢。在這種情況下,可以對那些很肥的父組件撰寫少量的測試,並對其子組件多寫幾個測試。
    -❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? +❌ **否則:** 呼叫組件的私有方法來測試組件的內部狀態。後續重構組件時你必須重構所有測試。你真的有能力進行這種程度的維護嗎?
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Working realistically with a fully rendered component +### :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") @@ -1075,7 +1075,7 @@ class Calendar extends React.Component { } } -//Examples use React & Enzyme +// Examples use React & Enzyme test("Realistic approach: When clicked to show filters, filters are displayed", () => { // Arrange const wrapper = mount(); @@ -1089,7 +1089,7 @@ test("Realistic approach: When clicked to show filters, filters are displayed", }); ``` -### :thumbsdown: Anti-Pattern Example: Mocking the reality with shallow rendering +### :thumbsdown: 反例: 使用 shallow rendering 來測試 ```javascript test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { From d4248a8c42b068f7e433b37a845866c54d1a4d79 Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 20 Jan 2022 22:13:17 +0800 Subject: [PATCH 393/502] Translate Section 3.4 --- readme-zh-TW.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index b9f76e82..28536e66 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1113,20 +1113,21 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up +## ⚪ ️ 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)) 可以幫助解決半確定性問題。 -:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution
    -❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance +❌ **否則:** 當 sleep 的時間太長時,測試速度會慢上一個數量級。當嘗試縮短 sleep 時間時,如果被測試的單元沒有及時響應,測試將會失敗。這時你不得不在脆弱的測試和糟糕的性能之間進行權衡。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) +### :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") @@ -1138,7 +1139,7 @@ cy.wait("@products"); // wait for route to appear // this line will get executed only when the route is ready ``` -### :clap: Doing It Right Example: Testing library that waits for DOM elements +### :clap: 正例:測試的函示庫等待 DOM 元素 ```javascript // @testing-library/dom @@ -1155,7 +1156,7 @@ test("movie title appears", async () => { }); ``` -### :thumbsdown: Anti-Pattern Example: custom sleep code +### :thumbsdown: 反例: 自製的 sleep 程式 ```javascript test("movie title appears", async () => { From 8328c8de690fcd17e7d494f53dada141204b19a3 Mon Sep 17 00:00:00 2001 From: yubintw Date: Fri, 28 Jan 2022 22:36:13 +0800 Subject: [PATCH 394/502] Translate Section 3.5 --- readme-zh-TW.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 28536e66..7417ee20 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1180,21 +1180,21 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.5 Watch how the content is served over the network +## ⚪ ️ 3.5 觀察資源經由網路被提供的情況 ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") -✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 +✅ **建議:** 使用一些活動監視器,以確保在真實網路下的頁面載入情況是最佳的 — 這包含一些使用者體驗的問題:像是緩慢的頁面載入時間或未經壓縮的資源。市面上有很豐富的檢查工具:像 [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 上使用它們。
    -❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration +❌ **否則:** 設計了一個精美的 UI、且通過了 100% 的功能測試與精心的包裝,使用者體驗卻因為 CDN 的錯誤設定而變得糟糕及緩慢。
    -
    Code Examples +
    程式範例 -### :clap: Doing It Right Example: Lighthouse page load inspection report +### :clap: 正例:Lighthouse 的頁面載入檢測報告 ![](/assets/lighthouse2.png "Lighthouse page load inspection report") From cf5c4bb3e6d26a3d24723d38963163a08a13bc8f Mon Sep 17 00:00:00 2001 From: yubintw Date: Sat, 29 Jan 2022 22:03:20 +0800 Subject: [PATCH 395/502] Translate Section 3.6 --- readme-zh-TW.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 7417ee20..f17400a3 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1204,19 +1204,19 @@ test("movie title appears", async () => { ## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs -:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +: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 拋出錯誤時測試組件行為。最後但並非最不重要的原因是,經過網絡的呼叫將會大大降低執行測試的速度。
    -❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower +❌ **否則:** 平均執行測試的時間不再只是幾毫秒而已,一個普通的 API 呼叫至少需要 100 毫秒,這會讓你的測試慢 20 倍以上。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Stubbing or intercepting API calls +### :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") From d434fe321fc2eda9cde6764b2ffd549ed669880e Mon Sep 17 00:00:00 2001 From: yubintw Date: Sun, 30 Jan 2022 18:01:09 +0800 Subject: [PATCH 396/502] Translate Section 3.7 --- readme-zh-TW.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index f17400a3..c4572a38 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1256,13 +1256,13 @@ test("When no products exist, show the appropriate message", () => {
    -## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system +## ⚪ ️ 3.7 寫幾個跨越整個系統的 E2E 測試 -:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +: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 運行它們。
    -❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected +❌ **否則:** UI 可能在功能測試上花費了大量的精力,但最後才發現後端回傳的內容 (UI 要使用的資料格式) 與預期中的不一樣。
    From 90aa6a1612ac68ccce69c7d83860abbb962301a2 Mon Sep 17 00:00:00 2001 From: yubintw Date: Mon, 31 Jan 2022 21:08:55 +0800 Subject: [PATCH 397/502] Translate Section 3.8 --- readme-zh-TW.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index c4572a38..9f5181c0 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1258,7 +1258,7 @@ test("When no products exist, show the appropriate message", () => { ## ⚪ ️ 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 運行它們。 +: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 運行它們。
    @@ -1266,21 +1266,21 @@ test("When no products exist, show the appropriate message", () => {
    -## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials +## ⚪ ️ 3.8 藉由重複使用登入憑證來加速 E2E 測試 -:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)). +white_check_mark: **建議:** 在涉及真實的後端並必須使用有效的使用者 token 進行 API 呼叫的 E2E 測試中,我們沒有必要將每個測試都從「新增使用者並登錄」開始。相反的,在測試執行開始之前只登錄一次 (使用 before-all hook),將 token 儲存在本地端中,並在每個 request 之間重複使用它。雖然這似乎違反了測試的核心原則之一 — 保持測試的獨立性,不要耦合資源。這是一個合理的擔憂,但在 E2E 測試中,執行測試的性能是一個關鍵問題,在執行每個測試案例之前呼叫 1-3 個 API 可能會大大增加執行時間。重複使用憑證並不意味著測試必須基於相同的使用者資料 — 如果相依於使用者資料 (例如測試使用者付款的歷史記錄),那麼要確保產生這些資料來作為測試的一部分,並避免與其他測試共享它們。還要記住,後端是可以 fake 的 — 如果你的重點是測試前端,那麼最好隔離它,然後 stub 後端 API (參考 3.6 節)。
    -❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again +❌ **否則:** 給定 200 個測試案例,假設登錄需要花費的時間為 100ms,則至少需要花費 20s,在這一遍遍的登錄上。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Logging-in before-all and not before-each +### :clap: 正例: 在 before-all 中登錄,而不是 before-each ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") From a70d199278da253f8cdc3d7931d1c754ac60e7e7 Mon Sep 17 00:00:00 2001 From: "YuBin, Hsu" <31545456+yubinTW@users.noreply.github.com> Date: Tue, 1 Feb 2022 14:06:25 +0800 Subject: [PATCH 398/502] Update code example of 3.9 --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 1e7990b3..fd7e398c 100644 --- a/readme.md +++ b/readme.md @@ -1376,9 +1376,9 @@ it("When doing smoke testing over all page, should load them all successfully", // using any E2E suite cy.visit("https://mysite.com/home"); cy.contains("Home"); - cy.contains("https://mysite.com/Login"); + cy.visit("https://mysite.com/Login"); cy.contains("Login"); - cy.contains("https://mysite.com/About"); + cy.visit("https://mysite.com/About"); cy.contains("About"); }); ``` From 9aca6a1f4fb3959694c0977af625996e834d0582 Mon Sep 17 00:00:00 2001 From: yubintw Date: Tue, 1 Feb 2022 22:45:01 +0800 Subject: [PATCH 399/502] Translate Section 3.9 --- readme-zh-TW.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 9f5181c0..3a261503 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1314,21 +1314,21 @@ beforeEach(setUser => () {
    -## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map +## ⚪ ️ 3.9 寫一個走過整個網站的 E2E 冒煙測試 -:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector +:white_check_mark: **建議:** 為了 production 環境的監控及開發時期的完整性檢查,執行一個 E2E 測試,讓這個測試走訪過所有或大多數的網站頁面,並確保那些頁面沒有損毀。這類型的測試投資報酬率很高,因為他很容易去撰寫及維護,卻可以檢測出各種類型的故障,包括功能性、網路或佈屬的問題。其他類型的冒煙測試或完整性檢查並沒有那麼可靠及詳盡 - 有些 ops 團隊只是 ping 網站首頁 (在production環境),或開發人員執行了一些整合測試,卻沒發現到打包或瀏覽器的問題。毫無疑問的,冒煙測試並不會取代功能測試,而只是作為一個快速的煙霧偵測器。
    -❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering +❌ **否則:** 一切看似很完美,所有的測試都通過了,在 production 環境的健康狀態檢查也是 ok 的,但 Payment 這個組件有一些打包的問題,導致 /Paymout 這個路徑沒有被渲染。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Smoke travelling across all pages +### :clap: 正例:一個跑過所有頁面的冒煙測試 ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") @@ -1338,9 +1338,9 @@ it("When doing smoke testing over all page, should load them all successfully", // using any E2E suite cy.visit("https://mysite.com/home"); cy.contains("Home"); - cy.contains("https://mysite.com/Login"); + cy.visit("https://mysite.com/Login"); cy.contains("Login"); - cy.contains("https://mysite.com/About"); + cy.visit("https://mysite.com/About"); cy.contains("About"); }); ``` From b5d2abd113101fbe2d2f741d3b6a918fe4f6ff33 Mon Sep 17 00:00:00 2001 From: yubintw Date: Wed, 2 Feb 2022 22:23:17 +0800 Subject: [PATCH 400/502] Translate Section 3.10 --- readme-zh-TW.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 3a261503..1602b882 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1349,19 +1349,19 @@ it("When doing smoke testing over all page, should load them all successfully",
    -## ⚪ ️ 3.10 Expose the tests as a live collaborative document +## ⚪ ️ 3.10 將測試作為一個活的協作文件來看待 -:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. +: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 等。),看他長得怎樣以及如何去觸發他的不同狀態 - 這也可以提供給產品相關人員,但主要是作為活的文件給使用這些組建的開發者們。 -❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value +❌ **否則:** 在測試上已經耗費了大量的資源,如果不好好利用這項投資來獲取更大的價值,非常可惜。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js +### :clap: 正例:利用 cucumber-js 以人類可閱讀的語言來描述測試 ![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") @@ -1382,7 +1382,7 @@ Feature: Twitter new tweet ``` -### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook +### :clap: 正例:利用 Storybook 來展示組件的的不同狀態及輸入 ![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") From bb7522c99b62f0cdc395074d5d5c4a9ce07e0deb Mon Sep 17 00:00:00 2001 From: yubintw Date: Fri, 4 Feb 2022 20:13:55 +0800 Subject: [PATCH 401/502] Translate Section 3.11 --- readme-zh-TW.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 1602b882..b05f8c2a 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1392,27 +1392,27 @@ Feature: Twitter new tweet

    -## ⚪ ️ 3.11 Detect visual issues with automated tools +## ⚪ ️ 3.11 使用自動化工作來偵測視覺問題 -:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge significant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by eliminating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/CSS changes that led to the issue +: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 發生問題的根本原因。
    -❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? +❌ **否則:** 一個顯示內容且通過100%的功能測試的頁面,載入速度非常快,但有一半的內容都被隱藏了,這樣的頁面是好的嗎?
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-Pattern Example: A typical visual regression - right content that is served badly +### :thumbsdown: 反例:一個典型的 visual regression,右側內容顯示異常 ![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks")
    -### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots +### :clap: 正例:設定 wraith 來抓取並比對 UI 截圖 ![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") @@ -1441,7 +1441,7 @@ paths: path: /subscribe ``` -### :clap: Doing It Right Example: Using Applitools to get snapshot comparison and other advanced features +### :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") From 4c39ab97c600f9beb0830591577f65cc46c2f382 Mon Sep 17 00:00:00 2001 From: yubintw Date: Sat, 5 Feb 2022 23:42:49 +0800 Subject: [PATCH 402/502] Translate Section 4.1 --- readme-zh-TW.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index b05f8c2a..6eab5531 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1468,33 +1468,33 @@ describe("visual validation", () => {

    -# Section 4️⃣: Measuring Test Effectiveness +# 第 4 章:測量測試效果

    -## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number +## ⚪ ️ 4.1 藉由足夠的覆蓋率來獲得信心,~80% 看起來是個幸運數 -:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. +: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)),足夠滿足大多數的應用程式。 -Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets +實作建議:你可能會想在 CI 工具中設定測試覆蓋率的門檻 ([Jest link](https://jestjs.io/docs/configuration#collectcoverage-boolean)),並中斷那些未達覆蓋率門檻的建置 (也可以為每個組件設定覆蓋率門檻,參考下面的程式範例)。另外,也可以監測建置的覆蓋率是否下降 (當有一個新的且覆蓋率較低的程式被 commit) — 這將促使開發者去提升或至少維持一定的測試數量。說了很多,但測試覆蓋率只是一個量化出來的數值,它並不能證明你的測試是強壯的。或許你也會被他騙到 (參考下一小節的內容)。
    -❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down +❌ **否則:** 自信與數字是相輔相成的,如果無法確保測試已經覆蓋了大部分的系統,你將感到害怕,且恐懼會使你變慢。
    -
    Code Examples +
    程式範例
    -### :clap: Example: A typical coverage report +### :clap: 正例:一個典型的覆蓋率報告 ![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report")
    -### :clap: Doing It Right Example: Setting up coverage per component (using Jest) +### :clap: 正例:為每個組件設定覆蓋率 (使用 Jest) ![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") From aa6eabe90a6d1fff9c7d284a0ed221db0667908d Mon Sep 17 00:00:00 2001 From: yubintw Date: Sun, 6 Feb 2022 23:36:17 +0800 Subject: [PATCH 403/502] Translate Section 4.2 --- readme-zh-TW.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 6eab5531..7c4ff197 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1504,22 +1504,23 @@ describe("visual validation", () => {

    -## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities +## ⚪ ️ 4.2 檢查測試覆蓋率的報告來發現沒有被測試的區域或奇怪的地方 + +:white_check_mark: **建議:** 有些問題隱藏在雷達之下,使用傳統的工具很難發現到它們。它們通常不是真正的 bug,大多數情況下是應用程式的奇怪行為,而這些行為可能會造成嚴重影響。例如,一些程式區域幾乎不會或很少被呼叫 — 你以為 "PricingCalculator" 這個 class 只會設定產品價格,結果他幾乎不會被呼叫,即使我們的資料庫中有 10000 件商品以及很多筆交易…… 測試覆蓋率報告可以幫助你發現應用程式是否按照你的期望在執行。除此之外,它會 highlight 出哪些類型的程式沒有被測試到,80% 的程式被測試並不能代表程式中關鍵的部分有被覆蓋到。產生報告很簡單,只需要在執行測試的時候開啟覆蓋率追蹤的功能,然後讓那些花花綠綠的報告來告訴你每個程式區塊被呼叫的頻率。如果你花時間去看這些數據,你可能會發現一些問題。 -:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas
    -❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from +❌ **否則:** 如果你不知道你的程式裡面有哪些地方沒有被測試到,你將無法知道問題的來源。
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-Pattern Example: What’s wrong with this coverage report? +### :thumbsdown: 反例:這個測試覆蓋率的報告出了什麼問題? -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) +基於一個真實世界的情境,我們在 QA 中追蹤了我們應用程式的使用情況,並發現了這個有趣的登錄模式 (提示:登入失敗的數量不成正比的,顯然是有問題的。最後發現,有一些前端的 bug 一直在打後端的登入 API) ![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report?") From a9c319608ebf22c47fe07ad5bf4ac33178a6140e Mon Sep 17 00:00:00 2001 From: yubintw Date: Mon, 7 Feb 2022 22:43:41 +0800 Subject: [PATCH 404/502] Translate Section 4.3 --- readme-zh-TW.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 7c4ff197..0def89d0 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1528,28 +1528,29 @@ describe("visual validation", () => {

    -## ⚪ ️ 4.3 Measure logical coverage using mutation testing +## ⚪ ️ 4.3 使用「變異測試」測量邏輯覆蓋率 -:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +:white_check_mark: **建議:** 傳統的測試覆蓋率通常是騙人的,他可能會告訴你有 100% 的測試覆蓋率,但可能你的 function 都沒有回傳正確的值。為什麼會這樣?因為他只是很單純的測量你的測試程式走過哪幾行,而不會檢查測試案例到底測試了什麼,他到底有沒有確實去斷言正確的回應。就像有個人因公出差,他出示了他的護照,他無法證明他做了什麼工作,只能證明有去過哪幾個機場。 -Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: +基於變異的測試,是透過測量"實際測試"的程式數量而不僅僅是"訪問"過的數量來提供協助。[Stryker](https://stryker-mutator.io/) 是一個用於進行變異測試的 JavaScript 函示庫,他的實作非常巧妙: -(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations +(1) 他會刻意在你的程式中「植入 bug」。例如程式 `newOrder.price === 0` 會被改成 `newOrder.price != 0`,這個 "bug" 就稱為變異。 -(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. +(2) 他會跑過一次測試,如果測試通過了代表有些問題,這些測試案例沒有達到發現 bug 的目的,導致這些變異活了下來。如果測試失敗了,非常好,那些變異就會被殺掉。 + +相較於傳統的測試覆蓋率,如果知道所有的變異都被殺死,會讓你更有自信,而且這兩者花費的時間差不多。 -Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar
    -❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code +❌ **否則:** 你可能會誤以為 85% 的測試覆蓋率能發現程式中 85% 的 bug。
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-Pattern Example: 100% coverage, 0% testing +### :thumbsdown: 反例: 100% coverage, 0% testing ![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") @@ -1564,12 +1565,12 @@ function addNewOrder(newOrder) { 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 +}); // Triggers 100% code coverage, but it doesn't check anything ```
    -### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :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)") From c8ffa37d7b65fd4b365432860f10c5ddcfffc12b Mon Sep 17 00:00:00 2001 From: yubintw Date: Tue, 8 Feb 2022 22:04:53 +0800 Subject: [PATCH 405/502] Translate Section 4.4 --- readme-zh-TW.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 0def89d0..69342eea 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1578,32 +1578,32 @@ it("Test addNewOrder, don't use such test names", () => {

    -## ⚪ ️4.4 Preventing test code issues with Test linters +## ⚪ ️4.4 使用 Test linter 來避免測試程式的問題 -:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +: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) 可以在一個測試案例沒有任何斷言 (沒有檢查任何內容) 時給出警告。
    -❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation +❌ **否則:** 當你滿足於 90% 的測試覆蓋率或 100% 的綠色報告時,卻發現很多測試都沒什麼斷言,或是很多測試直接被 skip 掉了。但願你沒有把這份程式佈署出去。
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-Pattern Example: A test case full of errors, luckily all are caught by Linters +### :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("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.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 +it("Test name", () => { // *error:no-identical-title. Assign unique titles to tests }); ``` From ceb7fc74a4b5af2b86607ffbd2b6a28db34c75af Mon Sep 17 00:00:00 2001 From: yubintw Date: Sat, 12 Feb 2022 22:07:02 +0800 Subject: [PATCH 406/502] Translate Section 5.1 --- readme-zh-TW.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 69342eea..7268d4a6 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1611,24 +1611,24 @@ it("Test name", () => { // *error:no-identical-title. Assign unique titles to te

    -# Section 5️⃣: CI and Other Quality Measures +# 第 5 章:持續整合 (CI) 或其他提高品質的手段

    -## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues +## ⚪ ️ 5.1 豐富你的 linter 並捨棄有 linting 問題的建置 -:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +: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(…)。
    -❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5 min linter setup could detect this TYPO and save your day +❌ **否則:** 想像在某個雨天中,你的程式一直 crash,而且 log 沒有顯示 stack trace 的訊息。到底發生什麼事了?你的程式錯誤地拋出了一個非 error 的物件,而且 stack trace 都不見了,這會讓你想去撞牆。只要用 5 分鐘來設定 linter 就可以幫你偵測出這種 typo 錯誤,並拯救你一整天。
    -
    Code Examples +
    程式範例
    -### :thumbsdown: Anti-Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug +### :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") From 8f6a7422599fa9632bd1c221f066390e71d487bd Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 6 Mar 2022 08:51:09 +0200 Subject: [PATCH 407/502] readme-zh-CN.md updated from https://stackedit.io/ --- readme-zh-CN.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme-zh-CN.md b/readme-zh-CN.md index 67e0bcbc..1abaa83b 100644 --- a/readme-zh-CN.md +++ b/readme-zh-CN.md @@ -68,7 +68,7 @@ JS 领域的 CI 指南(9 条) 我们的思维空间被主体生产代码充满,因此无法腾出额外的“大脑空间”存放复杂的东西。如果向可怜的大脑中塞进其他复杂代码,将会使得整个部分变慢,而这个部分正是用来解决我们需要测试的问题的。这也是大部分团队放弃测试的原因。 -另一方面,测试是一个友好的助手,一个你乐于与之合作、投资小回报大的助手。科学证明我们有两套大脑系统:系统 1 用于无需努力的活动如在一个空旷的路上开车;系统 2 用于复杂和繁琐的工作如算一道数学表达式。将你的测试为系统 1 设计,当你看一段测试代码时,需要像改 HTML 文档一样简单而不是像计算 2 × (17 × 24)。 +另一方面,测试是一个友好的助手,一个你乐于与之合作、投资小回汇报大的助手。科学证明我们有两套大脑系统:系统 1 用于无需努力的活动如在一个空旷的路上开车;系统 2 用于复杂和繁琐的工作如算一道数学表达式。将你的测试为系统 1 设计,当你看一段测试代码时,需要像改 HTML 文档一样简单而不是像计算 2 × (17 × 24)。 为了达到这个目的,我们可以通过选择性价比高、投入产出比(ROI)高的技术、工具以及测试对象。仅测试需要的内容,努力保持其灵活性,某些时候甚至值得去舍弃一些测试来换取灵活性和简洁性。 @@ -2052,3 +2052,6 @@ script: **Role:** 帮助保持本项目的运行,并审查与安全性有关的实践 **About:** 喜欢从事 Node.js 项目和 Web 应用安全性的工作。 + \ No newline at end of file From f411b594dcff2036a25031e71ef323f4ceca2ef9 Mon Sep 17 00:00:00 2001 From: yubintw Date: Wed, 30 Mar 2022 21:37:14 +0800 Subject: [PATCH 408/502] Translate Section 5.2 --- readme-zh-TW.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 7268d4a6..337ee368 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1202,7 +1202,7 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs +## ⚪ ️ 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 拋出錯誤時測試組件行為。最後但並非最不重要的原因是,經過網絡的呼叫將會大大降低執行測試的速度。 @@ -1636,22 +1636,22 @@ it("Test name", () => { // *error:no-identical-title. Assign unique titles to te

    -## ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI +## ⚪ ️ 5.2 透過本地端的 CI 來縮短回饋循環 -:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +: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) 重構。所以回饋越快,開發者可以在每個流程中可以執行的迭代就越多,且可以得到更好的結果。反過來,如果回饋來得很慢,一天只能執行很少個迭代,那團隊可能會因為急需執行下一個主題/任務/循環,而不再優化當前的循環。 -Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +目前有些 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) 可以幫助你)。
    -❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact +❌ **否則** 如果品質檢查的結果在程式提交後第二天才收到,那測試就不算開發的一部分了。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: 正例:用來執行程式品質檢查的 npm script,在開發者主動觸發或嘗試提交新程式時執行。 ```javascript "scripts": { From 0a5e3a89ccd2cad96de23f4b3fb8b89b713f7faf Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Wed, 6 Apr 2022 11:16:10 +0300 Subject: [PATCH 409/502] Update readme.md --- readme.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 1e7990b3..7651b2b9 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@
    -## 📗 46+ best practices: Super-comprehensive and exhaustive +## 📗 50+ best practices: Super-comprehensive and exhaustive This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and curates for you dozens of the best blog posts, books and tools the market has to offer @@ -958,6 +958,38 @@ it("When updating site name, get successful confirmation", async () => {
    +
    + +## ⚪ ️2.8 Choose a clear data clean-up strategy: After-all (recommended) or after-each + +:white_check_mark: **Do:** The timing when the tests clean the database determines the way the tests are being written. The two most viable options are cleaning after all the tests vs cleaning after every single test. Choosing the latter option, cleaning after every single test guarantees clean tables and builds convenient testing perks for the developer. No other records exist when the test starts, one can have certainty which data is being queried and even might be tempted to count rows during assertions. This comes with severe downsides: When running in a multi-process mode, tests are likely to interfere with each other. While process-1 purges tables, at the very moment process-2 queries for data and fail (because the DB was suddenly deleted by process-1). On top of this, It's harder to troubleshoot failing tests - Visiting the DB will show no records. + +The second option is to clean up after all the test files have finished (or even daily!). This approach means that the same DB with existing records serves all the tests and processes. To avoid stepping on each other's toes, the tests must add and act on specific records that they have added. Need to check that some record was added? Assume that there are other thousands of records and query for records that were added explicitly. Need to check that a record was deleted? Can't assume an empty table, check that this specific record is not there. This technique brings few powerful gains: It works natively in multi-process mode, when a developer wishes to understand what happened - the data is there and not deleted. It also increases the chance of finding bugs because the DB is full of records and not artificially empty. +
    + +❌ **Otherwise:** Without a strategy to separate records or clean - Tests will step on each other toes; Using transactions will work only for relational DB and likely to get complicated once there are inner transactions + +
    + +
    Code Examples + +
    + +### :clap: Cleaning after ALL the tests. Not neccesserily after every run. The more data we have while the tests are running - The more it resembles the production perks + +```javascript + // After-all clean up (recommended) +// global-teardown.js +module.exports = async () => { + // ... + if (Math.ceil(Math.random() * 10) === 10) { + await new OrderRepository().cleanup(); + } +}; +``` + +
    +

    # Section 3️⃣: Frontend Testing From 5d6b470f08bc11e70f40fdea06281d3e6844ad90 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Wed, 6 Apr 2022 11:29:28 +0300 Subject: [PATCH 410/502] Update readme.md --- readme.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 7651b2b9..7bc1d1c1 100644 --- a/readme.md +++ b/readme.md @@ -964,7 +964,7 @@ it("When updating site name, get successful confirmation", async () => { :white_check_mark: **Do:** The timing when the tests clean the database determines the way the tests are being written. The two most viable options are cleaning after all the tests vs cleaning after every single test. Choosing the latter option, cleaning after every single test guarantees clean tables and builds convenient testing perks for the developer. No other records exist when the test starts, one can have certainty which data is being queried and even might be tempted to count rows during assertions. This comes with severe downsides: When running in a multi-process mode, tests are likely to interfere with each other. While process-1 purges tables, at the very moment process-2 queries for data and fail (because the DB was suddenly deleted by process-1). On top of this, It's harder to troubleshoot failing tests - Visiting the DB will show no records. -The second option is to clean up after all the test files have finished (or even daily!). This approach means that the same DB with existing records serves all the tests and processes. To avoid stepping on each other's toes, the tests must add and act on specific records that they have added. Need to check that some record was added? Assume that there are other thousands of records and query for records that were added explicitly. Need to check that a record was deleted? Can't assume an empty table, check that this specific record is not there. This technique brings few powerful gains: It works natively in multi-process mode, when a developer wishes to understand what happened - the data is there and not deleted. It also increases the chance of finding bugs because the DB is full of records and not artificially empty. +The second option is to clean up after all the test files have finished (or even daily!). This approach means that the same DB with existing records serves all the tests and processes. To avoid stepping on each other's toes, the tests must add and act on specific records that they have added. Need to check that some record was added? Assume that there are other thousands of records and query for records that were added explicitly. Need to check that a record was deleted? Can't assume an empty table, check that this specific record is not there. This technique brings few powerful gains: It works natively in multi-process mode, when a developer wishes to understand what happened - the data is there and not deleted. It also increases the chance of finding bugs because the DB is full of records and not artificially empty. [See the full comparison table here](https://github.com/testjavascript/nodejs-integration-tests-best-practices/blob/master/graphics/db-clean-options.png).
    ❌ **Otherwise:** Without a strategy to separate records or clean - Tests will step on each other toes; Using transactions will work only for relational DB and likely to get complicated once there are inner transactions @@ -990,6 +990,85 @@ module.exports = async () => {
    +
    + +## ⚪ ️2.9 Isolate the component from the world using HTTP interceptor + +:white_check_mark: **Do:** Isolate the component under test by intercepting any outgoing HTTP request and providing the desired response so the collaborator HTTP API won't get hit. Nock is a great tool for this mission as it provides a convenient syntax for defining external services behavior. Isolation is a must to prevent noise and slow performance but mostly to simulate various scenarios and responses - A good flight simulator is not about painting clear blue sky rather bringing safe storms and chaos. This is reinforced in a Microservice architecture where the focus should always be on a single component without involving the rest of the world. Though it's possible to simulate external service behavior using test doubles (mocking), it's preferable not to touch the deployed code and act on the network level to keep the tests pure black-box. The downside of isolation is not detecting when the collaborator component changes and not realizing misunderstandings between the two services - Make sure to compensate for this using a few contract or E2E tests +
    + +❌ **Otherwise:** Some services provide a fake version that can be deployed by the caller locally, usually using Docker - This will ease the setup and boost the performance but won't help with simulating various responses; Some services provide 'sandbox' environment, so the real service is hit but no costs or side effects are triggered - This will cut down the noise of setting up the 3rd party service but also won't allow simulating scenarios + +
    + +
    Code Examples + +
    + +### :clap: Preventing network calls to externous components allows simulating scnearios and minimizing the noise + +```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 Test the response schema, mostly when there are auto-generated fields + +:white_check_mark: **Do:** When it is impossible to assert for specific data, check for mandatory field existence and types. Sometimes, the response contains important fields with dynamic data that can't be predicted when writing the test, like dates and incrementing numbers. If the API contract promises that these fields won't be null and hold the right types, it's imperative to test it. Most assertion libraries support checking types. If the response is small, check the return data and type together within the same assertion (see code example). One more option is to verify the entire response against an OpenAPI doc (Swagger). Most test runners have community extensions that validate API responses against their documentation. + + +
    + +❌ **Otherwise:** Although the code/API caller relies on some field with dynamic data (e.g., ID, date), it will not come in return and break the contract + +
    + +
    Code Examples + +
    + +### :clap: Asserting that fields with dynamic value exist and have the right type + +```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 Test the five potential outcomes + +:white_check_mark: **Do:** When planning your tests, consider covering the five typical flow's outputs. When your test is triggering some action (e.g., API call), a reaction is happening, something meaningful occurs and calls for testing. Note that we don't care about how things work. Our focus is on outcomes, things that are noticeable from the outside and might affect the user. These outcomes/reactions can be put in 5 categories: + +• Response - The test invokes an action (e.g., via API) and gets a response. It's now concerned with checking the response data correctness, schema, and HTTP status + +• A new state - After invoking an action, some **publicly accessible** data is probably modified + +• External calls - After invoking an action, the app might call an external component via HTTP or any other transport. For example, a call to send SMS, email or charge a credit card + +• Message queues - The outcome of a flow might be a message in a queue + +• Observability - Some things must be monitored, like errors or remarkable business events. When a transaction fails, not only we expect the right response but also correct error handling and proper logging/metrics. This information goes directly to a very important user - The ops user (i.e., production SRE/admin) + +
    +

    # Section 3️⃣: Frontend Testing From 8a57337cbf257dfa995065dbb44ece9df239bf0e Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Wed, 6 Apr 2022 11:39:06 +0300 Subject: [PATCH 411/502] Update readme.md --- readme.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 7bc1d1c1..298246a7 100644 --- a/readme.md +++ b/readme.md @@ -52,7 +52,7 @@ The foundation - structuring clean tests (12 bullets) #### [`Section 2: Backend`](#section-2️⃣-backend-testing) -Writing backend and Microservices tests efficiently (8 bullets) +Writing backend and Microservices tests efficiently (13 bullets) #### [`Section 3: Frontend`](#section-3️⃣-frontend-testing) @@ -1053,7 +1053,53 @@ beforeEach(() => {
    -## ⚪ ️2.11 Test the five potential outcomes +## ⚪ ️2.12 Check integrations corner cases and chaos + +:white_check_mark: **Do:** When checking integrations, go beyond the happy and sad paths. Check not only errored responses (e.g., HTTP 500 error) but also network-level anomalies like slow and timed-out responses. This will prove that the code is resilient and can handle various network scenarios like taking the right path after a timeout, has no fragile race conditions, and contains a circuit breaker for retries. Reputable interceptor tools can easily simulate various network behaviors like hectic service that occasionally fail. It can even realize when the default HTTP client timeout value is longer than the simulated response time and throw a timeout exception right away without waiting + + +
    + +❌ **Otherwise:** All your tests pass, it's only the production who will crash or won't report errors correctly when 3rd parties send excpetional responses + +
    + +
    Code Examples + +
    + +### :clap: Ensuring that on network failures, the circuit breaker can save the day + +```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 Test the five potential outcomes :white_check_mark: **Do:** When planning your tests, consider covering the five typical flow's outputs. When your test is triggering some action (e.g., API call), a reaction is happening, something meaningful occurs and calls for testing. Note that we don't care about how things work. Our focus is on outcomes, things that are noticeable from the outside and might affect the user. These outcomes/reactions can be put in 5 categories: From 4989789a884d7a1a3e560558b1cd03d1eb6e78c5 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Wed, 6 Apr 2022 11:59:56 +0300 Subject: [PATCH 412/502] Update readme.md --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index 298246a7..5a9cc890 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,11 @@ +## 🎊 April 2022 Announcement: A new edition was just released with 5 new best practices, many more code examples and 4 new language translations +
    + + # 👇 Why this guide can take your testing skills to the next level
    From 6e62e0f81c3414e960195f8860c35528bc46a382 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Wed, 6 Apr 2022 12:09:46 +0300 Subject: [PATCH 413/502] Update readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 5a9cc890..6ca37b33 100644 --- a/readme.md +++ b/readme.md @@ -40,6 +40,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇪🇸[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) +- 🇯🇵[Japanese (draft)](https://github.com/yuichkun/javascript-testing-best-practices/blob/master/readme-jp.md) - Courtesy of [Yuichi Yogo](https://github.com/yuichkun) and [ryo](https://github.com/kawamataryo) - Want to translate to your own language? please open an issue 💜

    From b8d091eb70c08a6288e9dc3ba00461c2514432de Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 6 Apr 2022 11:26:16 +0000 Subject: [PATCH 414/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 6ca37b33..8f52356a 100644 --- a/readme.md +++ b/readme.md @@ -2132,6 +2132,7 @@ Thanks goes to these wonderful people who have contributed to this repository!
    Otavio Araujo

    ⚠️ 🖋

    Alex Ivanov

    🖋
    Yiqiao Xu

    🖋 +
    YuBin, Hsu

    🌍 💻 From 2401aed66e6820e0f4404e010c1ecd6ce2a2507e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 6 Apr 2022 11:26:16 +0000 Subject: [PATCH 415/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 28eecb0d..52b52910 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -350,6 +350,16 @@ "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" + ] } ], "projectName": "javascript-testing-best-practices", From d4d879bc5327064a55c822ff42a06fd03d091f47 Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 7 Apr 2022 01:47:15 +0800 Subject: [PATCH 416/502] Translate Section 5.3 --- readme-zh-TW.md | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 337ee368..71cdc629 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1676,24 +1676,39 @@ it("Test name", () => { // *error:no-identical-title. Assign unique titles to te

    -## ⚪ ️5.3 Perform e2e testing over a true production-mirror +## ⚪ ️5.3 在真正 production 的鏡像環境中執行 e2e 測試 -:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of FaaS code. +: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 程式碼。 -The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +龐大的 Kubernetes 生態系還沒有一個標準、方便的本地端、CI鏡像的工具,儘管現在已有許多工具的出現。有一種方法是使用像是 [Minikube](https://kubernetes.io/docs/setup/minikube/) 和 [MicroK8s](https://microk8s.io/) 這樣的工具來運行一個 "最小化的 Kubernetes",這些工具更貼近現實,且成本花費很小。另一種方法是在遠端的 "真實 Kubernetes" 環境上運行測試,一些 CI 的服務供應商 (如 [Codefresh](https://codefresh.io/)) 與 Kubernetes 環境擁有原生的整合,讓在 CI pipeline 上執行真實的環境變得更為容易。有的供應商則可以讓你針對遠端的 Kubernetes 自訂腳本。
    -❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated +❌ **否則:** 在生產環境和測試環境中使用不同的技術,就會需要維護兩種佈署模型,會使開發人員與維運人員分開。
    -
    Code Examples +
    程式範例
    -### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([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
    +### :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 +```
    From 2341759ae1bb44c66454096cb9f5a34d247479df Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 7 Apr 2022 02:06:54 +0800 Subject: [PATCH 417/502] Translate Section 5.4 --- readme-zh-TW.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 71cdc629..3167ae0b 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1714,19 +1714,19 @@ name: test-for-ci

    -## ⚪ ️5.4 Parallelize test execution +## ⚪ ️5.4 並行測試工作 -:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes +: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 上使用多台機器來執行測試,並行化的重點是要保持測試的自主性,因為每個測試都可能在不同的程序上做執行。 -❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant +❌ **否則:** 如果送出程式碼一個小時後才收到測試結果,但你已經在開發下一個功能了,這會導致測試對你來說變的不是那麼重要。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :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)") From 1987ad1457a3bd7c989afb92c76a1102e7b3c033 Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 7 Apr 2022 02:29:23 +0800 Subject: [PATCH 418/502] Translate Section 5.5 --- readme-zh-TW.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 3167ae0b..cb77df0c 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1734,19 +1734,19 @@ name: test-for-ci

    -## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check +## ⚪ ️5.5 使用 License 和抄襲檢查來避免法務上的問題 -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stack Overflow and apparently violates some copyrights +: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 複製貼上明顯侵犯版權的程式。 -❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues +❌ **否則:** 在不經意的情況下,開發人員可能會使用具有不適當 License 的套件,或將商業程式複製貼上,從而遇到法律上的問題。
    -
    Code Examples +
    程式範例
    -### :clap: Doing It Right Example: +### :clap: 正例: ```javascript //install license-checker in your CI environment or also locally From 669071b67c7a41d7f08b257b7e061ba9a9e2825a Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 7 Apr 2022 02:29:42 +0800 Subject: [PATCH 419/502] Translate Section 5.6 --- readme-zh-TW.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index cb77df0c..26545245 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1765,19 +1765,19 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.6 Constantly inspect for vulnerable dependencies +## ⚪ ️5.6 持續檢查有漏洞的相依套件 -:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +:white_check_mark: **建議:** 即使是最有信譽的相依套件,如 Express,也有已知的漏洞。可以藉由使用社群工具 (如 [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit)) 或商業工具 (如 [snyk](https://snyk.io/) (也有免費版本)) 來輕鬆解決問題。可以在每次的建置中,透過 CI pipeline 調用他們。 -❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious +❌ **否則:** 在沒有專用工具的情況下,要保持你的程式沒有漏洞,就需要不斷追蹤網路上新發佈的漏洞威脅資訊,這會相當令人乏味。
    -
    Code Examples +
    程式範例
    -### :clap: Example: NPM Audit result +### :clap: 正例:NPM Audit 的結果 ![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") From 2db738746923903a1ffc99b5e302ef177d2ffc49 Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 7 Apr 2022 02:49:27 +0800 Subject: [PATCH 420/502] Translate Section 5.7 --- readme-zh-TW.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 26545245..5c455f36 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1785,26 +1785,27 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.7 Automate dependency updates +## ⚪ ️5.7 自動升級相依套件 -:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: +: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 can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. +(1) 使用 [npm outdated](https://docs.npmjs.com/cli/outdated) 或 npm-check-updates (ncu),當有過時的相依套件時,讓 CI 的建置失敗。這樣可以強制開發人員來更新相依套件。 -(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). +(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)。 -An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8)
    -❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky +❌ **否則:** 你在 production 環境所使用的相依套件,可能已經被該作者標示為是有風險的。
    -
    Code Examples +
    程式範例
    -### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions +### :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") From 481543e320af9c2dd382ac49c5ac563d083e54a0 Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 7 Apr 2022 03:09:47 +0800 Subject: [PATCH 421/502] Translate Section 5.8 --- readme-zh-TW.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 5c455f36..c91094ce 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1813,14 +1813,23 @@ license-checker --summary --failOn BSD

    -## ⚪ ️ 5.8 Other, non-Node related, CI tips +## ⚪ ️ 5.8 其他,與 node 無關的 CI 建議 -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +: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 可能是唯一的例外。 -
    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

    -❌ **Otherwise:** You‘ll miss years of wisdom +❌ **否則:** 你會錯過多年的智慧結晶

    From ceedfaa1bb952b6d6531360ce12e16acbf84adff Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 7 Apr 2022 03:21:15 +0800 Subject: [PATCH 422/502] Translate Section 5.9 --- readme-zh-TW.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index c91094ce..2c901fe0 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -1833,20 +1833,21 @@ license-checker --summary --failOn BSD

    -## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions +## ⚪ ️ 5.9 建置模型(Matrix):使用多個 Node 版本執行同一個 CI 流程 + +:white_check_mark: **建議:** 品質的檢查是用於發現意外,測試覆蓋的部分越多,你就越可能儘早地發現問題。在開發會重複使用的套件或執行具有各種設定和 Node 版本的多用戶生產環境時,CI pipeline 必須在所有設定的組合上執行測試。例如,假設我們的某些客戶使用 MySQL,另一批客戶使用 Postgres。一些 CI 工具支持一種稱為"Matrix"的功能,該功能可以針對 MySQL、Postgres 或多個 Node 版本(如8、9、10)的所有組合執行測試。只要設定即可完成而無需任何額外工作。其他不支援 Matrix 的 CI 可能可以通過安裝外掛或一定程度的調整來實現這個功能。 -:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that
    -❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? +❌ **否則:** 在做了那麼多辛苦的測試工作之後,怎麼能讓錯誤僅僅因為設定的問題而出現。
    -
    Code Examples +
    程式範例
    -### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +### :clap: 正例:使用 Travis (CI 供應商) 的建置定義,在多個 Node 版本上執行相同的測試
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    From 8a653fdfc35593159eb5030a2b202f117be59a49 Mon Sep 17 00:00:00 2001 From: yubintw Date: Thu, 7 Apr 2022 03:26:47 +0800 Subject: [PATCH 423/502] Update category anchor --- readme-zh-TW.md | 79 +++++-------------------------------------------- 1 file changed, 7 insertions(+), 72 deletions(-) diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 2c901fe0..2b20f0f0 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -44,27 +44,27 @@ ## `目錄` -#### [`第 0 章:黃金原則`](#section-0️⃣-the-golden-rule) +#### [`第 0 章:黃金原則`](#第-0-章黃金原則-1) 一個激發所有人的建議 (1個特殊項目) -#### [`第 1 章:測試剖析`](#section-1-the-test-anatomy-1) +#### [`第 1 章:測試剖析`](#第-1-章測試剖析-1) 基礎 - 建立乾淨的測試 (12項) -#### [`第 2 章:後端`](#section-2️⃣-backend-testing) +#### [`第 2 章:後端`](#第-2-章後端測試) 有效率地撰寫後端及微服務的測試 (8項) -#### [`第 3 章:前端`](#section-3️⃣-frontend-testing) +#### [`第 3 章:前端`](#第-3-章前端測試) 為網頁 UI (包括組件及E2E) 撰寫測試 (11項) -#### [`第 4 章:測量測試的有效程度`](#section-4️⃣-measuring-test-effectiveness) +#### [`第 4 章:測量測試的有效程度`](#第-4-章測量測試效果) 測量測試的品質 (4項) -#### [`第 5 章:持續整合 (Continuous Integration)`](#section-5️⃣-ci-and-other-quality-measures) +#### [`第 5 章:持續整合 (Continuous Integration)`](#第-5-章持續整合-ci-或其他提高品質的手段) JavaScript 世界的 CI 指南 (9項) @@ -1901,69 +1901,4 @@ Took care to revise, improve, lint and polish all the texts **Role:** Helps keep this project running, and reviews security related practices -**About:** Loves working on Node.js projects and web application security. - -## 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

    🖋
    - - - - - +**About:** Loves working on Node.js projects and web application security. \ No newline at end of file From a9f2b2011bd6f12f433ece485038d23af61fb1dc Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Thu, 7 Apr 2022 17:13:25 +0300 Subject: [PATCH 424/502] Update readme.md --- readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 12274846..963d15dd 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,8 @@ ## 🎊 April 2022 Announcement: A new edition was just released with 5 new best practices, many more code examples and 4 new language translations +## 👨‍🏫 Next workshop: Verona, Italy 🇮🇹, April 20th. [Tickets and more info here](https://2022.jsday.it/workshop/nodejs_testing.html) +
    @@ -27,8 +29,9 @@ Start by understanding the ubiquitous testing practices that are the foundation ### Written By 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 +- 📗 [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) - My comprehensive online course with more than [7 hours of video](https://www.testjavascript.com), 14 test types and more than 40 best practices - [Follow me on Twitter](https://twitter.com/goldbergyoni/) +- [Next workshop: Verona, Italy 🇮🇹, April 20th])(https://2022.jsday.it/workshop/nodejs_testing.html)
    From 56d762cc64171fdda5534d796e97fb5f51c485fe Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Thu, 7 Apr 2022 17:14:02 +0300 Subject: [PATCH 425/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 963d15dd..ca45db97 100644 --- a/readme.md +++ b/readme.md @@ -31,7 +31,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - A JavaScript & Node.js consultant - 📗 [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) - My comprehensive online course with more than [7 hours of video](https://www.testjavascript.com), 14 test types and more than 40 best practices - [Follow me on Twitter](https://twitter.com/goldbergyoni/) -- [Next workshop: Verona, Italy 🇮🇹, April 20th])(https://2022.jsday.it/workshop/nodejs_testing.html) +- [Next workshop: Verona, Italy 🇮🇹, April 20th](https://2022.jsday.it/workshop/nodejs_testing.html)
    From 24458e2b1d20ad4fa3e4637d0187fd84a36b2c25 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Thu, 7 Apr 2022 18:30:04 +0300 Subject: [PATCH 426/502] Update readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index ca45db97..e202c572 100644 --- a/readme.md +++ b/readme.md @@ -44,6 +44,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇧🇷[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) - 🇯🇵[Japanese (draft)](https://github.com/yuichkun/javascript-testing-best-practices/blob/master/readme-jp.md) - Courtesy of [Yuichi Yogo](https://github.com/yuichkun) and [ryo](https://github.com/kawamataryo) +- 🇹🇼[Traditional Chinese](readme-zh-TW.md) - Courtesy of [Yubin Hsu](https://github.com/yubinTW) - Want to translate to your own language? please open an issue 💜

    From ee52b39f5f8daec36cbe364912e292d361e60f9e Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Thu, 7 Apr 2022 18:50:55 +0300 Subject: [PATCH 427/502] Update readme.md --- readme.md | 65 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/readme.md b/readme.md index e202c572..0916a0de 100644 --- a/readme.md +++ b/readme.md @@ -84,11 +84,11 @@ Guidelines for CI in the JS world (9 bullets) ## ⚪️ 0 The Golden Rule: Design for lean testing :white_check_mark: **Do:** -Testing code is not like production-code - design it to be dead-simple, short, abstraction-free, flat, delightful to work with, lean. One should look at a test and get the intent instantly. +Testing code is not production-code - Design it to be short, dead-simple, flat, and delightful to work with. One should look at a test and get the intent instantly. -Our minds are full with the main production code, we don't have 'headspace' for additional complexity. Should we try to squeeze yet another challenging code into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. +See, our minds are already occupied with our main job - the production code. There is no 'headspace' for additional complexity. Should we try to squeeze yet another sus-system into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. -The tests are an opportunity for something else - a friendly and smiley assistant, one that it's delightful to work with and delivers great value for such a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). +The tests are an opportunity for something else - a friendly assistant, co-pilot, that delivers great value for a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. @@ -380,7 +380,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 Don’t “foo”, use realistic input data -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Chance](https://github.com/chancejs/chancejs) or [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing).
    ❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” @@ -536,12 +536,12 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {

    -## ⚪ ️1.9 Avoid global test fixtures and seeds, add data per-test +## ⚪ ️Copy code, but only that's neccessary -:white_check_mark: **Do:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests ([also known as ‘test fixture’](https://en.wikipedia.org/wiki/Test_fixture)) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +:white_check_mark: **Do:** Include all the necessary details that affect the test result, but nothing more. As an example, consider a test that should factor 100 lines of input JSON - Pasting this in every test is tedious. Extracting it outside to transferFactory.getJSON() will leave the test vague - Without data, it's hard to correlate the test result with the cause ("why is it supposed to return 400 status?"). The classic book x-unit patterns named this pattern 'the mystery guest' - Something unseen affected our test results, we don't know what exactly. We can do better by extracting repeatable long parts outside AND mention explictly which specific details matter to the test. Going with the example above, the test can pass parameters that highlight what is important: transferFactory.getJSON({sender: undefined}). In this example, the reader should immediately infer that the empty sender field is the reason why the test should expect a validation error or any other similar adequate outcome.
    -❌ **Otherwise:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **Otherwise:** Copying 500 JSON lines in will leave your tests unmaintainable and unreadable. Moving everything outside will end with vague tests that are hard to understand
    @@ -549,45 +549,41 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {
    -### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data +### :thumbsdown: Anti-Pattern Example: The test failure is unclear because all the cause is external and hides within huge JSON ![](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 :[ -}); +test("When no credit, the declined transfer does not appear in sender history", 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: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: Doing It Right Example: The test highlights what is the cause of the test result ```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, the declined transfer does not appear in sender history", 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 + });```
    @@ -784,6 +780,9 @@ A word of caution: the TDD argument in the software world takes a typical false- :white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. + +[We have a full guide that is solely dedicated to writing component tests in the right way](https://github.com/testjavascript/nodejs-integration-tests-best-practices) +
    ❌ **Otherwise:** You may spend long days on writing unit tests to find out that you got only 20% system coverage From e5e919db708ab9dfeea5f43c309261850a471972 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Thu, 7 Apr 2022 18:54:45 +0300 Subject: [PATCH 428/502] Update readme.md --- readme.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 0916a0de..1edc39f5 100644 --- a/readme.md +++ b/readme.md @@ -554,7 +554,7 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => { ![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") ```javascript -test("When no credit, the declined transfer does not appear in sender history", async() => { +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(); @@ -573,7 +573,7 @@ test("When no credit, the declined transfer does not appear in sender history", ```javascript -test("When no credit, the declined transfer does not appear in sender history", async() => { +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}); @@ -583,11 +583,12 @@ test("When no credit, the declined transfer does not appear in sender history", // Assert expect(transferResponse.status).toBe(409); // Obviously if the user has no credit it should fail - });``` + }); + ```
    -
    +

    ## ⚪ ️ 1.10 Don’t catch errors, expect them From 6283ef1c4c52f89e90f6b2f4a54a33b4e3a17221 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Thu, 7 Apr 2022 18:59:40 +0300 Subject: [PATCH 429/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 1edc39f5..7ae13adb 100644 --- a/readme.md +++ b/readme.md @@ -806,7 +806,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ## ⚪ ️2.3 Ensure new releases don’t break the API using contract tests -:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. [Consumer-driven contracts and the framework PACT](https://docs.pact.io/) were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. There are a spectrum of techniques that can mitigate the contract problem, some are simple, other are more feature-rich and demand a steeper learning curve. In a simple and recommended approach, the API provider publishes npm package with the API typing (e.g. JSDoc, TypeScript). Then the consumers can fetch this library and benefit from codign time intellisense and validation. A fancier approach it to use [PACT](https://docs.pact.io/) which were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration
    ❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear From 2f2aa2439a6b8bbc3c82f42a417982a4d4bf38e0 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Thu, 7 Apr 2022 19:00:17 +0300 Subject: [PATCH 430/502] Update questions-answers.md --- .operations/questions-answers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.operations/questions-answers.md b/.operations/questions-answers.md index 47fcd2f4..2f71c638 100644 --- a/.operations/questions-answers.md +++ b/.operations/questions-answers.md @@ -4,7 +4,7 @@ **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: From 64a7e3fe800d62b5852a3673143dd2d1edfefa92 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Thu, 7 Apr 2022 19:01:27 +0300 Subject: [PATCH 431/502] Update index.js --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index a068a34d..e3c1c215 100644 --- a/index.js +++ b/index.js @@ -1 +1,2 @@ // 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 From 4b30fb5ea5d6e9f37f92647b248cc22ed56ceefa Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Thu, 7 Apr 2022 19:01:36 +0300 Subject: [PATCH 432/502] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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": { From d0ea212465ce0366a64f0cc1f5ee9bf672878bce Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Thu, 7 Apr 2022 19:03:58 +0300 Subject: [PATCH 433/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 7ae13adb..f5123694 100644 --- a/readme.md +++ b/readme.md @@ -536,7 +536,7 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {

    -## ⚪ ️Copy code, but only that's neccessary +## ⚪ ️Copy code, but only what's neccessary :white_check_mark: **Do:** Include all the necessary details that affect the test result, but nothing more. As an example, consider a test that should factor 100 lines of input JSON - Pasting this in every test is tedious. Extracting it outside to transferFactory.getJSON() will leave the test vague - Without data, it's hard to correlate the test result with the cause ("why is it supposed to return 400 status?"). The classic book x-unit patterns named this pattern 'the mystery guest' - Something unseen affected our test results, we don't know what exactly. We can do better by extracting repeatable long parts outside AND mention explictly which specific details matter to the test. Going with the example above, the test can pass parameters that highlight what is important: transferFactory.getJSON({sender: undefined}). In this example, the reader should immediately infer that the empty sender field is the reason why the test should expect a validation error or any other similar adequate outcome.
    From 3d8d4c6ebb874419ba8c30126fe8ccb585449e51 Mon Sep 17 00:00:00 2001 From: jorbelca <76847923+jorbelca@users.noreply.github.com> Date: Sun, 10 Apr 2022 10:54:34 +0200 Subject: [PATCH 434/502] Update readme-es.md grabes to graves --- readme-es.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-es.md b/readme-es.md index 3325f51b..c80b14b5 100644 --- a/readme-es.md +++ b/readme-es.md @@ -893,7 +893,7 @@ Crédito: {

    -## ⚪ ️Copy code, but only what's neccessary +## ⚪ ️1.9 Copy code, but only what's neccessary :white_check_mark: **Do:** Include all the necessary details that affect the test result, but nothing more. As an example, consider a test that should factor 100 lines of input JSON - Pasting this in every test is tedious. Extracting it outside to transferFactory.getJSON() will leave the test vague - Without data, it's hard to correlate the test result with the cause ("why is it supposed to return 400 status?"). The classic book x-unit patterns named this pattern 'the mystery guest' - Something unseen affected our test results, we don't know what exactly. We can do better by extracting repeatable long parts outside AND mention explictly which specific details matter to the test. Going with the example above, the test can pass parameters that highlight what is important: transferFactory.getJSON({sender: undefined}). In this example, the reader should immediately infer that the empty sender field is the reason why the test should expect a validation error or any other similar adequate outcome.
    From 8f84be8ebde5be9195c2caa5894f91d4ef6fd9d9 Mon Sep 17 00:00:00 2001 From: Simon Ingeson <44818+smonn@users.noreply.github.com> Date: Mon, 11 Apr 2022 17:52:19 -0400 Subject: [PATCH 437/502] Fix markdown issue This was hiding the following chapter (2.10) --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 631c1b29..5d2638e3 100644 --- a/readme.md +++ b/readme.md @@ -1023,7 +1023,8 @@ beforeEach(() => { id: 1, name: 'John', }); -});``` +}); +```
    From 50a00c724e7ed74f4fa57fa47bd763f01f18fea1 Mon Sep 17 00:00:00 2001 From: Simon Ingeson <44818+smonn@users.noreply.github.com> Date: Mon, 11 Apr 2022 17:53:07 -0400 Subject: [PATCH 438/502] Fix chapter numbers Chapter 2.11 did not exist, renumbered 2.12 and 2.13 to 2.11 and 2.12 respectively --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 5d2638e3..30f643d2 100644 --- a/readme.md +++ b/readme.md @@ -1063,7 +1063,7 @@ beforeEach(() => {
    -## ⚪ ️2.12 Check integrations corner cases and chaos +## ⚪ ️2.11 Check integrations corner cases and chaos :white_check_mark: **Do:** When checking integrations, go beyond the happy and sad paths. Check not only errored responses (e.g., HTTP 500 error) but also network-level anomalies like slow and timed-out responses. This will prove that the code is resilient and can handle various network scenarios like taking the right path after a timeout, has no fragile race conditions, and contains a circuit breaker for retries. Reputable interceptor tools can easily simulate various network behaviors like hectic service that occasionally fail. It can even realize when the default HTTP client timeout value is longer than the simulated response time and throw a timeout exception right away without waiting @@ -1109,7 +1109,7 @@ beforeEach(() => {
    -## ⚪ ️2.13 Test the five potential outcomes +## ⚪ ️2.12 Test the five potential outcomes :white_check_mark: **Do:** When planning your tests, consider covering the five typical flow's outputs. When your test is triggering some action (e.g., API call), a reaction is happening, something meaningful occurs and calls for testing. Note that we don't care about how things work. Our focus is on outcomes, things that are noticeable from the outside and might affect the user. These outcomes/reactions can be put in 5 categories: From 75f4664c7f11caab833a1790e702c3dbfef041ec Mon Sep 17 00:00:00 2001 From: Simon Ingeson <44818+smonn@users.noreply.github.com> Date: Mon, 11 Apr 2022 17:53:51 -0400 Subject: [PATCH 439/502] Minor grammar --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 30f643d2..b519587b 100644 --- a/readme.md +++ b/readme.md @@ -1065,7 +1065,7 @@ beforeEach(() => { ## ⚪ ️2.11 Check integrations corner cases and chaos -:white_check_mark: **Do:** When checking integrations, go beyond the happy and sad paths. Check not only errored responses (e.g., HTTP 500 error) but also network-level anomalies like slow and timed-out responses. This will prove that the code is resilient and can handle various network scenarios like taking the right path after a timeout, has no fragile race conditions, and contains a circuit breaker for retries. Reputable interceptor tools can easily simulate various network behaviors like hectic service that occasionally fail. It can even realize when the default HTTP client timeout value is longer than the simulated response time and throw a timeout exception right away without waiting +:white_check_mark: **Do:** When checking integrations, go beyond the happy and sad paths. Check not only error responses (e.g. HTTP 500 error) but also network-level anomalies like slow and timed-out responses. This will prove that the code is resilient and can handle various network scenarios like taking the right path after a timeout, has no fragile race conditions, and contains a circuit breaker for retries. Reputable interceptor tools can easily simulate various network behaviors like hectic service that occasionally fail. It can even realize when the default HTTP client timeout value is longer than the simulated response time and throw a timeout exception right away without waiting
    From 867a2a008966e4778882fd84dae3ee7e2f8d5e84 Mon Sep 17 00:00:00 2001 From: Simon Ingeson <44818+smonn@users.noreply.github.com> Date: Mon, 11 Apr 2022 17:54:00 -0400 Subject: [PATCH 440/502] Fix typo --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b519587b..5a91cb28 100644 --- a/readme.md +++ b/readme.md @@ -1070,7 +1070,7 @@ beforeEach(() => {
    -❌ **Otherwise:** All your tests pass, it's only the production who will crash or won't report errors correctly when 3rd parties send excpetional responses +❌ **Otherwise:** All your tests pass, it's only the production who will crash or won't report errors correctly when 3rd parties send exceptional responses
    From 4070c5af27e06d340ae24eae4e55d923f7396850 Mon Sep 17 00:00:00 2001 From: Simon Ingeson <44818+smonn@users.noreply.github.com> Date: Mon, 11 Apr 2022 17:54:10 -0400 Subject: [PATCH 441/502] Use JSX lang for React --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 5a91cb28..f9d05d18 100644 --- a/readme.md +++ b/readme.md @@ -1201,7 +1201,7 @@ test("When flagging to show only VIP, should display only VIP members", () => { ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") -```html +```jsx // the markup code (part of React component)

    @@ -1228,7 +1228,7 @@ test("Whenever no data is passed to metric, show 0 as default", () => { ### :thumbsdown: Anti-Pattern Example: Relying on CSS attributes -```html +```jsx {value} From 0f14693fc8a3c931fbea01d8108aa89f5a113c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=9B=AA=E6=B6=9B?= <99980283> Date: Thu, 14 Apr 2022 17:07:31 +0800 Subject: [PATCH 442/502] image: translation some image to Chinese --- assets/header.pptx | Bin 123258 -> 123258 bytes assets/zh-CN/bp-1-3-parts.jpg | Bin 0 -> 183192 bytes assets/zh-CN/bp-12-rich-testing.jpg | Bin 0 -> 78756 bytes .../bp-13-component-test-yoni-goldberg.png | Bin 0 -> 252049 bytes ...4-testing-best-practices-contract-flow.png | Bin 0 -> 223587 bytes .../bp-20-yoni-goldberg-mutation-testing.jpg | Bin 0 -> 179509 bytes assets/zh-CN/headspace.png | Bin 0 -> 474856 bytes readme-zh-CN.md | 12 ++++++------ 8 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 assets/zh-CN/bp-1-3-parts.jpg create mode 100644 assets/zh-CN/bp-12-rich-testing.jpg create mode 100644 assets/zh-CN/bp-13-component-test-yoni-goldberg.png create mode 100644 assets/zh-CN/bp-14-testing-best-practices-contract-flow.png create mode 100644 assets/zh-CN/bp-20-yoni-goldberg-mutation-testing.jpg create mode 100644 assets/zh-CN/headspace.png diff --git a/assets/header.pptx b/assets/header.pptx index 925889c9b7d2b533109f2de5174f881dc26f7ddc..57b1eb07bc9be21a055849706e628541d2409fb9 100644 GIT binary patch literal 123258 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 BjHLho literal 123258 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 Txuq-6IFK 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 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 0000000000000000000000000000000000000000..7a90761124e415bc0d8a1b315b9214ae8e38f805 GIT binary patch literal 183192 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

    MP_#S5s=yrw7DkCTrcRg& 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

    !*RRg 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 H`9AqyQdg9Q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e943ddc2ea80e6ad84543290ef5568285b5fcec9 GIT binary patch literal 252049 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%0A(#F`u5t|k#> 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 Z%UQN(7%A@2yvbX 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�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ℑI_4cK2MBg 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 A0RR91 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bd2c6589e11f9b6eb7ea13decb9697d4fd1c667c GIT binary patch literal 179509 zcmeFZ2UJsCyDl091VunVKtzbr6@gD_3P@C>$%hCENX@6z5JObDghWy4) zq)SVrD~L#!1PB2E=?Nt)NC$h&(*xKE(cW`xc z_we*Wc>DVK2LuKMKYsT7MR-JHRCLnISIH@`o<=63)tTIZPz{s*T0zcZ-cjJ^z&N=+(sR^5%nb8rF2Ndyo1KiOXx! z6d1ov`-f%!Im5#Kw=DZN!~So($dJQa`@oyWB@BTtZtvfAg$keYr zNH}u$E$r7UY+VLHVmm`ZC5aKhD@;$6L08RirRLzHN4&infbwdmJ&-n4*Xb~Q8qcaY+yFER!=T`Ns{ zkeUq$iN3PBQ--gCbHs7G1qnc4;I5q|S$z+}5918LjruM_b#bi?q=kBH9}ckJgD}%z zv~lXDGt7&`KD!5DS%gX$K``@s5W$r_Nbx+&2#m^}dl0hM9wZ_VH{`dQvj?G9f@4o> z!};jG^+L8@1E-{L8I0^N@OuzME=PKN4`MUGjM;;<^zK2bM*942@_xhrcd!2YTK$LP z3Mt1tJy4B?UXHD-oL>GNR+-OGe82PJE6l4ZE%-st3+JytwrzrpzCDCn zffJ|_&d%dyrtU%N;Cm2@ikn~t_}g3lhb>J5bch~dKaN7IEY|Iy!8qNUlUqk2RaXD< z!M}L)fA#GDRbKx?=k(ii*uu6=_8A{F6iLQ1Uvn1qfvEV?>C+3x-Ks z49)W3onC>w`WVEB-PCChR3evH)uZd9_jviAj>T8c4^6`8H_`mq?=F7;4|DA~y-{=n zXSW5O&F4Tm&&IHT_mSl_+rB&7M2~^>*uPHr+7Can+6p1mFYMagXwn&Ry&iy`SlWZA z!k{GAbv-5+BA}Y(5FY{8K z$+3_(V$mNb>WqUgb#wSG?Ll}J`}QD{Z?nY8>A1F4Xpq|CbU~9ZF7z%9ClusrTgej4 z|$4+Wogc*dKwW4kT6BWCgCecA1x+%j^T-Z`sE1QO`RJ_$L;Cxf?(g ztU@`=UH^_5rA65}PCR8dO_-f~dzSmNZdWw53|@ZVB?}oe(t+h=)KrJC4lV3K_%`z# zyPGUO+1B?mGiUMgZYz~Zrq3PYO{gdRd9#!Y)tQJ9>WynVYBqV4YJwtOJMA6O(K!Ps z^Xg6XTJ81O@?n@)-MXRu_@ewu(jPc?7;xHiSrxQT1*jki8|(*QEdh9l{CBN!NbB@z z+1mO6{aeUmA@)87y(+z0jY<0aHAmiF8{zq|D2zT15Wo{XcAKzb>_32!sW@u6P^8>0 zUh*EJ2A@9)yqiE&o;!8dN;At9jWiX_Tn1mb(IR*pzm)kRC@eSA_nt$AqbqD^K-cCzFv;ct(;+Oeww*6;m( z=FDborPfmkD(`ZJqm+)OKR__eQy0DCh-bcullyL%!9#qNoefHLy6VbEIjcW>r?I81 zKd~LKgB;n#;hoTe+Gzsc{Nr7=xNu{@hhBfgiT<zc530rLUHgF}H_OjR16+lTPFk z=Hwp4HR*zXL!aa`^W?!i-w&^zqsqDh%J)S=HVI*y2^1XKpvVMspk)KZ-`St5lxV4ZWJccA)jT34#kYeaWtP)Q;WBy=x zKI1@x3|ok282!3mikz-61nr-TOw>pIbgO>e_5My>t?=jeTC~1q=#sPUBeJy>?!vc; zcxKIlrT64uxjpV|#{p+`I{9&9L(xdM~-=9yk@DW_%K-OkllU`?oq7XmRxLse4S#1j|h^Q(Q%(9ZyIOJ2kC>5ln=p`RL%lMfntPd2ZecfmK zO~uRQHk}+YBnQh#df#7z|D055i|{FnW!M&4mlBWl5Lo{8{GkDJXwTeK-;&*0(qySF ztd+_gq&VG7XM53iC$guhLi1_QcM_T{k5W1f@Rv3oI(-UXy>1ii78jlO5PIpy(<5p| zSE;pu?I5_Y2l?R9^xnbC;0%z<&@gAHk|O-#jM2|HD*Zg;nQfm%Z3Z<9%EM3hGs*(HqeocaL75wJaQakk^MWo(uK@T`z63XJEDlIPQiZ)hxY`SSkFW$|T# zXNa^m8LcIY=>G7xQKeGX=ff~VtQK|{CWM7x3>bw``X*CxvQm~2%!e+>l<&^+BPsVD za^fVPn&Gr*AO6mk%J;Cj+%|lB<``|rgQJ-DvSG{p(0OxW$s2p`jQmojR}lpw%8oBm`-+J4D3kTe%j2>Ecn($W;yx(AgSz z^UZOOKfGH)byfAx%6+k^iw}9I%&qyb_8UiRGGSDMBZg~lz{TRBo200TW8fu@&2(po zc;SV$RuBIGBZ-=j_)Q0i-@J9Xg$8B6RtTcQv_8k3% zYYrd32dPYWh7o-a&W$Vj8hMKo21G_dk*g;S3@N{KBic}bwoMG1na<}q$)(ocW|@Y? zW0Q4p?q)yZZU={m9TiMq;(=ErDFba>^A+L=0N!e#%#euD>U}Q$SUbtDK{!x6-yro~ z%fdIUc;h!K)+lf8A0IXa>&8AR4CKpLcueMR8NO-241|`Y4C`LJS11^o@t)&bkj!$v zn2(|s;B#H{`#D+wQ$Xl8fSc}pK@=VE+4!i?P{j~>g5KPon0e|EH2ciH;&uY!=8=xC zhh8?W?89gwun02C{ z@V*O&JH1Q(*jc14SEab@`%X){cq&cWfZNai%;y#@?jyn6GD8epjKOg)7#Q>>X-%@z zZDbU&kt#&wGb#nV^_F@Bqi_awn&-!xr9`6iu0@7lZlFhNd@3ou0pRaijGA>jFXc*F zP-djmq8D?@yTjjcN|qPAr;j13$MX4*6np2*zRf^Ap&wk#5@0lt1zjYGBb z9;I6Yf7R5~_4{b{D51O6ip1K7KGoL!oh-}qs=|k_^=nr=vJ)=@7E}cC%V}u1kHJBN zivV36tWpKW%1TSUI2VQ~t(^0W7cVu-$zPbp{nZi!n%z;S(hmgPYfAm{fG7K^<6>|0 zUO8C`+6#p|)$gl@$m4z}q7C6}a2DZqN_q5cyxt0B#HgY6c1|7M-d?dPmaq}H zga=2XD3H&x;0);&K3(Pr0|!{2IAZH4wgrQ$g(R%56(@C_WOE!xCR@x`yQ(l#A<~K>Ps_3X=fh>EgJQ2nmZdh zeb@Xb+xU^;5%GX{JHb_ZDfq+t?!aS)Q7=fT8D?-rP7h236UtIYz(t6BfMxS>cq~yH z=$=ZAY&6y^DFNpC^i|$2*sW?2OG=#_tHLHutA6n+Jt%PtpX>_JE3{`hV80v}S~8?d z&|=s+4Bj>Ze{&wjk=|2=FT4Xx+tl_i-S>xy*eeXDoz2Nq&94;xBLA>m^M;&>yAxM* z?3uZEX;}4=%^sw2=)xDRAzz)ZTO@tQSzCvDm&nPbOlLdvO5sJL!h!MHGs|D(>zt`V ztN>iICzxR0BgOC3hHtKtkxX3Qva61z^eY(uYNOdZuL>2;oJEs4OjMy|yeilLY2V8G zIj~A$+IUkPP&8RCWXLhebZSCs8(bXb5oc~A{-RN*5OXO@+QjL*tE;TSEP-2>n0-Ii zv0Ii;DOpeQi-H|Zh>YimE3r?)Yb2NwZ1q*Zh(rrWfN_LLz*QJca-B1jJN=E*zXyq` zCF=wSt&tu!boF-6jts&ogr4f#GoYM9*$hJyAZZUGvrM%HH1f$+9F0NtrToS6%j6!W zL61M}v|RkLFGAIg?H>=z&v|rp9%PYkPpd9mWsyhn77z`j(Ho#E$oD}x--Dhl)Y^ln z&7s5ws%=wZOqC5>*yHO0&OSGkPd)!twYzOa|9{XCqUOxd=`$mJ}@R?;z-HdKM|TWWEA( zlwm#LJkk2FKsB$tZO4v0E@$RrsClxj;F`!0hqG&Y5C}&sh&4@Mi&|O^H=deOKZ1md z6NNEG440D07{U?cmd={DHDZTZ>Ms5!#MfW$i)tfNVX3=oCeh9Au;f0O1_tkKycIYL zK5%+hL%r(*@^xDFAmY8gu5<5uyqEpo`dDD zilHGciAMW&z0DV^sGndye3vt{MIJ`mJC{0mq%I4vT-lbuC5E2a_YFJ`TZ~xC@C&>J z*b0P9c@*U=ru_`4!6EAvoBjBo9*pAh`TF&M>tUOp2jS=0r@{VYbNXG553pg=Xo(;> znqcXqWjvXuvki8@VJv9%W^Fy)I1!OgaUQoHZz4qGg%rqMk-v8#?{B@&=|5#$F6A-@ zchiXK80fHp62=RFDuuBT)0V>Xe^9)2p|ScGgWoUs&VT2z8}N&X)Kbx`exj7FOWtiM8Fba~g`LDW-74-;=O~|w= z^v=^hXQe=vc*aufF|j-5XluTQ8Fnd#EI;r4v{fS!)3*h4WB< z2Bs&yOG69^8i(M{uqVo}-J2Rh;zV0yq=zv^(1;Z^SAj>&BZ!*R^LMVTANWXa{UZ8# z=JLc%_B_jgGqMFe<|lN-`3qx_xinDf2N(9%6?l~b<5t~_T%}cId{Xj^Xy!JQJFnZ+ zfIqKjY`6V1|7*V0yRxXA`)MS*V6~{~Vp|L2VE5w>AFT{isCVW|t77Y`vQkk6yCmib z(AEefUerK?ZAmAzt%?HH6P+#Ryb4VQS2)l`m_V6&K~prx{x76}&w~9}v3;tYMRu~= z$s>6QxJ{r zP!JEg>mFx{S#)&cu#q7)J`D$KaW3W#OPYErf7` z<3Kx2iqd5L%}g`lFnrt|!R$lS_;Qa*Cy>desA-RFdxbcIJ7H%>UggZlmu)Nd&#$yQ zFl!pW>FL>zPFQI5Ju8$Pd2#yEi9xs@^?U%;T#-$#@7LYsd5delWuR^{eaXBaWy21thJagc`X@p2KkoWXF8?2*7~mHl6isZtF#}6mzWt`df zMUkWc#*!R7{g(5s>Djtq8tlM?Wns|y@y4S3$mLXXIB?b^3@hj~hQNwOa6|>}$y()wIj1}zu`Jne z`f;Fg3e6U4omOhoi{dG(M$;B;(MhTXM@qp$FO=v(cw?}|vT&K9TQarkK`>0PXi6$d zlfwUkD%OZ(ZKtw&8dgfNO6@Lk<~-(Znr4Rb=FEzW+1BSlXYxSRZ^vzBe>TnYLM zYD?~`zAww0eZK+7o}5?3o(%f@PYnx?Rx^&S&ht^4i-dMYD9)+L)e(Ei8s~4|Wil@(AbNH2l)H_U8l6 z{SXq_mhkl#FrV6$=h|zny|})H;v9I`@gUj0ON?IbjV`?~>XmBo*l7zsO5*V23KmBK zcF=9160Fldw{H~Q`}1$1z*u%S;6YrbD<$~BqYNZ#hN`5mj@%ybN%gZ6U*k1~zQA%} ztO2`XwjATsz@{nDWA~@1(oVzkO|f72a_VyR6n<2qBXJC|b>11kPT6q2_y5F=-^d(= zH7GS<8Lf?&l9uzOYL6e0kvk*n28OYXLfz#b?Os0^n8{+NHoDJ`a%3V_g7tCNbyOeX zxrkc7;8p6aC|J7;^rPtfPMD7*i*aEgR_~9p&XC90P7Bo%BL)dlM&Se`lKp} z%;=)+uCl)94czGBv;cmYpRUm&qc?!^d4C^sH_u^#$=uSz{Bq4wNVH-yJ|0-UEM59P zP^bUz45-}~4u~R#wXsh!OxQ=Y7n$DGI1!zMvok3}Wm(O3=1H^4`dd%TNZ6U1SX6qT ziooE$%@_avo5j{W*++djhj4T%=U~jFrO+bjNH#-k4695^YU!XDRXIlebhA7+w(57Y z_#@{myH#v?4|0&G1hQ(!iN}DD4jJi>ERSbXak>JxZe)}Vr%kxo3zt)7?p~Hw`1il{ zzw!6~694JK3l8?!cX9m zU#72l`JnjOR@8$Dx5TPP*D44K`t8Qb*GIxIV0H4$Q{N&7qrvcOr9Q(|QRDU1C($vn z3P1`5kv9sI*f8|p(Xt;t5}7b%3QRutS8tfTcY!eMecIwQ8{U=c6R+Yn+~Z$4zOXqs zkU8E>(8$~=G8_M5kWiS^=k*X*8PTi?V-=dlR&SO8+vo zp`7qJweP|TvMMKfxYX=+deJ3E6v@{8M#fzpzNO^U&p$c(L0q>10^OuTMxsw^i(XJZ zWg)(5UNM|BvTTvpqp5z}>AmAXi1D2Z&fQ75i7vd8FhBwJtman=RG4rVEc;QM)w2+aX^>5SOnnLHi4{NJKcMN)1@2nDFzm+8^Tr@N(nvV&)|LGIm9+XRD%IG;+ercxbXrN|7-k!NwS85(2TTiG`035`t?;Tv z^U;mUoEroBN#5t2$zP;f#!>Fq-2K;TR-Ftau~|8#HRM;--b}PzpoBQ(ajnN@U?6I> z2!CegfuljDs=tatgLeTMejua%{%nUdJiJsbath5w6N0Ft5k-A z-h=EnyS}@UR3AdQHM0i+0y0URssYqHySe(vP{st(J}U&y?=;^xZ@@J$)|h_$lCX=_ zqi(4eLPbt46&0zmZ8vz`X$hlxE`VhbW!{Akcu|A^Yedfj;3DCkLz|)S-DcHx`ySOf=j50jEcw|?F8Q|h#ZTTn02hn@2Syy&of3+67)juq z#N1$PMQZAy1*GBaynjZ{@7xzPe^B$?u+ZM&a`C`*=Qp{Y_e$0we}r~L6Ft8z_>VB^ z1D5^B0X6XyFEz)`#O1=t0lQU?mv#$;x-K@FzBv*@6b2&9D(R#b7=(7AyF$@-jJY4m zFV6l1DoD*ud_>`%+gYI%1>S6HA(?&C_#enlcy+=H&0Zn46C)v_QG>o^nbox;#D%%o z--92`t4@=A8|LgYS>2Y{LeGvz8BomMSe{CS2o-z1-?r1ySlbw|Dd?RG@v9zRSQ*}A zW47Qb@Y1EyZp<-2fu=r6rFCS|pj-g%PkL|oM!xJsM~Coxd^gaa=b5mt2xop7nmZXv>!+H;-M3X9-(!EYhN0KETS zrmAbPu_5hZ)?0YZ>dlc=N7(C>=x$33m~>w|n`<`{Bg9g}T&$(zLA>(te3ry_)Y#YW z4yUO8=MKqCP1!1%$3&3%do}bcgzH8VwC14Ipg86%sU{)1Q4b7@5($m2Zvm5*NpD+S z^#~6~Nx{0K-Ktqdnm=_iP?BP_nk4NfU0hu+Q8w_9oQOaLrTU>@J$sl9_v=C3n)0Rj(%=% zGW_7mmvBYIHD&&ee4G8nWr3}DKq;_ID8j)q?!6}T7={aQ1^c%7>VtQEZ%c1jXAALU z(qxo=3BKCA#JO!%%3RLFRGJjTa?sZ692va5R`O`0nrK^1{7IY{JwS@%ASU)8!D}O} z@E=N@_zK%Z?H6>VsiCZ>$;J7+CcGgKL!%I+RP%q9xqbXtU2kFBvFtGGnG!rai%dTM z`OmBS>KwBG6;SQ71-xNtBOSu?>5JC8@ffam3w@F3vc`)nJ!ix%m$#^9_aJ@a*afMK zb8`A4KL%~q_166{4aHeX;~+!jhLxzM5z{}XgvAzQNt;B?xK^&cXXpI6iat;t=p?x2MV9HvQ*gD z>AlU1y@!60s!ZDW7kjQEy`7WbO>Xz!KO&ZtAPy?XZpa{gd6$dRi#g08ttaxnYREHf7-Fl_7VAyi~|h>)kXWzj8lV~ zj!l=ZxvoPE?8uEx6p+1B50D7xo1${VCSMLu2+>JT8+905ovCnPHgr-lz7|E>>X$43 zFw8d8YHuGOE-&JeFZDlF_c8eHvqx7Bww||;?K~42Vr1z^Qg30h2)Nr>3=3@vDIK*_ z8wi#c#=||iGZgd>^$z#}ju=q+3C4?+1JEr7`xu8Sq&2QE)Sr7Ycu3Ee=i=QPss_l? z#$Wo!^AhEwFCLCTM_KKQ%QX#cU_Jm83PAw3a_%WddR|9DA`Sl3PfB(Fa4=)GSM2@4 zLk+HRZ6}qadjekFicPanukOaN{I#HUI$PrlN=M0|1T-tSfWuO6xSSJx`tWb{33Ox< zzMDJ_W`jN|zqLAJVAN>MP2^)7a?o5wy*Ity$+Uajx&N4*uX3;r z_4P}-jxiubx0_C2A4NOWfGF}(Aj=Z4q>)_>Z_O$YpdIG_VfNa1os<{?5Z~qX5%XD9qJ`l_-gG394N_- zJ>L$lqHOvr`<;3Myhl3WfC_23ot_FZmY(z!%fp<`6a!hHmPuVQpB?dcHvF?(Jn?F|o?D`!lg1g_3)->U7(b>>r z_fmvTbg*(>#I9HQIs}W9WSqT5GJL{=ZNN3x=~7#8k@)hFXni=HFd2&fbjM#v+!=A4 z_!K>bj0^Rst1un;rqNaMEb2PV4yyKO%rz%jeY`7UBg5xs{aSINvrDSW^3X&rLNCA9 zx|sQQhSsy{teQs;Ue4F|Gy62FUS0}e>g-k$WwHIR8p{qmmv`1N`fkEgtYATEt~SZL zmS3^D%)Gp` zfSnq$nVxJNDMptNQw8T5cM5_U(y5V+CQ?d?t_0*uubT6x?>07l)lT(!u4ek1OXPm1 z7Xh7wOJGog2FhB6Q6Rpn(vOjt{d%CJ0CR;j=x=yT;Aia*x4yq(e2+AwF5mexL-Vy% z70i2ebe7621_TH}f?KwOej(Dq!fFn~Cbt{oq@7EBxP6k$5Zcr;LAfc4DH2KNWYK5O z<8@>oE^=*iM&A)EU=Qr(kr2zcX5wK$HJj7BV_QAdEk%_;U{$Js zm$)*oz~STCr3lY=_NGZe0Xlc}#)8e|6>f~L$roQmgUVMoFabZE+KwZ4uv!D^(XEzG z$p+a|(vteuHAAQ|4sVBEnzNiz5`0g<>G8q0jwZ=UinvyOefITSRIcjO9z>L*hB4kv z+=HO_FJRR%S9jkS2vPuKizosnh(QI?yYJM}IhA=|mf@d{h?wMO(eAa^djO~SWa;D`o{LN}W!LD`^$vF4(wq`>txq2*iVf>;}E<$-{ ztzg$?b=B{NPD7PU=dv6tl>H|?V!3HLA$$TltVt5`G7$N`fx3P$2N0X;joh+Jn%FJ* zD)J@Q#YnoiaCF4`^PA9~uz<967+HdID1=cmiEhRn0qRHE(>!V)zNLibpVfknLZ_4> zP(DO=DfvOQYG@_7sCQ=+YqM$xyAP_0Et~NIdl2s)^>!HnhSKugN%%3$pF7{A#n^euAr^tSBdP9c?ig=#Vz?>O&K zO(`@^NzR#8#II3*zO+OlgSsu4p;k<2M{jJ_`60IbBrDj+VMiT$x@P3b?+0{;l^OxwEEL62U2WV zqk3?0km3lfqY~|ZHy^3H-8byDsWa?yAkpFCXPGM(V|^~?i7tY85S$JY)UD{Jk0(Hd zD}PFF+d+mnO5@r{wUD68udFi+!J=X_U*C6%`Jj5Js^X4QZKmeJ2l zQTET#av+MPP)9H)bZYd+^w8VDr`Lpp|+z8o} zlShz$9!5tM9$Z>zIlt^FaasXau{%E0|1!|dHZA3j!lWCNgj)6kkwT52wbcQtwy$5Q|hx;ciRkf7U&1&jd@`0yo zY-;Pz^+@{#a5-I=c<|cAmM4$8G0IJ+6Mtalke zOgRUjbjfs6RNC~Cttj7d%QH^$FPx735aSkhxkXpMoX+$CEzD#aRWjgln+ZqNn&=7F zhVmHdQ5|?s-v?;{CGS<6ape?>!}Bn`#n7+iibG!AZYi$tKk1$wtAb$H4Y6l1w*l8l zcsNl9z%k^SC&X$8v`T%axYM+nrX6rz4N`Xzg$qJwk{jY`KbN0A{NivODtTqCP{NNg z(${5Baz_ttA)P$llH={`d@tL5Vj%-x`ic@7&`?qSh4Yz8w^PAMa{6#pwr{3BV>P@d zWazyy%V!&v5T10$wt?QfQSD^Uvx!a$Wv@J<4IivuaQvWfX5n_QyTIeZWstk!kxH6l zstg*)VGhz)qYO?WlR#&28iCY#T0M_9X>@Ex1#4#)bo!)4w|a?tCW@b2a^)jwJcH|l zw!uKS-_U{c175?O1_RycGaYhrXPfG!%}8|vWpCTR@AU3ID|`_@VMAw^K9r1=lkj^U z?&17gIRM1vbsUFqe?fnAP9?-)_3t3C2ZGqXQwib|j(5CEN-p0e4tb=Fs^%Y*`Zgfx zYMPi6?THZn({%ZR=Rt=I1=Zv;k4S;6lER^NC6($DAklW~XKsnVr&NGlk>{xWyS%Ju z_Ip|t0Z?(rN5dv_9!EsnFE!B8X@&6tf*1Wmju{*Z%5qE@DO=@otojhAO8CpBD$F{+ z#YidRfty=b0Ib4t`Q@~L=ag~NWUrXFj3&bgQ^GEg~8l<0T9;|U*??0;D$L$L9U$R=lxUL%yKZ-MjAC-C!L-1 z?&kd%&(z*I)|IXl{;5~d)iK7@=6ZS}9y~AXcF}j*?X$J~-a5BF$M`;IKb53(^E;*FiO$cV3q+e6$lLinWxZ)DYUag{ ztYRG29#v)B(s=_Mm+Y6&bmZ;1(IK(wTl zb$&k4S^f`2OB|?{Cv9J+ld?PY$hHY z#(^Sr$$tM;C_HH3c6lXeZPNNb(pqTcwxfcFQQf9i&kqt4{c=PhR{+`D><^J3{tzEtU z{+-7WPAg6#^Zf~ml@F|0*R;lF$)+ZzUO4 zz}8(e0Ci?zF>H_BssaG_7ITWe`dnX~!DYq}uY3orn-D?8YJOQUf8PqPY2~=>DGv;!^!}x4@YsTCEPKupBX$7$#wC>0$@={_m+b(MN-fSK($yk;k9# zC0=>=qw0oKkq2gjETlDuFBf{kk;UllJ|hrSK(MHG1gjXNK9vwt)@YUci~Pu*(3YtS z74}B@-Yt)d-Y??ztE`lpzaF>Z3U`;%NwT^DmXD6A#SP z9}iB0bi>WLr1{kv>e-jJm|BF9990Q45+4-v>3BkCsnAsFQ%yLZH=buhTF5SKu6aTc zC#LXu=x1HHbdkS%Au1@1C%~AN+`-pr@N%+uA9hfjbZ|*C({jqQ`QcQL><%&*WB72= zeD}J#@DH9?$1yWc@j|y6JGbpDZ{?)e0}>jhFTMLWjArdEW5DU@_MQk7 zPm#d6QzMC-UXAYQfY#bfcEDq2N9+iKpyQLKx!IIle17&;Mu1Z(f|?>U6EIUYJW#g+ zmBwm-UbX@s%MtFLvOIwiW28KtUX9eXd#1j$(Hn6*D<+s*C(CC(=i^J~`pXM)lbUA* zSMNiGck@Y;C1A2+Amxp(@}Gz*3JU1xoh0VJBO@G;7;Z*H^vS{g5sNSecBt$Q+~lse z^NgFTtnv#;pwtayFXtHhB0L;lNo{FxROeZk*hgQ9sAUw@^bgXG8>kSF$9`&fnp&r!G*VzWEQZZ4DoW$a_)*p=`veA9Z0-CrDl(I1v%UIgD`wRZc}Pg!(9d>&u|EO*^}R z#w)b>q^(Z@sC;wmOZY{`3CG%s=0iTwklC4Ie$A{HrX!;)mjRt5z9D$az+zq6(_>MrkU6(D9ICi^4<-*6mO!d|H2;Wqo@fIuzFvZR)pXwD4LN{ZjfI7x4 zLx%kEg2ZY0!79hq!;Zk!{ual1w5(}k&xHl3+q2%faK1M)PrGs|M7Px0qAE0{NP`2| zHlh%{H~OsWKy`^^^~KqzYT0!+UoP0$3ICC#==WEjxfJI8!vARmW( zcO5j*?vCQ`%V}w0O!RS9g zA10`-c=hDBmTe0JALnbX&*>P%{zw04;O$1Pmh{)G+aek`zMg$`bSPUqm06kIoOQec zK<7AX>eLUy5koH8+qpI4YbIcV|CcYPD~;VsxVc-&QR!jenQ4raI{7&HM++j&==IPa zHmY5$IxG%;TUyuxzF!zMsNMBD_m4xqx=lSSHho8GqyTZ z)xOOHQm*#+pTPxN1)}yK#F~*&_>o2giazqGzNX2qp|mPQr``|RR!j=qgIpo}4HkNM zQ9~Lb{JA{I>$4t2P*5SJsMxKgoJEU&kFCjvQQXXAOuZkPFoH&8Wt2ZAz2jVdpHAs+)6UM zp<%ap+oONslJSt@i=pYnKotT*6+@Me(~MpDx~4Pni1MbS3_vmzE&0YutGmq<@p7Qj zB@4E!8u*}-`+bc1PQGqgLG8$fvjH;{cW~AmLM*apRV-3C`*y&Eq}(yPr5=D116=U4 zVD~3cis4#Xp9@VQujas5dPH!|>64Mg{;um=ggN!AAO(&@vLuN?@!;9buL*EKZ)8wI7iXf{a@GW zoxbIv3|tlwn312M_IF%hKq9Duf6h7fahPpY=Eafjdq1K<^$tuVQ`$M}b%)MSqxH=_ zCqW6cLr4C3%KVPS``Xl7I^*%mgAKKl#YnmxGZ=83P5>@8Fb5c(v7D1Z&)Q@p0p3Ze z>gZOJ52<{HK5L~@3IV~(&{g+0$!`LS2{x3Sv-fanc!C2jTg;s4!!RJpym4i8(_*&< zF2r8|0lSm6mzN&%c>GnmJ<>P|H+n}l(HAk%)KKTmFW!abqGm42*xLlKMyDk#_Iw^rFLYjO@qTg!3|CpY_)`m`5 z?gx^bvxG@?>J>xd$ITI_Eg#C^g#yKxfWc%ed}`c@)Wy3b(FP}re!}tTo8@?cD~k$W zG-{LtCBcZj&B8aW`4ml%i*tZbpN?1Ey4lLAgNNVoYrQqZNQwP}E~eCD(KEb_SV!Mj`6GvPLuKE0;aosdCz@ zP1V?67p?R==QD?^J(0OJ*{Xv_ew=mjb=H4+?>40&*tKIUkRVtGcCrxLQmh9+-PW9i}LbQ?i?zX@` zDmCpyjU>_^s&@3hr&CO-#T8MdYPeC#CZUxIXiG3bvEUQcjZD{;30a1A>4|+x!#RJ1@`O(kL zbeo97Ag9I_mEu_S(RDa&abV_RCP!$&1F8~Btqd;CL34V}>ZD8hl&T{rXP;|p_~E}Q zs9i>xqc3k}G~`#?!Bc&WCu@dm{7==CsMswEiw*h!o)Jb?9qT%Fpnfh=z43G?PL&X$4@3G5)19tG2+G}EH}sBMJ5w;6nD{20 zMZL)x!qJhWB^E=D=DdO9-&npyD~v#^M6B)??-~wi${VMnn!zox95(+IB3 za%Aj5?(C-E{vt$Az~~FPNTLLNvwb+rKJctY@{c4GM>Uql;PgO1AASONAO9Sb<=PC| zazGC`N#dQjk#*m;zKp=gG>uSm@%bX65=fom3qO~&B`a6Y8bH5gfFdT5rw@NCbzIH;EAh}^ZtDGnNGT>JBD zyo|klVmea0-PVTU40=W+hfgV&^9?3A4UDi9<~e8SfuOc}!pLt&=2_sw2P1tNFFb2( zJrd=O&wPz;m(g;kEIO6^1#>Hj1bSIULHtbr@417Gq0{PzgLGRwMvv}v!8s_3W-LyPgrgf_(*jbhF4&@M>g8eI-lCG-v>C8Dc-b2ZdaTo>{(I7p6o0CXMwf72A@3 zXjB`};elQ~(e!E89r!)q1po=gYFpp0EFqu{EEwC4e;qTrizxjxqJgUuM)mGN_K7WP zZTqs^am@rg7u%Pf#+jb%i7%y7r^HjaoDrg(CF9NxE>0)MPux2AGr;2O+u-twij?tn zSCkh>DnRL03CFMlaDKKk9XD0lKGWMqdbS?p`H}ciXJpuseAW{Gx|{oRp9ZwCR0fM0 ztP9z<#eB&!V#7v=3JVhg<&4_rs~Be*aD47o-(TR|xH&N8{C`pR-ce0`?YgKP6#)eV z1tBU@l=f3WR7xxq5fMS@LR162&mt3 zYZQ?jOuaeTQ}D1=Pf@-UYGY3=I#pOiMxrCJTPFY>l^tX*3oMKhak8=GjYseurK4CKgTftj0 zodz4ia~(N$F$_(K#z)I5FUmum(~p1hcKWs`DADqDC!}{q9)zVG&gOlp2@17+(7b*b z-++iy6oXdA-|EJ148}L2kMFx(N#k-d#`Mi)|NUpzdjPi+&){#u@Z_cP*~&} zc<8WRD|Se)>FTjY-I27+r?*$p-?_DWMFe`K!IOX1d42`cI{nk!`;ct+xRd=gH3|jq z15{pT-8k9#koy`69Y;o|7>V@r1IESRE!AmRj=yD3*OHtv1_jg;&Sk5y;Ee{Du2vsW z2L*4y(ubU7NjWsKn=yAPhxnk0k)!cw%1^`)SJv;9E4zc_0GBOISM~dkj&`!6jE+IsFATst}P>}Z~a%zt2jjaax=5&l?Eh*7k znX+{F@bNQASJ|+aB<%_LJyt&_=(&YDSHAs2u)Piq@_r@=;} z;vXNkX7pwd6fJTyypZp`h&}Iq%;+6G4o`cjcZ@ohoIbfq`V5c8YTq#W2#xbxQJR7@ zgK+2xN2-2IJY_oQtT?Wq(bu4HU|0G>%on=?zJ!2?-ihv|EI0;>Fg%~4oIjanx(!XI zH60%TwDr(-I~tMZiw3w;{ZTlb)~UoTf;>Gr9o(Wij%OUni?b8s-JI<@^N=b?8?#C~ ztabZbX<0RYe0Q>vy(p1agJ9cWpcAYwWO1}OU`CQX;vsXA2q+H!Y=ra89iEkyYR%so z;c_#@-s{6VHH{)Xp>Cd*{@`87M(*}#2qWC83Dz0e{(tbZOLo^4GV*1t6d()J2F)#U;NvOD5=?p6V@jh;+T(P~^k%t4 zuO5@2k~)W6Pt%dF@(&78?WcA#6-7VymgBd21y=6}{OflhH$erfkcb~j9K8~9OOveC zSxJpMib#R6gN6&yUyQc?Sy$2p7aXj@qrkwtMi06k62xHW(XkUqkv3&~RBGcRt<=1+ zBN4tv7CBF)jO3wYN8o=DDzMtsBE4`qU}^Es;AIh?hmmnYwP5*%>W}YV|L{KDv~#pT zG4|*D;tAk-_;}K>OcVAm?khr7I`Hb_4U{HK%0#(Ovb;Z!>DJ9>-fC%15^hGM!r7)Q z=zx7aW_Tmll1&D{PRd$jbNpV^C8Dnff@x@qDRU^S(Q4T`(`T{3XQ_jQEZl{x2SVP> z=AikiGqBGJl*l>*bn6M>$sPmi4>JkpL&tG)78xdQ_i;2V7QgoR$-75e{%!{}z`_cw zM&L$nLo`4_Jn%27iOnEfTdYT`QOZNR#b-13_xNfPG___#p86V1T}aM}N)Wma^HR6e zw7>cXC3*n~%GgNk?zwum-1GU(`97`(Ih%5+Cg1%lQG)Z=QhA>;QuDrr`3(B}RJdx_ zhx)sf&lX88hpu3Jf>qQJBHbYi^}kj@xW3ESd2%rwTeIkU7qk1??zY!Oe)YTy=w8{c z)vw`Y^Qq!#)8x4z#bZy9l8Ia&qX6yJ-uEXj&;Qz6m=F8((R95pY;1?BN&Yy(Ar(@pQY26hpFS z^1}9l5)TD5C(nOHRJY`}+)9&}M1xSZXDRIb)v~eygR_V7bB8-rhLBFWW8)%Wg`Iw$ zX^=X%0-BtF0Dek-)~ns1aLe34ztCq}mYIO)1Dw=@1agWC{8z-lst_~IsC&+#Fx`q! zhXl#GR#C26Z1E4{Jb}tJ^YXpAYAUI2BZt=Wf6AZW^MtQ2rhh*bj}+_Hp+7`u19l{_ zJ6fpbbG4h@O=rCz`HsswW6Q8R%|A8fQb#}QyWo1QtFPG!m3dUlN65Ka-X*HA_mN)I z-tzQ#@mV9O!Fe4`D~rku_mnY*c!|pO3)QbWE?!C8e5m`{#@%2SDTgzqjW*1p*O&RK z@~#S{@RC%>A3jWqFIoK%pT6RZ9wi4&u2DYtoF?ES%R>u!ZCS*!lJH-drY9YGwoJJ# zZKyi}HS^A58m~adxeqwmP6|^`+zR+t2s~-MV^N1HiRTTx^xC&OGuB&2G7$UFzKzQU( z*qU!#>pJGXdi(bXri_+`Dms$-PJ z@9>C4n3g6_9lti6j zbMwb((zCs7UT)*+_Lk>uYAWsBcQuwxhBf;5@e1~#4Z>K=M$W^tJ;2;_;X~x+K_Tpl z`)lg#35p)OiBv`?z`w0QvipAC{1%_TvR!ZcIC=v=jNQuAW{-6=8%g#Xjpkl2iA^~> z+3j;g0OeTG6EHObvET|2Np9BdAz8AiCjC0tt+T_wUXm>Dhipk7x6~YDEL>m~D~ePm zvkQ0VC$We^!{}2Es|m|HF9x8>7*>#)$V;@Cr7wnao{o(o!Nx5jMNbgj^Qki&uY%5U z`R9>SCcju-hV5h}PNUaDsTOR(D51GId6HX( z-2JWjK*#}mqLRO>m#tO$eA!CYP|fvI3GtvOEjhxNVsr}zNJ4lPIE?r_lJh!-7&?@m zTjm%bZx{OxBTg3|Ui(Nsgz>hDG5U)G2i zyWw~GN$+GbH2Xz@CpuPd4xLpmUkgVOLF1lsy5s7$3!BjDP%N9rfW07z;)7*Ik!~?o zpL&`Ru?5xxpXF@78=tx_hI7j+jed$n(l<==U6q1V}`vm}celHlB`VRP(tVB&>R zIo)HBC-VtA{(LI8gIyOH#@i!o!$=a4F%;Rz%N+Zt<&)$6__VHOKh;EmRivMz{2jG) z4{YE-+nIa78KB2wKd08ixAUd?3192P`I-a{>xob)xYaW9dQ1aX)uwNlhUk~v6v1j= zJlE`ZD|YKm!Zo!od-C&bz9*Y-Z%z6x**VQEJ#Z;zqCd5>hH{AyF7mwlb4GIl{-ri6XTjjpyXmJ}510?D z8TXB7zg60qUvi#k0rrp0x7ZaKoOnh`WO49uhC4poNO4BVqKtDnijLR?rCL#xqcvtk zI{TJ}aO2L7jc0~3-jHnO9U5Y3*s%>zE5kQbxHX1%0y4FFPLtFL+l}&~f6d|)rKWAX z!-|)7-ko@oVx7pFLM8_d#iz?ZzMjTYI?HLs&ULbbCRFFTLv9T#0QZEyK7UrYqkoJ! zg>6YYPqpC2th&3q48IJ*AE@4bw3>0)bW^4M%hb;w>YIoc=FXsZ#r0je5}cx3MLCdRTm3}Cnq2U#85P1ipi61!mf|ZXT?VQ7y6+gb4o6=r z$yG@c&8!2frSh9LcAaIV<>C1kp=mlEI+WLl8QzcX#YpRF%LK{%=!LQ)5?A= zrJC^EXLC5OPgZv)6G9|T-*_c{syPW5#lC?eBh_g|(E+$8B2G>lFcepQsMk!B2mHb!bF0}$R&f7C*7mocoK?QI8ca?4xv|4ZfGdBNicED{Y5;Jz8?W2*y9k$&CyP zU{7#8IGBd?QSEE#mkHHGp6ybH&V~!Uu%nmys0OCD)eapOBi&C=B44(crX`S1)XuxO z4CXt`Wm&dgiCnxmJU!q%>E}!6nL)qd9|1;|FpJ$o3SuB6euFd-q7_4cw4Gwka5j}8 zg3sRQ)yfwWdBk`FqSIWscr9Eya72(pZ_bT`M-Yizru3<`+4f) z@F5A|HVns^YYc7UUA*He1?7E!JX>LV>h{l|b=i~`Or7w4sm6xs33qc`@s(Ef&8Bu> zg;RPZ7B*yJ5xYvRm6i3?@jKR6MzYH}>@;R+F_nZcJhWt_0($={2rUmAxbU{3Zb8i) zeTG%@3RKIHpD<~Dy;k=@Hgdejm%%&sb<4rnAqy!LBWtH_5J%^+UyE^bxm3S;jSx5?_6>DdIObh=6Z|K%O$IpJWWhJ8Ln8j0o^^emyLoE zMVhU)pZ`GB5zP+v?t;&y|8+Z%D)OeCcbVCGuWQP$)@Paq9mMjb(m{}K!5R_)8ZHdH zv{&8H0WH2Y29NO?GCQczcJIK(;8&jCzTGBTcV9)gT04h29=?e9RwU5MnXNW8YBwl} zyiiUlxC~FaX(Das@#K1UGkN_CR?E#BLEY^7--f-D_Jim;=L& z&#EQwf6t$)_CBn8?{aH53z%X9q|L}5USx-9~r?nZOfc2}2QH_|34Pd~B zQ{d5=Mx5K3S|oOd%Xxa3q?m!%n$>f9rf!@$zw8kH;U^OMwy*zu!NG(?wVPsxlRPKz zb3*_SES!MV(_}!ngz*3YXdY+x7@2ETvPv7SLnnvkg*dO^lZnhs4<;U~?YD*5*ncNS zw*9reWgU;dJb%jjCvDggZi+){>id3YlZW%T7daxWtQbb>8p^jnlkvGaWOe-rULaxQ@ETA;*%A7LQG5%Ga^;B9;VF4HUU^ z(}VW*uUt;mB>JA4WnPsiJM$sSB4E6$`_1Ns_#ujL>v1-W9$!DmuFD4^bIlaFmJcd; zMqxhce7;u&TBjz2&!0IRfcq90bfs`zKGML(xUkY!|ED%LNBZOITOX@cQnQkh>}ihr zb@P-VhZG!fAaBun65;QUFq&Jn;=TZr)CMwZCQE26bw7i%V^He{ii!fl_Fad98d4Yy z0B*$%ibBd|PnZL5Ni`u^uTCB|%-+#pEFmROW&iq80T-=P{B5+icOdB!dp-l1>!j|c zzGGL>9r1Or0JF2sI<_jkUIE1MP*m#iKA(z}yEf6jdcEA$;Xmv2+>5#9cM~QMWr(>1 zP;j-#Ax{LoB#-YG{s8vb#_&Pz3X7kEdkViU0PP@cRm_{QKvo%RlyH>n*B9Gb0u{1F z0jj(=0Ms|l2>18CAJDa0Kck5Y4jyegK0Lh^naEM@b1p1;&?fhlCwIo_Sknhn{lfB( zxnpx~y@!>5eoTz3?Y-C?rX3nzgk3yA!yfnLXN(Sxo-jfH(O@qs{{h8!Iez$f3z4QU zMR@y*z$SEMC??Sj4LjOOvU+w;%4(EUURg_So>e~_{LPpi=(n& z!LkSM`QlN^IOooM5ETq3-*D?$-a5aZ^!ZRpD{z;u6qu%PoD0^wj+*GJKUiY+tl_X- z<26&ED>JqFAx?w8QGE>T4{NmK&{mA6KxCZKh>?bHw?A{Nn!-%mmJu?_ALPG%^s=4m zXDMDGlgQQT^0(}r6YH)%tU$0YxmeKkVoBQ=5>H(9zN2Kr#^GY#Xu1B#7}8OAwAl;c zXozpp#0`XlYRS)MG)SdajBPiDiO7#$7{y23e>c^IJj0SqhIV9jM|Qv8*= zDpK%RZZnrjx?>0*Qh<`ggMt)f}TBE9Yw|u(diFU{$T)V*_YVFfB zCUXsY$Op#0)QM!kzWiAiy9R%WR{AlMc^aDi^kZyhcs_l+O2fbNguWW{Rt2%5?c>^a zA;#2QW+T^Za;uEQ^j2vpD=hQ{7QgWGOz6tg0{;|l+Rw{(6sr&{!QBM4vVS>q0`uJ_ zSVLor4xI4-&X$})z7i!MhG{3$y{3^Wi$0Qa_0E$wX5M?wN|tSkUB91)oqNcZuo1iw zZkiTu@7G2h2ojG6WYZxIM$JqrzJALH%Nn6L~chYw$#qQj`eB^NR zLa}WHO_?|vv{E-`6Ya+erA@5}NvSvR?|`L~S1(8~wg0SpKo+Lo{zrY)f8ywIe4s&M9a!GwC>pZGgol2S^{95ce%?^Hv3#Qx|a;mfXSZ5V| z(rR<&&4GP3D=D3RPE z)k7*M2=dX$x@Ku49+{tnD#DRjWA8)p4ULwtWl56~@_|?noOJuo!Xf zLRnLxviC3brBLDaFm_;f3wHf&&N1hfYJB*pyW=^2@42~x-Uq)}>V18R5@B@8j9qZStv{<@tDG81jqwS}_<&@g2rMB?fG=PF z{s%L@iHh!4q(RA6eRqIK)p?8}T!E#la_p+;VKS?}ekaBp>QAo9XPM#zKFoajoqj({ z??=ZO8Xl5Iu;tflwdI9+W^+OO-EnKPAToREfeKFR9M%tK8x=;{H69V9ShfmRJ5hi$ zwIy?C=x+$*FWqgL8JSZU`5Dv2!$hAVnGfgIiaR5Pn|zks7?F*&a0aarzBAa~F21vu zZ=O5i6%-Xq{g|l|wFXpkfc78={9nT>G->>u@mw5dWp12zPIvGbz0(=wEqenRMLWDU zQ3I8Gneu7FQ_7c03qu~5l^5`-7}vNGc;snp?EnPwIU?I*!nB2=COl<8vA9sT-^)Mw z&g0<%km8vnVnW{LARsYxYVe2;HNSMFqqQxRpA@JOQ20KMjVkT-b?~s#846 z2!l`9H`cfq-9atNWZ~-FLUy(=6vsRqZBzSv5BspTtG2<}fqqFjW0-m!ox+-KZJvb{ zCpoHbafN7bwWtQeeCrLUw)EUbv0Yd&{#C)MUlWa;}ZWF;qWq6A-yp>c7dDzD5Z`tVUl+mcx_BwAB#%V3@iqC$i)gpMQG0m8k6=dm9$E;^)eKI%!b7K60M7WF73LEb;keJ~<#fV22;zGa9Wur%TJ zw)eu8%pLu3ciQj~HjporeH>tp^Y_6yDQzyzPAsd2#R z+H+LiX}&=v!;MaLK>6m*z=ylDygwq2%}>%_`G%W`R;v_?a4pN?CWPX{P>oVS zc?y?U6iyCLw=8po)Ab9dk6!fn9* zHt$ZGF+)(Ltgd-&vlaIB8(Cx5R`5TL95%Jn;D;cgLkPaK8PB`iAC8xkM3*3;Z`-MN+N2VvhDpL89 zjm@VAT0tGIz6A&Q7+LHziP9@Fu+T?Ut&UeB_II>&a?IuR?5KQw85sM%Y%BgpYx4;? zJtl&8a>jMv=jzmU7a!wj=YRzC_1BJ$^h)>9#gK=19svNz>gZgJ3cpvk+^+56voFYRO zr!)(tIIS;9H+{I8N*8ITEi>Ux0eOeXg_$RMhHRIZg3Z*gDkh^RpIXLmvxG+ z7P-^*atvJ@DTTKZmg40+8SU_%n3f4JZ;OFzjPBv)fL#7(odVFn z`oL9CAJ14GBCuWGil?M7^;j7>$sjUz)VO~n`(o!p4OlD!^PFW+EZ$brQZRM^IxpYl zKH}Dc{|X0WvR47<4*LoFIgngczX}xnxSPm;6&-+X|K1h_wSoz<${t|NDT5En0xZVI zQc}kGXsApS;b;M)w>*7R1WHucx3`I@K9jKATbjr#c-3H5RVJ z!sT6<;RIVi>Zl+D?bEiFkwS5w~zCb|Mrh$v~fnA3~ks(M(x#mf?sQA5M>~~3LLr#d_fufOZ1*U z>)OZ14*yw)KSf@adrJ1!VVuC<%k~e>DUSH^nMvVijJMm>apr`#oLwh!+q&kSl5Ad+HplQ2eYQ zofN{B0(3fe-VMGEu`2D?7pAWleoxk-%(r@V!`h|-_+(NcNej(dBeU$O*iw7GOe;4) zP*^AZ19}RN0_wPax!()DPiwzezTjU|YlK2g^QLT*uPp@+B}Yyg6HH(($3!me{<@?K zQGss|(Wb{&i+_3|x}f4=a0=9xQ~?$wcUu=4Tjmk?|EC}tt`DFzslVsT2@?6X0gU)) z*F%FhE3o!#hUT}x56+VWVxUc8hkEizN3ugBil)~w4J@AwsbHIs!(`#s+3|R>nY3_9 zoRJP+mq_Gn{>G{>U#`h{GPpLT`N8&Wiv3XdDk4$f>=%p_3Fc94%b2~a!*j7Yx#jCGbL_6dIa_%!e0BlQ)6?=A7=O6?f>m=&ZF z*@A>rEnAzLARO}jup`uKd1B&n{H1}-jMbj7PnRxiuQ77G1(^3?g-gA4y5K>os2h-g zXM%1^1m4yJKc!?QQ>|{1Z|E+Qk|HMlM#&}VSRTCABhY1pX}esU&J6?CkOAQ)595Q_ ziIQQ*zt9y0RG@(3iThd^hwOsleNlw#TrG(p<*%;(>TZM`BH|;pLI4brb}kT?&k{L z+Dz;^O)#LU8mD2y%7INIawqlQ0?6zUdIT&lq8 zqPU{(7HuZfK4?3-Qn&0sCJrWLf=~_Jf!(EHpw4qdcdt<%qxuJDX>^be>WW1V``3ht`}dPe_p0vb zEAN>%PDplDJ~QSPf(`Puf1RKYE5u6hgR)W9Y@>LF<%TudweQQbC-HOI2Yh=*>W_$+ zAy$%~;6e^9OlU^Q{@jc*W z2U3>ax&fw&g$Oe{ci4$h0Dbfh&RigmUqS_AG>0}yY;7Y{WYTo~5!>Lt2RIK~xr#7E z1Wblx$Yrkf>te;tz1+6+dl`jTplT|Z2<{tuqR5x*uS*4_P*H38EfSUcLXb+5;sJ$U z1*Mip29I9bmF~SezdKF7a4(!JUAU-p&oFNDqx1W4D8fS?AbFPkVs_vG`WINPfs&Z8 z@Cn6k@|HbnwmF0JDcw&q^Tw9F?|!Yk#6;$f8zaBl5N%2XG3aBc{e&TQ-^5_aeIJRn zSoM|@+X{>e1+;QZku`OGfc6|wfrv%HI5p;w8>g(H!bkxq&Jklby7v6?Dv!PUzRfCq zDjLCcCbNs}gZf@wS8h7zFzR$dKKV)eXM8;QE*$2CDYs`jvV+F+p8Lt#QAp~H7Y6pl z*N4%|Iu;f;hRj{iwqAQHn19v<@j7hxA{Z*x#D)Me0nf+U&kg)phgjepdj zU@*|AGcgaf(^9+=sCu96Jek-+=inf2pCC;epBpzeK*+Ega1R`=yIbM?g3MNNccOr6 zK3A}Q$hRW6z>0FX%hqEDG~Y7e+xQ3E>Dm#Bupa-}V5Yy|_+=s?Vo%F}$u(PihRrD1 zg9RO4y;}|rR+!Dg)X2@sDE(P?E*Q?rbwgYUeke4lg$rxJM)ut#7=#Cn;}stm_@NG= zWbArpp=5l-qvuANz3mcj(}su_v1~N}gMUf(R^mA1-?ak`$_VZiQn=^}w$`)b+EHlr znH1sf&)1M|CBZg-0kdMaa~00Cm*9m=kHG}#24XyY=e7Xx^l|JasL#?|>yW&ie6Z%v zjtuf#@k~0M5?6%rLsIdUoR(BQs=;1%S>s=XD433K!OY=W;5wOa89>gL;`}i67XI?^ z-Lvow{-YNM$9PtCP(ohgedMu_gl{hsZ||NzGLfBl&?&cDhmB|g?!nUvzUQF7?|BVe=X{=E8H4f3ckT<#FByBzbNEn9#Y{W5#nubw@@U>xt3$v_ zc`p^L&hOdBS*PU8j28kZ? zExUC>uMsQZqqub-K)?Hi>oy8b*KoVZch;>0%%jW)$l&C_a!iP0oo^w+6Wxu0@%4EY zD2-me?Rd>sKW4yI>#1;eyOb6ivca)pw7p%sP%*hx*oP%Flj|j`v)h9ij`dNH+*MV?dOZ|*fFWkP&jBgkga(b=2zO73n8uz z+=#R7YD-RX!;p%`>k1Jr_TR|6;gd%Y+c9$%gP5hewo3z-);0y*4iZ_>T+&^L=kNMe zUvXMuE-RqGxn9l{de)SN>m2!j_Sz9-bLOp4hwMz4&&+K6GWhYLJzvrc`-2Q1pwIKb zS z2eh$?c?tp3*e~3MU42Wi;;RxQgvYO2y)d{b@^>nKvsS;Z)vjaHLJ4Rf+u5nUpq;15 z4A3`hFS$qy2x_5eVMy>{JT8wM(bZX;owkQLVL4@g?^EGyO>cqS_ctR@9#;;^0yGcM znzKSpGy2|Q>=$t8jApOlf_VAD-CIHp?`t$t2JfaNGbb@?UFvkP?k9Xz4Z9O{2hF46 zZAmR27a~TBqWXf7RxJBYK)s~J2MYJFg(YlO(+-^uoSQRwfUjrRlU{Ec--<>FXa!x?cV(IZN zJ!l;*Y$4P0VcT)&gTj_NjedE5r`Bg+7k(app*psr!}Go`ud_X@A0%bI6jlpfW4yv7 zfB=qe4wPg85PP@t`ySyQV`$SsH_+T|QY@{(Zr6__ki7`wWS$i6uw>^o*4Y(uHZqd5 zqVlHi)pK}L9K%c z4@yrDIgj9B%RSd9)WY7n3lNL`L#QC^rIl(&(tUgGAoG757A`dB8Dn{xVScrTt;WyA z&GCl?U{(3)C&0t!cGLf?6R1BWn-?y^x$^@!e-^;O2oYF=lGd-ry|fhG z?0b=ApLAza)tRg$#CdDSuI=~lhr&}C1lNM@vP->^_Y)I;qu_KTq^#Y<6T^JbGuki6 zA+%*fL0b9w4ztzT&zZM4hQh4;{T6M0#e~BIQJKdP$!ZUmq@pm6;Xyd$GAR2)nTmJz%(S^?sf9$7!DTzSLbi$!Gr@T zG`1!32}qSKBcH>?=jd$vXwuP7-dCx7Gd;P1T0&gy=DA+ow|%|elB4#%b?e{9gsHxP zUJ9&1k8Kbg%V=X)*~*(PnZ8j!ZNBiy-|VG^Kv7_O(*qTS=9?&BwPZYj!gw2zBDl$xCVlf2iE z^?K?}qwTC2oRI%B038wn=_ z^zscUe9=$nKFZiWwrgyjcc0fx3Cd# za8$S~H;fY>PcqkgN(y7ZV-z(g_IBkQcq7n_H+x8bquGV^$s@lVXIeEqx~7S0^F5T+ z^>pf_>m7LL!=DWDCAIv>YHA#J63{p8wKAPrn1!xPc)+3Z#Hr}#6_(`mYRt2X-99P= z_$ec4bhq#bq`+}A;iNtmZbuIa6*$TB*-61P$3X*gI?jg1IHY|vB;BqYu~Wn7QDNm% z#-Li}P@rk_cQqnB%ir3zbN-WN%9xX~`;hvbP}4AnMrf`r?N?Cr>>WWr_KCo?~=3X};|?7CzpwX9fI1(V2h;U>bO zCK{x&&w#4$3vi$Kr;A?SSMSZPU7p2h-GWc%o_Y!PK!F(g2t;g1i+n;d3(1-peCRME zhsu?I;qHRl>%@N;UHbOseZOq`Dnt93`1x*;lf5Yqlg^9UAjltI1BE1b8vk-1J!o$H z6YB0$pHJFzb9Xh}7jiBhe&4BWA^22&-AKDZyeuI9K2(PYPYrXwPdpF!)}Jru`d(%v?WZ%+FjNZLwSMTc57C1 z=@Vv^?o{2>S8s)?LAFl(96W$f=-DvBGPc?IH(z$VCa1bbXNXUwf-PlW^b$G1UXZ*> z9`jy3>*lrB*7j{W0i8o{*5??Xs{e*91)JtdE4%D>O< ze!BD~&8K@Oq8V8!Hs<8FI!Ye09T^ZDh2ZqDczX9XuSbTsQc)L`UiFh^DJL&{KPY@` zUk_uS1v;-{t@SWby=LrQ2Le9I;H=-R9;ON+v!$o{YsauTd z;p#9y`?Kp!A{-L6$6F)Lp>Ly6!Ja~~I6(pmj;GFu(Xl(dMK?yt^sY4NJ#fZy9sG85 z%sJJ8Rh9j)0lR^;*>fDb1q!;rCPyyyRM4#Yd=rPa3Y@(`h&|sr<+nUweVHB%VjkeX zFd%bqrolNj;4B^t4xDuW%g{(z;rUF>i;R9}c=|q4(d7aVPFz6W6%4OLt7VHQaZ>52 z&oOskk*Q2)*H}H!O6Ci(-@aM6r7g3yQMSe`?hHLj+HH~&$)WbO))zK_BjNx4lX!*khC>H}=303!Py=#j zaMpM&F0ST*^*1P2pV~uC+|E{J&{G2_QpY+#+A0J!n=BC94!%s^j}GsNCVRHNARqbEYJ%H-ctk=J3IU@s z2VDhu={k;HtEKE&5&=qL%mxt#bT{T-H<$h@w3|Bzx@PT-a0d7+p*hkv>$lGXA}a!< z;rWuzbog8lhjkr#OPAOJmhi@=ZTH1GN76C|+D((%OWO}d+uvb~v>P$-9SeftH7CHk z{IZ0HR8|@c<+(#7!3P{)KLFNj@%yEeH+EQWi;9?cMc#@$zi3shFoTQ>os?dW#oejM zRO;2btIhOf6(N=G_-Pg&lr%a0SEEuEs;DUM!!kZn2;f;FtrQ5*$XFG&vAL{9qO9iI zo8JkJbH00iDL+5b0@8$!3n*jKS46;)SVH7WhSF8%qykrHV*HIab7vFPO@}+@@L~CP z{R+x1IwoL-ED1~D;MZ+%LMne3jO!yPGqBeKaUOfBi`HasH8tv;rrDI{-U-(0M~FiO z?IVIIn}KIM13<3Cmgdb-89#KH>f^Y&szRsn_vuY~r86ZBqi@cu?;(cH;R$Ma-@e?o zF4}eEV?n{ob*Hzl{-Oh*j6#o`WAdznVZU9H~Mt`Dhs*4 zS7J;p>y!~;$c}YSmD2&Wy|taBUDOWAufNI13eArlndi|?j8dJ*PC&FQ5F>_?X2&Ik zDpF(N#%1M|=issXjc=|#Lr9?85NzMbFR-CdMoaQU;cWaKlrcv!=7F{bw#mN7+-FBS zH4&kr*5mj}Fxs3dNE3(^+(0?I_bwF(oB*a}jr=?zJD>eRpc({@fj44V!8YFv;v5L$>SCsDvT|3=T8|@Ngk@gCnO=!x{ocd&Jt6~+>PH9fM#>HhH z(^E~>?Q}%9C~hc&L^14vqGf0DwwbFTD>5sO2m?*usJ&;SGQLIEWL@59kL$TO7r4Ki z5Z69@(oNniYPWRgcA(Ok#7$(!&vDrCP2g~Y0I^fqgi?&QWyo66ZTA|Hg6KLF;6@vi#<=D;jjQn({Hh%x#r(l21v zTq~EIStvuumCn3CO8?Zj>0x z7oT{LD%n%``#E8_x1|EPf)UaIbt*wODLRy4%BFl@9OU6<(-0UjFmb z&zBU0iS!O1u}0Wfu@#+GA1}_|#5O?7vuod_$X(Kt8r9I$?`km5@w%;_5rgWQ8=zR9 z+1K6lw;qi=)J^B%C7?o+&!A59+g@Kk#-ftOaYE#Z112N{4?-ag8W+tB(=ffG60TCA+^d88r&zQ|kcKbLjqh31J)wN6d z3ceJF07|y<$A#+gZetB>iYZaG{kX7v2eb%# zOANd9oQrznnq^!}Zc*ta&7$$0O$yY96$A6%%nM!?MY=VkG{H^30)1#uoH;ZrI~+)4 z#Kh|IJ5KV?<6OKyI}&{p+ULkCgFe4?MR(d>S*H+g^eRVHWkvc{N^3akZ(30xN)!4p zVG%EBl$0NgX_6h%vll83sP^WNJZ5@VH42qNKjXR|(@H!W$wOqj$^iqt4`n{7(KZtf z{aT0W`aQ1lRYDvpXk3#=UEkG)#f*YJuUdZ%?x5#IqW4294VfwfML)3)>`P_c*_Tv< zn`^pa_Pw<|8P+Z`))^>mZF3x~yO8?I7sOU5iF=bH60fb}9K^vy-YR?AarIo=v1a8t zwXamGuA19u`IDn*JP@~lMuXk99nJ@c)jf~~Ipsv@dA-isMEd{a-}*%?UmA}8LguMg zO9)5ody@b27q8dZcVXC8;$#K*T=6W8hy2GOVE_8X8JOw+saAnU{iox_{?Vd25T+bn zFUa|`ZXP%!eRp54|F5*fziEn_=+kbd|!`e zT@_A5z>?AK?}CHmrd#$A_ODs3l?YBHK&vTkAgHouY!|aAj3yyE0w8QcrUo#OY}92a zwAlI1+H16ePsdaMF=@_{$IN=DHzs6;I~JELVLm>>HIOTg|JzD-G%+`!r3DN z7PK3kg|Y$2pf5?Equu1BIl;fwk8|sB^^DV=d!CTJs5G=f8sOEUnf~M*a5_M(pBu?k z#vyn42#;`dVq$wj+PpSmzSvq|CI%)aGj~ryjs^XnUr!iu5fQ&)UUEbuJ`KKOmrmLD zjRX-=Lj$UyDmpCk^h&d~l=GcB`Q89Aq;0itf|KJH_hd3194mrgThO2bo_uwI88|nk zMa_4fa3T7-LvWDdZM!&9^x6`MGAk~Iyqlls!*E_xxrsSV> zWYsZbOYPw;5=;2rWBjKXvfcLf5bPaCFY3)Ze?{0vn5PH@#D@nCM%{?Dwzai?ypDdY z@?-Rx(!%JuYS~^Fp0t%@?FZ($ZE$M&O8DfQkw8fbtf;6R!%x9orVxuL{E$arCcf#18b!e+|fP^BIa2X?nxqsGmdxF>ZAgx6krilkWlKAI6buj<_B-bz##ZD1@Ly>=J zGyLCQ1#k2*-s9Q-y!mEjXS=!qRP-;;jQ?j_e7Zkn{m);|dhYwfvj1`Ssr~=t?Lj{G z(!UQcb{hNNZQ=h;2mZ%f{HulhTNnSwU;J-9?#^|78MT{$I5Jf4=8`7~g*}fd9tcn};=(t!v^~mQsp{ zB7=gGDhE^$Qp!{+5G@5nL{oFtDgdM_W@4eRg);GN0`~G6u@%#Gz zACKPecLOZI-@gF{=U=Y+w-xlaoB4OHpm!h$FSdsN(u??^Ob^N0we5>p1b=O^w{`cHbT9s~brU&C7rPV~L| zvKu_fONI76Hova_4+chrfc6t*KUr@8{R*y~h)=rNvr`4SY5P6?V8k%>%;CQ*?CE7T zEuSCrBp6}dF;7UMg@#}W+zcfCx_ZPyp6-nUUAtBJ&cE2YTW97c8l3)J-SO`#kN=VP z<;-C{CiV#^UmioC5tT>bU?zU`Zg9h=ZXZT=*?j`QHMSf>1g1(en4j1^BQFl6rxpQP zn}5ap8#?*l{hwL)|G?V#!$$I-XZ-)Z-2OG=|NGqDE8DyB!@uln_zkPDcF*Y5O+-t9j`T(j0I+M-qwD&cXrhCJ}P`}IVjd} z=1|Hu54v_=kzvf+J)_s3DlWDRnP&QqE}Z?^^W*J6y&db}ZfN1Y@?yyv63PyXAW z`P`R=rE}@IS>fbZVedx-(5n+y2(%Cr>-a>ug;`YaEx%&C1r#dQ_4JOOzEJZD%Ef^Z z!cnQ|NV)lt4r&nX_)R))cTPC@Cv`GW~eKg*{lGCNIkEhCG^Z3>>C;5Q(wOOxP)aC)1hZu2dG1-@; zr|s>(&C*vK&~Sgq8@&nH8oNDQd;*@X#$Ml0x-4y%?fc*s#Kk3{wt^nN59o47vQc|# zLfHXacD*B9p^iOq_LS?${O|T2^FK(_6C`K23j}pc_u2B<~u#2a~i4rMHCV@^*QUZYxF^iYPQqdr}v9T zP{XpI9WLTZx%t$nFFJc`9_m2sq&eA~Cktl;0`fwKruQIPjYut%1;}5*8nCBzk9MfV zrhFx&xQu$I8x6R(d4A1VnDrl`E@4Xv=O3?JEB1hHvM%((OR!}|!En>Qlk^)qyd2!Z z26OtM2zVQHo_2?@2mgJaB}^nV#)n0z_BX;eT|{?8A9(EAiN(?-9#59TD;H*5xl#tM zhMM#oe@q-t8s^y9Pw72dV?v)r#yV@e2z z#JJm*HbqdFrM3XBftD!Fs6!c!r(5>C8lvILHjV-=9hyOzNJh1M(iW|%xd6YVFpg|J z0SyFk^~GyTs*W^O#Go%}Xt~jR^m?CMNPlvZE6`u~rpB9Ok+W1)-e>U|mV(+sM^gar zyV`!8ewQ?N-`hyD)Zmo+!yBLBGw`ma6fQ- zP+si8AeTz55C?9|~Kwq|ibpv=IxXc8~9TDyY7)N5G5w1x)yD@jHLIsoI;`K(8 zn)j%G3%l01cc|JkKRpM0WxZTzh`Xn^v!CwDhpXF%>=;WDz;%M0(OPkR`?p=^U=DZb zCk#XwI_y3p&4fIOcb-cwwZ)p*;KxOoP2590j2(al$mfZ8ebYQb{dh0=4PMC7SB6U2H z6YTMl`2kl?PtU9f6Kqi4xiM?fEAg5;dDWO_?NJ!LCsA8Ea98JxTMy|E%_c2@p_&C* zUrK+f3@fgp@OdeDJF2>7O>-i0>T8C2T)6oMoCj?_MxB{2pFOo?(N0Y#)-7RxkU3#z zWS+FSE0FMQzootY%aA9}3XL;MCac6`hdS5U1Ep912s8qWr~zD5ro0$XkFJ0s#DA=M z{P&9Mzvo@V{3rJYk}B4(<9nZPB9+6FD(hg5j9#i?>dPp%jPNZ*=DlvX zCAS(96crmU-(1FC4Qgp1ci2oe$jR>huyQ$ZnWWIW~EpbFqr`qkIMkw&EXod{*kzkv~R=?xO&xS!P+^thn1RTm=GUP=pdsDgFCS z_kp$DjWa(?ni%XFnYU9{@5x6cxt~r=#}42z58z;DifDAGUovx^%(4o zGs*-Yx7x0PJlbxYj43O0p!vH6e7t2p=@dOrUQTpp{}kawj@M64tvy zjMn6~d{#Qfd4GZ>QxSH47m{J2560B~hJHXJ*k`9DPkRa4fi;NwxuhDyKn9{K zj9gn6z;eW5ROya8kD3%Ph8RJ#w%TxVD9YCDVcXQ2A4wudwPhtdz`&N1DW8=l%zKCW zLCP*bxHn8{G{w}o?(NHuXdFUQ%V1ON-a>9N|5-IPo9mXmIen-*d~ScvsCTVrRQ}`h z))RkQMVTLne_KQkV6y0cx&gAuiGNuge_pY_J-a{spPoGk))qVpenj(@&+pdFx zNaHXfQ4iL)sDj4b=r@hLnxFWkY8Gl~R1)lcBi*U(^*QDD4}Txye;IH0hcYx!5YUmW zPN&Fh1i98$=a0Idsql@$x65|DO$s$8ve}HqaST{BCf)wW#IhaGvMF|9_kGCT^NL=< zQqQVSy_yRMnI(huck}S`-`02Q4B~I=+g?p!_50ZW>C@vN0Lt_rEf*kQ43a1T4qMjw z{Z1cw`YKL!G|$ol>K!^1cAu4|iO0a*o;jc<>-_Jw?RWF?A9{fQG$8-aobDfEyWUPB z>rapVqQy+V!106tB}9k|nz|$18@ZZYGG}>$Ku82;0zRP8CYnWWHn$7p|iaQ2d809cha zS@+W-y*Zv$74Y`j;Hh`wxi>f!{5eM4v`?pSaouovb+?;cL^7lONAq^=U)`_O6wBvo zWk)eZ`8AUxhTWKf9yCQWr%TGA&dyDRUa0SUp8c2ejFsTa5R%WhHB7mz0QrP&eg(bf zaTTp5eOuNuhwJlCU?#*b@XQs>fFb;5QSMlBY>`7gr!J+i*q9o&IRbFF)YP;uF(e;t z6?3ginBsg=|I79G!Pyl|L^rOs=u+(2q}07TtLn796^f!QWrTLZ8nInGRduJ7l>zwr z^2qwNKgM~w+4jKZ26q8Ubs-JfhOwTO*g&|b;aCKV!nd*lkX!y7)@^UbHsKhNxi1UV zzMu=`5Zh%)a_4i(i!lhYvDLg%GAFi@FaW|kt@XzT_;(Luu2u;ru0UH}XCqkf_3Vi} zShw&~oqnU|rK?4NQ2yG;oQ>v#E}^(=pq)SMiH8ofB33MG z*^U!ZdLy>2y&4(O*6ZXT&m_Pm7@B>i4yg5$FA5`X4oP z%OTh1lOJu=W)UmkH}HF&7Uq%owclOxw%y^Gkm0%K!7O~ygnc$^BVTMC5IzNmqWnLZP;?(B+7nQ@N$-`R}P;!konqr zvv^##n~jSDjbT{7jO(v#!&A1Q=IG8QQGxBUGl|q8XQ!+i?>uC?MDKD$Z)MwX1)QQg zA88rwpN8IbnjWe!c{YaSnM)LKK_L}HPqn&mRnCd0Y!+@<;?`MU>*tNQ>Zm@m%zV|x zG@qJyUeuQDx+WhN*KZ8CT288z`?% z-kas@8j`6{G3EKBgO*QhG%h^+_gIDhlivaI8kZqmJ}HH}RnaQe&)Ffcm>HuG8cLY zIDulyy@#k~y?cVFA=`r~jf`-s=&Q`>Rn_dRn6s4tCnP=$acHSjH66q*uIbD;x;sk# zm$0(iKE~DJ_&3RCdG#y3tf-pQ_*u=I6aD6r$V~YK($IBKeOfix?%9H9L2rw&Zpx=y zNFwD`mBf2G%OcXD!NSMtE;zX2Hmfvhc)a~#iunFcprl{NY{5oaQ7TK}7Bv!Q7 z+U4|FB!($9csaMsyi?WRMOsJL4pJM+jBSC|nq9Acd4JWn{O*XVe&d^Pa1%KPV%ZhX z(P#HM{*}4R#ve**U=wefMp1Vl&%NxLG3vigvj{z~smW?6rPF8C2n^bfx;1NNf4wZ8 zjkwF$k8w6{a@^RHv=yny9>e_q*HDNp9i|<1RJ#nRV3)SDYl>tR7$gCs1BUi0th^gFTsQsi zSc?DiP5&n2Nl|mLm*o~?=wk^Aa?3z0^i3;PPzwDTgZ+bUhk1YeDD{sAH3s{}eE*H# z|Ga%pX6OK0o(=(S+kfL6KZG(xlmo_3RRDnZ1WM?)=jf%pwnDF1k9*6D0#vN{)(7||dS zw+d#?fvUgYw=(A+k1_fADgMewKP#>8YF0!K5qS4@(mh4?p^^=kj0A1x%m+nlJygj{a_M{>#4nAF#W64&nmI5fQGLvepo` z2{~liM;Y@%aawlLf61)8f4bmW)sMWPtN40)6HhSLfctRsWCiq)#@uABOUBZ~=?~W| z8tpl1{AB(76mUL?JK8wzQ;Bf`?$K9Xhc{nIIZo}({3ia%)3vA@GHd~$f$uG?cs%76 zU6mP~GhAEw&|V`+AyRmggBXG0qM2BM+D+@dHRAZ!vETQzmik?y*qOI+TR$sZ!k}j1 zvf6fe-$nQkDm@~28g#eJarW9kt!0jU?B#mt8^or{#rCK+*5VO`gQ%Z~qXoA2fRvuT62a^v=<$ts?ID9n_eLHJ zTL(u_%`k9bOjlcNHZnh8nSr7gPccP0pz^%Nur8;8xVBPFiCt?n-Y~ZXt$?+}P7EJf zu0TkOUn&u~Wlu`3^1BZ_q0wwobgyr=O+qA_u$%;H zEUq!nQD>fhoj4}@E0}oKKK&##29j1imJtMv`mzfBR&h9`85DAu^;uFYwbDm~7bkBO; zrcmChywK@O) z&H+TGy~_|)J~;9;B)KuG>W<#xJW2?B%{6;!<$nQE_TPvQw?FARdPxPdw6$49P`=`~ z(!|gc?XQfDMzi&YB)62aox?}MJL(p&_^uyfSB3cUF$j$lPuk18Sd^h}GIm(aT}Hl0 zlh(uOthTU@)XxA{Z@ zx$kUmhf!A1#UPWi51*ugnIEzDT3(V(utN)kCcIUe7g1>fE3l!WHk*@H2PI+J48vaH zheo{H;;|?F|C+r7<3>k zrGO99G$##rBplIxlSOqXTkS3L?<%5t&=Jq)r2FZKy|mLmB!+Xwt9V(~uEF~k?9&Xx ztXPspc`^Nqi)u6>CWCL{qW zbF4I8at0b5X0rQV!`M2u6cEbcCNFOl*F!I*Rx(#n>g}+ORGu2%AxuyuR!g|P9EL?Sq=I5Yb1A)~6i5d*6ep|K zl=e8b@DBBVAc>q1fU1Z&<69ERZ1k!scpStyBL3)RUUOfx6fbyx9jSu+F8gQ<#^F>F z)^)J$U7KeXGrmy$=WC?ne~x+nF$7}lcDq~*8j$!-h3z|2{?{WI52g+Rt@fqjU1wxC zYI|g=lW34&&^!G8dQq*vYb}E$zxd6|CSg%Vz1Vxy8olb@UZ5aP_HTdbKN`k$6Zsw< z#172gBFMh}Fb-_PL_1u&t>kB0R`ER$2tlbpe=o2WtDnYD}> z>Cm$euhF`tRk$`s<377_Y;fqJce}JUCNM2{@+%yJetzudtxVB59(yP3gW>RQ#O9p( zm7*lm9SFC|TPUsm5F6{XqEk|R`pHYuvD9m_TT%|qfb9MfkEj$iH#)7JwB9XJP~?;+ z(|s>d9w_ALa+@%f(vSDg@&s;WEozCd%2JZ_2f};e24IcLVEB#V<`l-7c0L)j{8-o0ZD|+c*zgPqI}Eko_$%fqF%R?3&%pnf4)}-MfWhG`yZ>ur z<;{5WUs8^p$vmSCd1m6~{~D2(#5LVCb9JU(_+fpRhRfFftnPyUTh8O2t$0r?@3>7w zJL+x4A4ebXO238c4vX1u-SK?#TELHy`abT;xkZLTpLEa%R)H3eH-jcXznN(F#$RVG zFAZC@YLZeuP5Yx~`e*O-VEYd+7keXuL0j-6;fh0j9^k(T;d8u^cFf9NQ_jZ)>@1~F zopor14ELhO_-%XG+i47*CV%R&Sp?S9T&wK>BH^N^)=z zL3$AhI~T5K?o|s74pk(2;km)zPG>Msj;ak#5vmmRU5_xvTu`hAjc( zxtg8>=Ar^h%pN68pQ$F@y1eVot)s8*TO@NluJ7WO{QcC41BHLf2LF~7u0|vgx6utn zDzEzq`r@*?*NG}nI4h8JuGjbhn|rAJj_!v=-}hj=J|L=^GNp$gwFYv*2U?0^SKvx) zp^$Xz`rC*g_oT4MjPUvLeg3B`mzGIrcJUBqL057^PFC#wt^Q2M`H6@=sJKITcad76 z&@xQawPb^76a}P~nvUVBCYmr$s?e7QTkDXm0k+<)va<(QgT&bI6PUTrM{F}z2`N3BtElBEAYB$k77~w@?RaC#$J?E8h$*n^Hk?<`@L;kmol!u zmz)5FiCFcBRhF7sNqlc*7FSIq%(&uUWZYt?`<(;q(d~N%rRY|t>l=#`-*txFDzTl9 z%?rHrZfBHj_qU6j9-lh;%M=3mPF@OdsdylcWjOrgyJ|gkwoWVAKV*!%tgeX7CEVLK3_q(&yY}j zeRk}}1cB40K=WcsF`LV2TsXed~B-9!|=rk(3M_$UxO(=V8X5_{@QN9px|59?K>*_Jz ziGpg0#&W`_2|)*P8Gp@GyKbc=_2{v*iF)dBJiz(|ktXy?Yu0&^=l$4tscB?3$h|+u zxo+O{L*K-%%jmZan^x|D%yA5S#M$i5c)~lJWvi7{4RV(-!}+t4<0&a~whWMOdp&T( z=1%dSUI^Pqo>t@7pG(Lw8_ zp|XaKwd}}L{*o~EMf(UvxzUBt0}9A~jqlR1h0k8-lf{=~5(cnbB;73cS(0Xj{zT?j z;E3NKxfLpOuKkUeL9#1g`QC)eOn|qIb{?AOU>u8XCU$Aue8K zRsg|y-amezzue@Z;%+hi^V*4zt2uL1m);Mus`i z%LSw>t3`Ihs3s?sDHTCkB5A{17-)e%A-WTn?uRQ2WTLaQ1V|O#=0HX_R{g-KncA!? z?ov)h@xi^17IA<=u8CrySbtpV0HGv!VuGH!i1?8-+5?Ur?$_4+ets?EnzJy`Nfdhlw1HJR%`8wN?V9gT=@ca}woF}%{1KvF3h%Z= zW4FBBZXsEMg}eEsjs9?K62XNSr(q1oZt4|5jS?--u>#Mr5(jXr~bNqYR;2{0CKkmDMHN{ z0UHOf;sQXJPWE6xIl9UEBX-CrW*4DTW1Y&$`2u{?#W`sKPOL{#Q}i*Zyhfn(&E;*i z+{x6Y9~b$$0_q#~w9ZLdge01HXGUhCaj@0wV^ulXi99rDQggte%52#^y7$_RIbLOi za2o)m*^2@i)NUWKW05qDi_BLf)2wJ-BkKiP(0$Bn znaHQjoR0-C6))IK+6ZM!lb2e<+|8B6xx1FT#O55<93?WKAshP!-4`>SE;({>|EWBE zf43_(iuwL3{64VJU4&>rFx%wCMF_}J-?iSM=l}!ueNm#!S+sOcG;=5@yey&$`G^v% z<0jP^{IQK(>mM%TMKjNs8tv?OqtLDB7N%oouGe0t-8Ko?9$4$1ruem}{Rc}s$$okA zB7A^+PK-;Sdyc3tx1+Vu3V_T4;9#l0glSqh%c>)t2tJ4Dx-Yr-`4;WfpE3X$8o@pQFOX z=^L)iG>87m<6e$3I`_ro{_7dM$p?13+pdKh_nB;lvYAs9)%}nv!NdFz8KjOAme+nH_O!SRJu{zhy&7D!( zcVk=cD)vt_uHTNM{i9{VOz9~km~$6QQC|W;bj7e0hL5NxB`u{s$LmA4*1k~fiCDR3 zZ{V2(RS^qHrue;@y;+{_5pA5HgRfv7gaMSuO%9iYK;7Cm@Fs}EhQcu|-5$^`xQAD(XEW}B}2i#V^f|4otBfs>U z#(MSjvcF3^gN$=pOsK+6mE7f2{lY^G}52GsAB+kVEOt$Wa>`p-&-E zVCFz7_iE_R%pI|HC0+tZ$2GcYNx%v}}i)b+E<0rHoAgT)5- zUK;&G6~knjNh4L&BWg3mfjNFhqM1`XQQLaA=d%hvOufT050_ zlYXCmw`$hk!@L5^(pqNtSOUw0UB*{aUAg$;N zA@+Oj*<)%uq+S)QEwMcwBeAsQc7e=(HnG|1wik*;x@&#l6^;$759Oir z;P0u=;&!-}3P*iSUpA`en{EyJ@GCPcWC3f0!nm&y%?(?`+ap&y_ED$313c=)YhP@Pe>@RwMq^WrV$PAQT`4tM=RB)kwjL@j?zu6cFchE7 z8OCiy1+6qa2`EHIUsMZ=hzi@6sN(T+a6|+y-StA4nJ7Rw0`~ZLG5ifN*`2EbIXsTk zZq(-P==wzQ5n)zx8zQXru_s?vbbrFcT#tBF{lGMU%d7Qn&0bJTMA70Km~3(bZ;R2V zRAmfdCq&GjDB}Z8nlGujdb$X0C|vi^Qy9AjU))A(Y4MmDS}-0WFZrZGd&_3llW2$; zqR39r+nm7Ii`Ro_!?_#Bfo#?TwDX?4CuQhOr^U!ha>$n8p|$O$%&;ursn=pMI5z?$ z>Ja7ye!rY1^RLp=?5CF9K~T-puP;U4shj8xQ;VzVrRB(<6kO!m&c0A@HI*VGTJ;;H zExn&;Gp+zTl>+>kB)vOfB^dU2Bi8rLxkg{UE(k@CoQlZP@8G7p&Cxmr1%%}#hgN2* zywhh=YNmLnrzL-2y>3FWT9jPZ$cWEBkUZwy);;KbYPPFA#FVmNf8fE|<__cBWgOdHC~w z?MzzQ-5!G!A5UG#UE1Eoh=p>bVNN32$b-n>m4(+9Zp6(cu7oXj!`8_6)vl6^GfBm#^VS=OZ3*+qx*hYswo?lIH76SoyOgta;3 zEw7#5mH~$d+UF$GC~I1o6aGf18b|!n(oa7PJL0s4mM8-{d=V#TV$J^K#kH>FUMY%#MK8>N<{*fx+g zJ0m+WoK*X`ECPo+boGI*+p<$8Q(BuDK2?fb#f{wiS`P+1yhg$s3$8NsRzLFvR8YUt zIziaegFm3B5({t8+Jo+_#kH9j_Y%Ud#9wTarTd_Qi(*$^BK?vW z*38{qQPqu;IS4S7d5a~&r6}{42LkUmuCFk`x-MrWZShGLr426u8{^**r)9lOM2LS1 zov&Z9ymB0k-WsonEFXvCwyhqX+KUS1Hm%QWoj+1kz1Wp@a$F1nupzQyD1j=VAo+G`6cP;bMl zJpD-I`D)`Nbs?*q9L(ttMz5McQOzkMj;r}*O$Ec)^N!tNSiL~1?vt>mZ;S1td{;+h zetSGPA{~-AtaUcq28O&^=7t`mJ90 zjps+T0`qeVPF3Z$6a(#*(ZSwvm@fSq*Q(pvm3W+~FTE_})TVlsYU`A6!rhFSb?5;$M>%m2bSlG~#$ne6r?`LB@#8czNd8%=hPqPwGGey5{ht2|)KoZo z%})>fH#gn74CnfPI(H;PQmpD1B?xF2)aB(7gyke|JHXw0#DY|qA90J!>`;1(R%|rZ z$L*H=g-7J88IzxtdcKdjqmahH^(gK@(_+V-yzjd%dOIqr1m~-H0A;4v+IW$6!)PZ^ zb4~fKNz*mFxTp7P4c8cQdKC?g3_b8e1$IqN>aipJt=8sXhQE@TRmks!q3$uZDBg>` z3-jm<4O-WFVgT3tmn8>`W=TDk8PIm`o|jXtZm{IB8UajbwY3nuIfJmR&(f1W1o&I- zRG_@jv??yQHK@p37TSenPzL_lHveLdGZLXIdwq6 z-y_0!V1CBrtr7PRj60BXzRL5sUuwLoshLetuj=*p?wy6L10jE2D1l4(R=l@qVuo>u z(6Z9sFzqy>rWc9jSHz7px=r0*E^lh17(J74b0p+bFKPpMHRSZKzf&6kU2Z&~rU(ga zRU$zRge9TA&rwiqLqv4{Ey7_mt!j%0d(tbJ#|}0w^=>`YzhCzV){z^~N}DBHOho(y zKpUhx#Er~A?0EqPa

    @xPnyFvlXV6sH$i)ob+$?o+mk|1263F?M*T4`rR9F-ek&k-ElPs?WyYQPrT ziSg4*#j}}DkrREbs&tIbq9wj9)_yi<^y~HeO4THB@+Uy0!&tVvj~+7OZC_cY3K2Gk z5P$sn?T?~eM;>GEZmXIN8{d9T`O=Fiw-vYCkchAlRSKnen}4?Zy6yW44zxL~x)4eV zlF`K$GA)ouvHGoY9GJ92&*>|+>?&k)%}*Wj5t`*%6V+P7)QMP6d>c*mOZAK^!4C{( zU56^K5I&%2s1zoacuOa~o}?9N48IMy6hU4;SKk}KcTZhgF(QWRS>$g^;SW1z)4#7j`@N6 zb&co_U48QXPJ?_y^RbA*k{O}w?4Wxoev|wGVXLC8-|HbrUMOIY!yB4T_gr13U6-IT z=2q=@{>O7`t2s+HVda*(7-OtOX3UH4$R@^W=U7fe|bFku9^Ll#q>w4 zgJS)g$CDrpt&%#Bi2Djd6s5IeRQqy@8zXRga_g<0($p)H=$1Y{J>E={DhUX?7ox_& zL>R7AtOmtx1}une)R!qx8)z2nY2@8D>(FIiyg0y(bVnklzNty*v!ErO>QL7sV0rsc zIb(={1<8*+h#=hN$OTD=o*w!5$Yc~nSr{}hsWa)n4&4KqPC%Y z@0ZV$GvJwHS8N8`eW=f&TP<|+@PqPl>9+l~>(pvV{FN}%(%(R|WI40m_ur{tKXh(lh1(Xm4-)6GzyC)+V6t&SD=r8a-SW$e{y@@8(By^TO2Cq4?gFH++hYqw@DJUuH<=oMQ~H z-zc5^6_fO!SUcut4XvwwucpxKQY!8V5}s8XihpUdb!c~dr*@rdMJ1zWcBF>>z~R&g z_HDoxMY)BTSi=OpG>6h(v-9c7l74wDs3;C-;!X0=3Qe(c1MV*O$g2zv%V+cZdU=J* zd>X#Hme#sqv#~`BFXG4H{`;~Y!E?`#T#^uhRt#tX$h>F0e#%EpRLNeFL^OC?OI;Ed zTt%_>D5FST{_%#Frfsz}gDM_>`Id8w@-K>hB5mm=@yZSq%X&xe(^tK+m=W^x8JQVB z>B|K;H9qDMrB#1s-vxAfPH1c|KDdz(2B3FLoZ4O3I>kQOo}7{XTm7#U){v)IUDfV% zU8fg@sRAt>qw@lyG;3)NPj6nT*%n?k)UId`^)ZEvqQtF|3NThqN>HndSWfnaH%`hkYH=%nvEC+*m|=>)u<5%F zTR#=pJs3z!s|psr*JK+-TzWstrO>v|`mdz&_trbDQvjECeI;ofWM}`2sN=TmF!rOD zCsOskSJUCCD1YA!|I40PkJ4&rRNSVH3rt zyYu}CoK90+^j{sF*y#}%Cq|)S#Q)9J6U?THQu!@t*d~TaWR7dR4egg4XQa2bP0hpd z;wf!X_}*pYl1hymCz;$BHF``@TO+4N3o@kcN%d|p zHcFH-DY8U|#6-H@Y}7G*{_WY3yuY}Q@`PC<$;u;Y!VWg zC(<@FSz;Hr0y!&U_+Ms|ARhvB9B%=pQry64?8bKh#I-*3Cz%xL)2 zU7gAzIbqT1;7MDyv;j2sh{w30x^-;qo>5b47m_Lx+AUibm?2&VqM*p@%f@z#(JjYc)% z9i(T$Fxp;+b9TK!RTSL?T4j;7eBNt>c92fS9Wg=*bIEk1i{|cLPH^L#?Frs#ctDGb z%jU6am#%W$Qe2`^D-pvK6m@UnXC+_M2a_f)LWHFs!el!QTte2~#ZO#+R8*a^4eo;& z@knnK&K^_fEW-s$Z)nhTtZ0O9L=t`jYLPH4DyBdjz;90W@0!tRW}$vKr9j>fA~V>d zMa%BNqxC{*EwYz6oZ{8H)Q}oa(q-sPG4|1+w<2PFo*R!foJ!7 zOiT=BT-kK@o+C}QxcuFOP!s!u9F0>JmPPN^7lsJdg&k(|?2nrmVlzgWJ)kqPktQ{S z%6lx@3uQ;%FRF&RGvfNVO+&L`s%It6Iw#f}kB9rtKMf++mfyQ@V|TRKJ4UPi^Cw^X zxHKZK5jk!Ob6_oiqhGf~zHH~dU* zIg!uByCke(WR$nUAMT;uY0e3`S}se5|62RXdoXwb#W^Z@(+gqzPsK8=(*1_=-~D0K~-DV1tX`ki~b8;Qi!;6aYw+Rf=8Rf+|(2Wd*W2S~WF zpxhO|GTxvzPQC9rmSgSgoY0|jMplq=033E7; zRKZMO7(_DNplVngU*yDsrFv};1O8UDK=nwVC3CZ#G$DA#IgVFx@x{ZcIXZ<>_-Y;F2Xfj&Ws49yx=#>hsXtjkeQ(mJ_4RVO_Azn*H$Oh-h2NZh9 z+o;&DTaZnJtI;nNnoDLEra;}KEjsuTb+syfdWkvxfK;bgzyoM&m@6nDJPuf0L!^@&V+zgE_?Tvlaj95mh)p?gmC2wBVk@`mD6o5&`HLHG(pZY>Y4EAWK%e z(7GuEWZVO*(vq%n(16cdM*9?kP2&Ube!MUWeUb95Im>`3L9$vsYP}3l{;Y$&8FGtq zOudJ_p5WzV;n?FFSk;Jw&+=NN9PiAa{>hJ|OYe79zq*7&Z%-X}(*Hu4pdA`bYbls7 z$mVF^3q(bVb>HxrcWP)eOJ$ystcE`Bx6qb?(p$zauTG^3YFBS_tE_b!dSuz;ZzJs_ zW8D-6D>*J=6{~K~Nik(qAl}VK)P+jQZE-KqCLy7_8NcrC-PIeR3iSJ3=>C z8#*Bnp77|l^c-F@6a1t`|9l3r7Ek<@^j z(KYUbTz4JyCD+?$2yd)omOfNfJ^YFpW=?*AYLW6WMGKd2a?J+0NIc~?G~RRvw2vgT z5RlcNJ+ym-{dnb`Ot0R(@1OKmVqKavYyHaakDyEQZHMXMP3~;gsNdwgu&g~+Z8-HO z#kcsdZahMi)0nqgUSO?5=w@45z_uQE{EC|CE=rbslEUx8T27|)jqVNiw{zryoD``eTaxYTt9YPF&NFUrSp64c8DY zTWZ25&MXhZZ#WiP+`{YfabJB_x@s>;T6jA7xI-l-8p5yWgnd@eg)k(9t%AES<6GBLnIzk40qZ;Q{c{29& zA&mu0-0T`r+;L$E^>CR$WESWml^cVGi4e{k}7DlII@7{g?lq}ZQ*q^JjqlS*|B!$ z1b;f#!9y&m4XL@%)yJJF`yhN=mSCkjUpn_6qHy&X&I#{C9xqYgNV`*GK$m~ z5UEN?Y=D5kC<6>I5Cst=K%|D6NLLV$E(s(gh)7Q;;XsP-W@hdk@7(*l_j&H~yzl+} z@qRw=`4G;@+57Ch*4nFl*IGXAOf3_oruhAQsy_sY26y2?F zdxdkq8Ta0d4dO(UIiSrX>*Wf(%siVQ7rVy67Nwbm=u~=DgHXK_?*?9W9rPwx0Zq?S^)tpCGw1bgmJWm)Ogo=DO&{JJHTLASa|&w zG~=81B~gK*E>3H{^+)^5&3Pqh2m5;h@S7K0T|$UF&CHi{ce!UT@ESptGb3$u5` zS$PXi%`%K$q-?9Kcr^7m>Z&4sk=`eK+-h*1Gv05`BSt99UAPj9Fy?3Qv#GDCA3r@! zf7Jyy{Jye8@^ELy#?b!#c!UvPIt^w(6X0d{Ro7NWSHU(FE^? zumU(?O$44$DrdH>-5PxV5k+?VUY$i}Nl=1f6fwZcoy4+o9mFWl#8U^`^fYi@SS+}! zkwDMj-Gy3*P|`v43mig|_k%r5dye^C?f#Ldnl!_XJ%skHrrv6Lv^4zSDL#Gi`ol_` zh=Cr;ibfn2V1$`d{&zErk)_Dj^5k%fqH5ES!^jAR1jRk&4Q1BuDY^2lmsEcsZe~QJ zvxIjnVfHuG;Eq5I02B>=UPJj4TVEK<)y>^)*y~@ zp92$Zn0qej5+TEfjTghr?_njmEwzVgxF=b95BV8^JpDz9i+l@fUdZ)%+~}38*{uL%!j((Yp&hBMrs<qr{Pe3TP+T6l$24U|TA7YN zTxX7)9e8AZ1Ni~=nmVP|Dwwb_?ZnJSxr8xvB6UW^LWY}Ey7@iLs>l9O*$uThCNEo9 zjWDnWMd%h{?;^!Z;CC5{U4Dc$_E13yTq%>C`)*Omv^I7VF=6TP9r~ix#QYx0YKRE9 zj#wgmfggIs*bBvh)tCu0bmPPfvl{ZFvvyjr%Nz4{vdMzr(c5h0#L-4(imb&q-b$iD z>Q)Nq>2SG2U^?s_X5SQTsKYV2z#VA{pEr`@7V}#^=V#iIu2ce?@^D{^@#*kD;5rHE zb*$D4r4ALOt>Ubo?BSptp`=?_3IenxA|mAn+|#yoXqFtzn_{fJHC>e*PgA>g^^2Gm znWN{J=Y-~e57bvJfd>)Shdb1RQ-W+vxyF=l#=2o_NsIf=XE`tHk!h^ckMM z;ER|)zS;@NhC}-Rd3uN4Ce_UB_$(4vLiA`e?=RcbQ9(GMoxMpUpJG~F5{<_t-l)js zcquw*F!fN=g;YPu?wR{A`KqJbMHcSer|5vWb^C8 zuXfEjY4KNydKvqKqy^NYIesXP$vGW;^Q?C2`nqKD9RWPsbG6y~X-!fw-{?d@!WnkxbZVym+$ZxYM%j{& z(~5{J5dPvS1f^fo2;x=pfr`onm}i+HVp%lPtD?2_wKEgc;TaV^zOcT5un~CuK>TJg zl6p-7&5J^<7f5qW>QI|J^~Q4C zS5O0*Ek2{@V23@DGV~55y`RH(mp03(Sv`Pn8ou24y~`<3f7r&?%Nq1XYX;HotKM|e zIzqwvhg8=n)GC2K$2<&cmF-BA#eTdmL$Zyw3!ga(Slp8GB=C&V0%`aU^sY~Qst&e`{{#5KcZl7< z$(lHsk{5L8WJ#}~72oJ{#~3Z^!86dQZMEk?jnHCgjr@#NC}rD^;UkzDJG%;D@9;F( zSCs5kVOkY(7`8b`rG_F4WwHp7uLNdMHH%7W)xCRqx9;oz=s2#onMHybBwNzuTSR>= zh6U<80u3n=uj90{vL=H`SaXhUN-jjTm6YMbF!>8@lYYu;%W z`d-Zj3^TAxq_$$-fw<# z^5^tLhwU|_b;4($Z=*NuB=3{(A&4?EG&za2pzcsm3-Bw&2eJr~9({W}hQn&v2hWY` z|KyCRCl%boI2Q1|mTcJY$p!wIAOl`GCvfT@OEL^6$5n|cA%)e|AXGWVi;?Vb{?_64 zxn6W|sky4{A_%iBr4Ssuh3}21Y?MIG1~@q#hGNBnKyav0UcLg^ATFMFvhI>cvSY)r zrzwW~?%^I~mM&Ym#YLiODvt@>_OAJWlfz!*hV+)?cvPTjDzmRw+3aK_8QSP$3nyof zMJW<+3v_Zm`Xj7gO|(IRYrsQ8O?;(sg-7~ICC;2`fkRGE=Xl%LUUKL3f^F)vNSZRO zEoyJhh|LI{Q0e5x55VraKinsfUAI~C z94&h|;!3o&j+NCmF+bV;XO9~LmG7VZqK<-1wMKfjOXKSCuTU`*gyr033r6+6Pr%AolCVtW~-oliDg zi!}0gai9LdJ-!#EA-8>`af)I8lH?`~#}8%Ln@>p$;qU9iBVUjUpiHjmYvQo?Bwzfj zaRk}AUu|yzrt)%Ze2PD|3zS^i+Z01WVt^NX_p2u~^9PGgO2GZg3K5_3QTKt`FLeUu z2Wf(kjr(CDxj7FGYp}#h@Yjhtm_}x>PqK@xqgpxFi`D$h+cF8Su zLr6J%SwhN?4=WdElC6Zl!D!1Xe-`er~-N zX>_N;xDjDb?X+zYS&sHa>@bOsQu6}ady@KtH?4@5%_TKXY@07&!#4#DpC|!J-Z^b9 ze@r1Tyt$s>UYKJ3P}Qle(~GV?HG-`qW9e4>XD=7lQU$W4>{VxDwoBLS`;|bd8qwA2{Ul5U&K@xxv}>FcG-J1B+WYs;n_0}ar!7lD6Yvq zEc429l~BQc)clwl0jw!$AB-R0$2FSOES={~TZw;JxnOY3bTZGu(<608LEg0F#=E}}N1bg|?H z2;TscLTm=gsOj@@#>PtSChJ#BJI_G!#U`C|F&SHbifk=n&OlesG`nA_bM=x9Zi%(^&!YrfHPaQET_0256H87{>~EUrAc{LPI3g z%2t~o?Qqg1@PbV>4hR427P++MRhzQ$p(RsAwaNrq4tkhC17f{_e*y};B+NqW6c})* z_XLhZcv^~$*%iShBKkxx>RZD~<9c6*!&>i%#Y;}sQv^6Zq1u6G1w@+*R5=8U^v7IW zh_km~7eW~_19=6@r>ob#LtQU>(pH1oFWt6izQcpvUy%gucTtKWZ4btJ zaI{90Y=`Si(4+NEb?0jb6ES;H759aHx`|%*4yN9~O<3|uq7Cc0<}7#& zZY!kHh?fu`vmrD~GQ#0{Jw<^it=TC?9hGo zZ1D7CZ{YXu4X&RZwsJM2$6p%>+>c$+gfoTF2)&@#A+LfwEE3{!#aYOPSV@R<;T3eB zg_8dK*ps4I(M@AzCZK~HZLYrEkwfoD@%7?U)og(H<(xJDu*Vz%nW}Z|O6~|h{e)CD z5(t6oWBT>{ILmJJ%+iP_24$r=r?}_3_M~Gg@uBGP3E>I)siIrc`(n1$EJaM?UuGoJ zR()a?;U^P^sQozD*0Zb-&ppvO>CZRXn}=s~xu7F<=98Xp3yxUdI**zry&^>c@HdiU$CLo= zr!~qu*JjZc@av(t^Tk}H@SrrOtei2Mgu-*=iiYRpa&|g_BEu8$tWYi|kgPy&40d zB5!?Lo&jmMT_h1BJFVYV?)|JjPfJTX{JO&ls_ zL$3cUlIUJf=>P;_)>lmS=x>4@wH-4b8+I`{bmOY)_XnbbfPVIiy$PXoZ| z3)*Oc6imxXT()$w>1x)9_wqRYAqY_*P>Vz#sjC-qzRBT;z5FI7_9^{yw&;TE6X&G; zkI82bF6wY%8zdpk%@HgTE&MfQ17{}OtLi-VY`!ODQ;#8{QbTYu18v>+*nwWvJbUYq z-Mc(3wL=4R8(4kG;sWlds0YtEH?z|d7<|Zjcqm1>u)AtICviA6mo}jPi@Zesb31-_wA>f{b4bxRfHx^Y4G|)97)k}t_$(O~?XgfNeujp1_ zUMT6-aX}fpbARS$i{tf+fXC8GaaA{t$>e;G3Ij?tN+6?l63Wbh?~THs1gjY3_(HBdRHjgUM&Xo)M0GLOJ1LwARe z5!QG8@f)DRW{Kl)xa{Ko?mh`odHi9*VJ}RRuTIFT($59C!8TW&-J8TCTe`7ljGOw^ z_d3v7i-||h7=1+6&GWm4X+#|vRJ;yMgWs@b5Si0W3k-W;gTG#pGG#-*vCoC}>b~FrwHC>px&7g} z#VEBH)f6Jly8vxqODdu5aW8#YAb>=RB9{53xFiR$7&&b(;~gIpy`2g1vMD84CLi=N zKYtyG!PAiJrrKM@W_#aZ&us#;WBAZC&Gu+`f|W1{|Qk`4S>PR?@15fOPP}-c8iIY%1`8+ ziSOJkP8WK4xGY@_T{oueZ4CRk@ueL2mi}8J#`gHT09AShYI&U5$edgWk1W`C8X9a) zn9;hGC?AK+-mbsLZW`}Un2klw1tMx!g^#E#GV+=ky3YFcKFS`hPE-h~uGc&t5${Q! z;%Db8`!6~EFt@2@`}tux{Sqv&C)T72V{m&>xUPfzGuU%m7(v8Fr|p&R1!frh6=4!Q zd;x9HP;}hc>tR84RcYz?ro1`5gxuU_zGv|(rTg3zo(XqxvH=XepTh)r;R;2xlRT8^ zD|eUcP`AZ&$A&!u#`)ru5AY;;K{0(!7a0|7QzdF5yy^=D=+5AFSi?}ul$XZ(CZ}Ea zEz04BtYYCt51CspUGHEY&Xj)ucGkBd1zvfk?Oyc}3(IQ-n5Wh%yu7{$Mb{LHI3oIX z%N$a8)0)qmAtZMBpOIVemNI$KR?2DY3+&vKSpiE?%EgCYp+NI~1&NHLF%}j>#K+m|?iW162(e-%a9N459N5r(} z#`nzk(Tbzd&)n@@DjmluyUHyp-;{>l3ix>K7gZ3_vT2k@acpNI6vzVUn~TF$`fhc? zb$v~Y!=UicgTyIm2K*l8Q+(dXie86}dHcSPR({iCv-l9ysi zmch)UOr|N-3ZSp`QU# zYOEEVZSR1xf79G5QGder1JUbJNg3-7G8F5<_YyTI>2VgPB8I$aYD4FfvY0`GS@-Ld zjNW_YKk036NZyp2Goz-bXv`|jP_wnLDi#=DzSh$!9W#NN56J`g5Qnp4v70?9YNNd%0nH)$^kiUB{%`KbLhsP z3WS9xV<$H;Otf{N7`KWG3)gWpG2`mgqqH4nlufKQ@*4JBL!~TYi$Xpv!tPQzXQ+3O zMOlrD&|MaCRbAEuccAr_^A7T&yqvuuIOFo3bka=RpSlhVq~{MzIi^z{bXT1&wOQA@ zEqPoRX8kRFI(Eij1y}Ys`Dmqd^F@yLJ;6~9>b0(dW_T7Ti^-V|$H?a8l~i3lfMG|= zX2dzJ`_@l;Vsf}|%2ZE}TtO*=qnJ8Guz?j5YA~xEKm`lrMgXNh?x#Z)^OPG; z7+cf9N~<3h|9GDGiij$|O+=Wj8;EE6p+h$`0`Q~C_kPW+qDH%i@ zViY`ko~TDNORa5EC(9S=&X>nUSjbu%<&?Qz&%2%YnhNc2k9yuwb&?Zvwn~KrG@mW; zhGs85(?T0QIw^B1FYg)9i2tP^HT&`9$#}aY%4HHmqq5%*2JMH{;%SogjGa(9_Y&)T z%5#BrulgiOV3TP(KKJ&yqo+@Lf!6ETkC(5<1-=`nQ={e%D6vB)soukcOa*+UD;q(657`uejq>LDhh3cc;4@sp#P zr$_eQBs?BBhm7aHh%Iq}UP9e-NBcl7aD#pIpV+TI{4AezGQamzsvWSs9|6zo9Lo*# ziI)4Dd!ohOr-Ns)aY3=~$AO+|<2wnW*#|!I1 zuZ`h0cltTHE?y|=oTnR|yr|lj_#OSZ@1WHC%50#Tz0UFedbMW`QjYGp zc&Nd`TVf0-L7HG)N6;y~3nn6wowWd;{O8PA!Y3bMTu|J>~nFy^AmnJrPWrkHEK^IRFJ~D;~5x z1i7U@;72EMbHF!ge=~M@LpUS00NHL2exR8@-K~wUW8>Z0m&Suh&XFYr~he-N@nB`&aeX~}BXReV}8KCOhy{JVRBL%v?r zPMGVm1{&HLg1`($`v6W+Ixf!B+R~9oS&F6YoNCo(l|9mS4BJ|IRZhJDJU2!Xo z4fqZqz<_jK3}5c~3IG0k#C%AE=yr$bc3%_zPqzXzY5}r{f%EV;7i@XEvh9B?m&ae0 zYwp0D+As;i+DtB1dZ*<4PE5Zp^sHzRxR7f${Wf9v%7pO~=Pf{g=I{6&JUDClACHP$ z8MRNautFd3zoCz;zf$H`>kRvymQMb9@1NJs1OFRKZ~F6@!w}p4s?WV-%BKYEko=dIY z`967|s#bK)&@A36Im=vX9V5yz#JJ3&%tk{J^sfpPd6C2EYFVoKoK^Qa=Ape_*K z7fku?eK~MoKrKNu$mtU-(?K34plkcTxh;ES^EH1C?yI<5aLdE|#5HnyBi z55hH~bv4{PN85hBRO&k%7roP>kneTU4G>8m&?|% z&}l!QzHnC57je^Lzv(Zhwf~~gsF?`%D&PfBKPW05p8+oCz&O#rej8YkIO{l(R31`< zy}qJsbge!S*$zUsD03ix6_NZ|z}jDZ`0rg+jel|Xs{h0rQIn^&_QUMi#1Ve?oqzG- zmPpq2t`GR8Xo7Ib-u!Dt=FaY3orL0eOde*WqVjRzTK>yz3xQLDu?p|-ZI|-~=c8@~ zWsDrD4-macrBH9@;%l|CfFutk2qF8d=Mb*exNOkbHDKV#_J|OVRprKDU2EYhcdU#g zgRedG``t_S|7!WIx^+^LylCy_4_frO`#KNW?g9q~T^KJ^#i-PEjNR03n{{-0yEwg{ z&pa;d;mN`Ud-cKU)9}p99E1{QNMZJ{6FS0yVJ^QVO274R)7C6rxz9s?sRmE33ea9`zpk4ZK3iWVX zT}feSzbh!|_NZE37%Fgp(5^I^!4bIwJ$qwxYI8t{xgzx7j#4lqu!Zu+V0T7mW8R^ThfM^m}90rBE1L9!9B z@aqn!R74ZH0K?vO%alBG)&~ETVR7sq2##OH#IGib_0I}2R$x(9M8$8*VD=~Gep+2+ zDsGePO5fqE?vo63$UnCDB48J-tZdM-ctt7{yRQrE2N4Nm$fyTz`xh}w#p`{m2V%=8 z%wQoSzR1l@BDA7z(@KPImFx|ooql3?uCzs057^#osdbVEu*{0vm4EZ}e=1lG-u;a# z|6tMox;53Fa)Ht}(Bw>MYd3J3>RNmfd9v-)iC{dV^+6P`EAUkvSf^$!D z_5?+#ApHCIgti_Rp^4IYb45VvD0H_`p-F%Rr^~_ui0B#JAnc%`CByMWW^F0n z!`?D>pUL()h6w=_OR~JyKKz7>)Tw-ty_X`vtEXMLvf5dM02H*m4AOy>C|)4l52b|N zEi7$nAOU1R6_l8PMr@3&_3if{^Gazi;(TN$$s6`WWku?iCbrg1g*11lVXhogiq6G> zLv(`x`7#L6hT7p?G>AP$cA#!P=|1I0d$+$6sSDF^xEyWq*m}jUx4kL8Qv|ZApS3igH#A~prhobA>zmoX;x>x3?(megmG)Q6d)#ZQPc@s4|41|ViK$xo6b&tpU9 zL5>iWwF}U#Ud$O4@^>o2+DBm5Gb@g?}VE} zkKvEx9Z@?R`h!rDQ$$3D>}Y?VKa(zho9M6aq zJ-Pcy)0Opc8V#+C2IlTFS8X20%YSmEmjts53D<8-3H zlFLq4k>W(k5+5CDEd?i^=kcirw_8y>=Zrv91?phoWk6BXP?Cp}Zg0sO3M_Ubnig0W z?R&n%D;F-eF#WjMbi~2%W`vc?Y?9Trhlhz8nwIPPoB)MVWFTidP8z{~I9kqhM#*0J z8S4WkX_{b1z9l7RyXG9d%P%4URk8U~?Y*8nH2-{%^aT#;f#Gos8>2gH%3WZ4Fn0Xn zP0zVpc#gf#v674lyGXx7A1R%@O7Rg!;jg9!^KeifCx6`gTa#xHzLqIo;wX1Yfjt51 z*AMM2!rbCn+!%k-%s{2g<)Rg)TrQC@Dz8x=ni|t2g+)64z1s93kR)eN(d$1GK9|`k z?Gv0|_|{kYL|%nXk$34HIq_KrmKu}Mj6Jb0Vus|u z4VbR9y6YHPF@3rDgRy7u&9V;Pa|EMUaH@6O%RJ+Jry z?oweRsr`0!4Lr5*^x5t4PfvrkomZex{$A|&9CG7MgC-uRQ=+ab+QpI03fHWHb1Lh5 z?JW^It0Tw9eZ6qq1*duWWYhAl={GU-?RM@X$3m#X)JVAso7aQRjrNGlXJ*dmbo1PP zPkEd5{6lPqfOzF6w)gcOlhJa~wo_xtES(wp49mOjENK4F=%mk?PR#eDxg(P!>w5QCOEM`B1yst7hKF z-XYk^T%}&$g+nitm{qNks)|-TEXd6PY_x2cSMK|vD<|#8IO9xuW_uj(A^SM|Nu@J0 zCIE!8ZT>YX3caK69hGxw>>R$vK(t$S_~-DQRJQZZknhr~hKCbt%#ff<(polr?f4}# zG$wyv!S>9@vnL@sq^TirY4CTDfR+0qNO@(d{>fy^fvHO7;p#lmNAX&BeE!5BcoJ1i+5U3(w9b(Q+g)c{?;oHL@ zd=e*9b`kaa<0(M3p-KbXJB-6ZK19vyL#85uxk%=>!6X;t!P)lvNaz0LNC!l(oAB+W zuyM^>K#_&9L~gY|*$m?U^yIn+qTTrS6D&BNi-atMcY!;Ia2?rwc}|2y)(P4}50EN= zgXrrw0LD-F2Ehr?N4KvQwaCVEGWe^3_X_a=pItsGfbiiP$U&rU`GIfhgqF7cmRp_9d`-l5+@|bPDi2{^{wW9V%RLQ8;P?o*4o3t=P#; zfdc*&QTixhjwOP9!X7jZ>HGVK|J{my*fz7os}mZxBS-55$am1iNIvP$kN&%@`@6jg zA@gMfY8*m6Dhwt$e@mOXYT!6KxcVHu;mL$4+3v@%E7$#ITPnY;JpY+u<&UA?$^#U$ zMjCoxgXAPbrehy3<3j_L)`N4~7K?*ChJvXQp9ykRMp>W96xYvNdrjQ2HKpbUo4ulf zSfz(QRd1pmXn0R-w~j-Ph}?>PLCS>W=0sb~ zJbCR6S@lw?eYz82of(l_-Dt)a!p{3n9azkF(-7B9ESSNPfDwv;dIs2*lNEnN3CMp z#%9l(&%snH@#CXplf8{O9Ypngx$$}KO9og64|H{xTQf>pTXL=BhXl@NoP1GHpCNqs z$h>K^&&b^L!lR}e_GWb4nUw3n7WhiX(VCe}SUqTw<6QfAOeAinBe`L~Uh|oPU6Y4y zdB})<6s&kg$f-!&S~{Y1%HkYzu-zLbkTPM{D}9?%cb}~SsPWaODzg)&@I81<(+4M_ zlmn&*WFA93B!FJ7*+J0`I^S**4%#ZP_}lyxN4w`CR*b(~pO>utm03Z3D8QjF-BeS! z6`*1H$xpkaVuxknrVcABmHS=s%6Uq$eIgha0fYL6TAg1Lww4$;2AXxvS5Ab<6TP#F z&0Gu48-A#Yle%{3x=%5wrL^2$_zeX7yKC%LLQM@e3Vja|dxD%V_Gmsp@0)dacX-QM z<5iZbIXT&4aL>2DG09*oWcI6=(L~l((ULanbYp)Sq?oi{JpwxtZ=-4(*b)Lq3lszA=pd4CbZmViEt;-sZzV4O7( z7nH8yPtO)5rB`@E*7B&i1#m|!aNcaJoe<%G@ys1R+VrN572fjFQ4>G%9_Kz7AwcBUV8vC%WdN zC7meUn2jg1#z%VFI3IV$TnM{79Cm58w3if%cV&LKRIku9wSFiy_yXkP!x=rv*>SPu zroZGC{PoM@iTib%WMEZs&E#mx4+y8~iH0W2`W&uhK-XOFa=@HPOrjzbS<5;9`nua0cjsfh{T%P)CP>&>t&6;nrcZq{cA;MaTCPLwfk+&Xv%|k4QtnR z&G{~PF2i{d!sofT)uJ|qruyR^%6V>C{Z0OB4wcs3xaMNp7`&S4|F zx75Rm8dF$m2JI5#w63@4;dR~VF${^L(`##(n`iXe2EL^c1t!ArWgE8S{i-W$Y8Xxn z^7kHcTz`QhB0QtneGas56-{75YFDXWY*5-4?)7^0=**Q$%PXGj%POtG^cBK zB`^3Pzwy%<1g%)xsY{Fsd#q|p#a!hPWGI2G7pqN*Vyxyc>t{AWD61^->Mfi-m)hULljE+-;-3gw??8djz8oe%a#T!DJD#jZ(YnS14FVe0l4thK z=5H-I0BZi{YE3R=Tz_+y-aJvT1U*Z}%V6 z^TO8-`kDLgpp<$aIN^JfTx^QDe4P~BV4#AFnnnDWez!AnS$h80igURW=1bdVs1v^+ z`9|)&{#k-Yfy{+Am@w^*Ho6^9u4HU7YLd)Z-GJYslkHaVic@l*q39lzFneC^z$M|! z{nv~^B5}j*wLt`dj27=onXrg@b><;z)8veeIhA`pc~4=QiA#*Z!_2eK7|q)sxSn}` z=)(C<_tg=?MDXIIaqy^?I*k<*+A&Ik-(wB=Vj$b&VhMfj;h;cP>L%(P%#P%-4_2S-eI` z>R_BrIigH<-o?h%`}1S7D)u*Kz)wZkpU6(SdidQ9VtJ*FnXy<;zL>n;G$V zqF3Sv+G4#1+Tqo^mnt+3m3aGuxYGuKs`K6{-8Lm%KgbeTu}ku~_=$UX)%Uqy#5UpI zR6BnWyDYtA@m^x%9Es-53-6@5!XM~|N0yxN^4~sut+;cZc)VifGBv)`!R{EfLA_Ac zO5l*5Ja50W$SV*&sdC}+*)w%axga!KlfaULg8CBWmIE?b{=Io#a5v)!_#xiIfEF1i z&)rL z#-0?WBCivqft?FlN5LbU6rc!JEAni2@gV#{aY?s!^|@LHf-=_8@J#P)gHJ)yHP_P* zbsMd79x7$>cZ)hq`4AKLFGFTRHN^LB!y5Ko3CcOB_v5aDu6FG`ouJ9D8bd2A?}7d;`LT;L^w!vk;g za(>jR0?eeuqb@8+io!}V>==8%xfG0AUJeH)iG9b@ZzyNDi&J^rll34?pds3v75KnV zB|k`SD085~izp-p`^aUo9VUYP%Bo5WW;>oJM;?{a@d)%z-0KO73On@G)B5VDj|fEp znV|3h;#|`Lml=epl?W~gD20p54|K^U--hrmBy~{WZ_+7@4w_5irNwDI0}P` zBOI^$E*XkXDskQKk3H5dn=1c_JnYzWM#k%l*hekr`M9Nx)s|oVeQYiE5=p#LFDl8J*-Gr0mfpy3#y2qjXW{j+2#BNH4xF9k15h{|PH{rDfk; zmN(W3vZ8mUUR;O8&uU-B_vNCNcf>!l0}9KV4j+ITDZqceX&w6A#-(7<#=CqBG{HLv z=_s!hw_t^g)|;I-P#nBC{mJ2(N5*H}1otPV8Uxf|Up4lTxKB6pgoAkt5<%?(8F?12 zl1t>lpaK?b(}z+b8@4qFbeB5cmD_9iV4!`UdUncSyp8(pqE#mV{46Ba3Rv74I})3 zpg3L`o6gR3W(4}dHp1(G0Co4UNh_y@;@7kcm?4`u@BC1CsxbDPgA$JA;T6;fRk$V#(S zCMA5b&az++Zh~?$oIwvR8ll`n!oAI;m3*9%vF43atI}|Ky37O}wNyFRO@lomgJ)F{M&EM$CP{5Nz5{19RjI?x8@{yWt!Nhi8Eum-_8ebn3YtS2aau$h{khVOz{?Y&GSpVii2(q7SiSxE zyd3um3Mm)>EkZ6DY`5R*!@I`bmg%y84M9s;5y}?EQ-`t$TM_#Bn$vIuKnAj;vAUc| z(UvlvZz%_FHR&5N?^IW0|j>@5BZ=o=*Bpx zNFDAuE|Hx}0X6?yzld2^lCRT6JvJ5V`{sepy4Hd-Du}v`7X~)~)KX-%CHCkSu{{hB zXLRD?KkvB%{};VHSDmy4LJNf0C{&&aabfMq1HI33*X(2+ZnQgFQCT|*0cHmY@$u2W z2g76R@a-@`2(>X5GEt|F#)=TkVGt8kLyDwY;7h~cS!AdKM4Uzv1%Mly=mPiOBThG4 zM8G*XJS756e2=<(5i`Vdfl93KJ4Ek~abTRk4)Hra@UP>5a9;vyuIY;y%$xi4TM$wa zA7iL@w#K}?;_|XTY3rr6-5azWhFz{1*kT59pWU6TE$#{Ek^EzxUw?lkIu8!%e|!oc&s` zdFmC$-A4T(B+XEpY{OjTAXk2q4|wN)Uo8C9tTX$Y zfmOmLhxw)BN86d6+W(VtLk(F6n}vgut#6IUlFEs($G1y;5erH74DxE$v9i_8;XFOG z<8@}?uo;a0Um*l{Pi#_`4`OlPfpci z>mSPN_O4(DR;-I>cI~Vbd4BKs;F9zCl{IS@1rhjD)W&Er>aR$m|AWl>X9H{o(2yTn zV>``1sxG*Z!zPpBDQ&qe?-Ty^gg*8g*5x15-#WRUoF}Hh}!;%BC$T`E5)?kTcTedqdVU6*|v+y+6m;p8Ts@wle1pE&KaR#e> zNp2qK>5EYSbu7dDgOjcEhD>d(kMvmWU1cfBlDU)oFOV$XL=v;db(hUjE<0W}#&~aO zd8_sy8kdVc+`>kNlk%O(VPU(eJ2ibXgO$dcoy~XGWBb&25 z8ld{pY_Em7yv31$n3$@=g=y&}?qcbWW8_|mU_pFiD07H!6nu;tZrQQ0)|Ffyg}7W} z_v0{fN=>r%j-ETnFg%d&#?os*Z5DV#_$-K6d%GmRk|ne>uw&BQn#-&LqDx0RIub>z zFT2&p(tTNawUS|5gSzS&2#^}Za$nd`oY@W6G%U;4d={UcaG#Z7vv(8Qpd4AFL-RyhV^oY(bO{k!&0x8T!xBj*kqWY`=(wpP}Ox4aY4dv>K1 z#1+T3S=}CsadXeN_t|b?(^+SR`{vK?`;jFDPj_{mn!B)WLB>V{*3d4|%GJBT@tlwY zO-tODEfof*(;Tjs@-Dtrw`dzI)v5ytk*^i*=qIbSRc+^A0!hW|Ql5<~s6=WD&o$L| zGEP1pII%OmJNf;gvO|c${A{4%eYfP`y~&eV2eRL4klUVjI&<0|&I_+Bv~GrPwf%P6 z7qLu_PDFkA<=BPm0~gl&8_TwhBc=R|;B2KtmP7P825ifOT%bZ|>RpxAG{(AU*LZxt zb@DF!MEd*n#>T!P(B?TELN`>%h2yj7^a-VXPh7gxTU^o1J;=isiNX(k2}nOLr^%90 zCwXBtMG~U3e?f?M3W75ngYJ!wJBZub+^z}nmBZ45G`C|TG9B}NiteXai^iTxx}=u_ zKNSSpia;q(@t*GXp_G?b$7mmwy!My>cx=5JvL{#y-_5`B{}`Z#E=^{H)0rL`>-Lao=QX>Af?R!uyHo?8$ZHU z-B|zOH^V)a&9xoHfOB*bbU#dHyj}X~kVTo#oTRbu2BJ(n1^!Enbf~&tkE;<@RH{CU2P1Ygy^3% z0#KU}2ZI8K&F{`Lr9mT_E`jk=Pi{zh`-NAjuJ>3WNgsnn=QXF>n;;Ma}xt?v4veFuY@85~-e4E*;gLafWS3#*Ri|lP{Q%Pofpsw9$%ut_f zO3&WjULxj{UWS*(y+=rJ+-lAu_Ks6_6?9=G*QHeyS5)`AKaJ^$x7%+JHf@!-S~2Bj z7bw1E45_iNA>`wZb7jLaVbR3_eZ_K7H={4U#pZPxKYpX+X9o-N25p)Ajb#+Xye30D zo`0NwTH5{ANbQX`-U}I8ir81#X&(8QG|zA9n%=6Wm{gS#8WO$TrvlbM)rTQz5A?U%%kVm z6pp%h1$zv%(@avHEGL}A7E90eN=8$5eP&9sx|p`zy9*rcmv`#Ee@kEvG}E0Ud<5Tj z2Prl!BkQA;(Nl8EJ5Tg7rrC{Wo!jKx_q%7B=TsaP$>JKZl&<*M#s7!BH-Ty@Th~Rk zY$!!UR6s#VDIy92HZ&?oN)Ztd5dEl^q?9g5NJCPaiJv=)20Ei5U&3`d0 z{~ne<0-3)D?vEe-1IPSt!D;q4(D>hD_DAIX_rU#c_L52?A_JLAjtX{SD0w?x& z5zJfvNt)8LeG7{**0&40np;z<%90RnI(!YTL_@64XMhT^($}L`rVrPh`r}dVR(<`q z^E*`9TrlLBxnX6)7aq>Xzjz^$XK7vMQxbFR)4gBHT9^na?b&z)Kggm-7sh0bBt8Io z+m2AkE9j$@!G+jH`ey!YqN+E|e7lw)B&!?|b>->H+mAYzi+xcq);l-}4TMhpe**-} z4UUIrKp&nzxOU z+rTw6Dd8jT;}u6NQJb1eybn*^!Bls7e=I1~)tJIF5@Qo7NM7W3L>07I@^SPG=v;(< zbB@txqBR|F7QUpRz4pJb);)horuc)w+)lGif37;R{Ca%N7x(~clvXdREW-1!HPvPf zN)%u}EC)q8_80}{mEQH$i&0q(bo+?9+)000UlBVf=#xhgtd>DDi)MIO1FVpYL>;QD zKiTxaAEtsk=Gdhmok@p7=VxYf)2;16dgvISm?A4gc|KOc20ggVDB${TUEI(C}E zSD)p%_!_r}LRrdT!ODHo>WYCLu9|^C3u)KD&9+pE` zBQw0+P zTBLMC-~tYDIcliMi4Gc*tN_%BxRwg)!;FUxT6EN$^X=4W)D1iWlH&D`HuR<58b$pK z9c$?J-Wr@>i&hG#9(AGy;Ui2jj38RJIbUiRTbCY;866yEZ$aWPOOr>ZoC2y_ zCQ0*uuFBKS^&CW1emyY0W{eD}sXw3qzVgrR!);EDfpHEr-eXcQMnk*LI|1EvMhcrE zUrXpoUst6lCEDT&+u}H>gqh6)`C@69W$(U+?rDSCd#TSn%&-+*9~O@3wDvAB!bF-J zawL6Q*qM$)x~CLPN;W^46dZ0?SGcjY^_|-Vjj^N`{K0}pkeg5+GUW8voP*qcOK{!C zyMVd1R>`$wlcGM)(fpW2!i7hbt_Q+yS`AZsTxJ`+{Z`kA&j0b1&9j5w-|9X8CdGxP z{(uy4rH)ugy*GOC+2AK)iEdE=uzhu+Cy-BdN#wp#fgXnS1HUQj;6hh#g%$ZM+Z|OT z?XD9lh%40Bn6BoU$|KlCo4YX-KqEvu0UlT{LoC|C0p+sVTo=PvLAgOO@E8)uo2lC4*`>d}Ly^bQ?YAcd&VNt}sm@JsV3Hip{88%O1 z;Glo2r8Htd>)SI`VpXBgYSkUt$j|if8?8YKv@@V$7sxnD9L`{DHr^)!g_C<7oJzJ5MA+#ngJ@u0aQc|v?HzboeI++-&r#Z-$FhxM5QXpuxR6TW&M5)X zpNQp z`8qY}pXO?TATqwidt|qPz^BucG%!GY=ib|=Ubg(sbpGk!(ort592<{bRJ4FjfXSh5 zCB^j<t=cSZ>Ys*xtrO+O+#()I>rX0H5|+1SGzjf@O9SPcs$H++gRw(oy+5E78oP4TsfdT*R4cpp(kpG2b&171_o*Yk1sfk&qIHK>+b%u%ozHo%NOY>>8#{`= z2Z{#S&BnSbjKk9>3!UQIQY=GqWlIk=XH=E&cDd%^;xOp#MZzf(${{ox@^TG zg2|DS3U|8B45udMIx|f}$^#{fHop=RV^bg>od`w=8BCA&mfj$7@OOltdDRV7erDU5 z4)Iv@KD4H)MMmZ&7V?!<9Yod1-c^HBXo|F&U!zFBKwWC=w@MVv}k#34pf*^f)xK{wKxpUQJD>&JX!;V2t|LPf0wjB$xh z%J`hwVHLDcNjDkGH4$F)*3XpebD-P6#Muk=Pkz^Y3h> zo)bC~N#3Ke8}bWpD)nE&6qUQ=Q70DRskil4>SIPx8!?ZE^f4at&1dDQ)G=T5r>fv# z>rEnaDlPiTAasV2q*Ai;c*axFJ6?7@&20_ z;7#76vV%D_w82s7{4-S5>o%oh2aX#`Wv{}^1o8bw6dT}CPD|mc(8Ce3EOQW8iV(I4 zFQ%SUYoI?)Q?kr--*%MR;4PozJg2EYwwzEBphGI0wV}sNL;7E2)PfOFGdsY{=sm+ayYRaE-nDdl2d5tVM z`{CAs#(mFsT;IbQ7k3f;?fq6SkL5`^7&}24q~18BP2t-m%do>G8jOgWqdg+Dd+t|- z3cOR4v)26;=l#nb4crrY76N7y8$f>t&c-k``Nq>K)lIdtn8nh=kg>P@*4|UE27#GhQDG4;ryvKivjkU-t=m;;jA)w8{u~D+4=oUC+l$ zZsPV5%Y4)Mw;U(%V7^12f!+yE;dR6>xifBjb{Jl9^yhCwT13jXmJoJHi24f>&G;S} zO5wnM7h!n~Rq};6zuG&wVNPqQlDdlGdq~4(>vGt$x)x^Tx%KQs%&r=|l+Y8Bz?ru2 zM^rd34XwjT|4D@8?N9aIA6FnwlhMmcv~qsFpg6X?FDae;L8Aa@Nxue$Rtnr#0Pbxl z^2{{}2=?>Ti+>y+EbHKQ;5qzp=HR%Dc9ux=BT9 z`?QP4Xz1}Zy$ABy%s$57P`|`=uz!Dherx%%Y!_WioIo}T!Muh(d8O$=#LTamP0WrE zt;-jK$7txB zr{2j;3(omCGBcBsMDwetepGNYJ2KZH)t>J>lZPS1_WL!o9i1wRdOl@Klv$xvSCCC= zs<3>&`a7Q7c39mDymy|?T|LZo)}<-0NmU~Df$=i(efC@RIv}ua;AvCV?XMN& z8tFjqB~e0H(t%Ya28g5W#cHfF4c)?g^w&3^O%rRApXZ@fOwnfSF>F$`?s%s$l{z~S z<{ZhNf6cvY);bq7JJd(5n4hgcQ6pOzDsV4`oHtu8z|PPs znv+g#yro*5%qb@{@CU~jUMUY5JTpLgbp+*dOmnBZ%eBw(m&<{$o)=J#ID9GQK-WvM zvL8|>SY5fOa-d+6&U~9$hx>3bv%ooog~;q{=OYE5p%6hH-+itg8wM|>lE|X7eWl9m zjn)A#Co9$>EM?LM2u%w0xE+M$qN@JocB>YeZy)&;(R=QJ%&^XYU)>Rtd??Jkg1@dY zG24m$=<4J7$uBHneDd|Zv?2MsPw8Jj0wbKS^ka}LZvuF?IbZ3o(&X&+w14)Qd1`Fz z{0){GfYATKqM&TfLOVK}DaboS*lmD&hq9)4^rJxn&&l$Yj)L zLH!QL#``CBXJ}H~u@M?ARsk`Kb8JMfnQHwzgzu}W__+S6R5oUJDR&-NAOKz-H>ANJ zmNV#P+e1-?ZxA{0dh}t~o{^qet+lUra|~?w`v(jHmJ@HGIAqW$7*5NCuU*XibJgYa z2giUvh;#C6DfS*p)=2NCrd$YB8)(yTs=DitQfP%nn{OrN%Q9lFzDhN~wYLL8TXf=g zyQ>u?Vsj=1Z>_a@o;v5BjoDY!uKI*g-`Wrzv_JD?I}fV3ogJC|K5K;gwNSE@=V)w# zl6KN7u$y>`p1Tv;Vsa;nBqpJORF4CwlJRVQcn|Dj>>OVe5dVEO@x$BI9{+N`OY4#~ zX#bw>nEm_PUlqLm#U!)p|9sK+;r~BF_vb3&IuHymqa@N{)Dsy`tL5M2{T=J5*+5q- z<{`otce??fKBR#;B|$KITl&zJ#_&Pj3C`(BR4ORt@@@g4QdHAn?>@|{4>?n?t^Kuq z2H{v@aE6)hTdRVj;`F)YfropM?AkUeeHohM45AxhQdO3@z_hQ|=va}fcej~azW0;U zr^f!MBp@EDJ3LJ9zVoz=a7|K&;#~mj66Lc%=vW`CjGJzBg>{KkvwY|Vy}NY>)n1kK zE$%iEt6B{fCw)m^K^It%m0j_S!aV(A{pRQT7E8K{aS`HRWjvHb9F!6Tu#F z3ONU|DnJh%1o(2lOMK%J&hws|&m_o`s1x!dL2=E*q$SLtq;K7>;x*sWNBTwEn}&62 z$PcM>8GaD3vGr0>tvJ6=DBs#%G3d$qlJtl6r=VFR9j!gn7yF1Y{1UaEt|CIyAZ(!s z$WEs}o+TeTbgfgx44s!#%@VlVOD|wQEC04^cbkP?2pe6RvEsMCOJ;LJhcSx7)&2FB z*31IiNSh8L+vJ$%KxY;qg*O0dyY<3j@Rnjn$GF(|2(~{6A0kN4jYUWyocPYl2s09^ z00jvzpY4(;;x5-R4We@A(@ve6ZER*LQy*IHl%tvSJ`V-dc|w$psr)YhalGosm;8LJ zXgh!xYlaA=VG(o~Zz2MG#Wv8~9xr1mL?jpG>$dIh?QhZ1>U}3uOvII~!uKu);6Y<~ zoezW^AwNu)snYb!6>Z?fayJaWUo^mOhwey5XDS-1R9rvJ>V9zSbg;k9p<`GUT*-dK zmJ@~mxT>)PDz?Od(UG8NxEJ<5Ep1&7eRp+PaiSUhWJL6+$!~?7`_luzmdAbBlVGZh z8?Uxv3>UC}(nSq`Gly^0n}c~1U>!!G%JkZSqdx4n>0ECPcUuj8FXTB4dzk!_Bi;l8@N@3j9~*>Rn}KP9h;%G@8)Le{(94cxGt z-!D@cZ43C)Wt)@fX9+z-9w-&w1IQ8mI9{ZKgc;f33WKWiNTdXEcB@KX<9p)|MfDudx8mTlNUj?XM~ofY4|YR-`vV~|*O!5ty`>t}{BGd;A^FBN=N{1;B3ciUWtEBi?+YT=NMDXa(zV2}sX=Ugaq^gv1t_0z3%ewDeL6pCf*r=s6 zT@TxUznuH2wqG%kX*vj$iGIKFloMb-(HleX0|9VAFE$pohUf+Ztd^44Xrd?VLE6rF zV2iYOsgYFRO>)Z#&3Df08?KMjWvKsAKp|jie>ZN1zzw!e1Cr%CkNSbG`$U6|^il)&EQHXVfQf>oG+7X&88hvx}MjX|x zf>`c5KiX`bVx)Q)>mwS4)Pz5a0{e~Rg-1o3d+8g&6G(vpuvCu|>rb-lt+|>fbj9bG z2M6m0ZW2$8r8)+I0BKHPL}L-d@PVLFRLxl^h38{E@V$Jfz$NU#UoPXvN51b7K+W`>9@I#J1srRWOO7OM|n#PaB6IQ`(1~vT8U8v*re=hlRRMl;_0I( zSJTxd^}e$EebYzhISuMyFV9-EJx*{^oDcW<#PEuhsRLuC6i=s&?7)hS8lCcc6X?prxIAwWWT!*J+eVk3FG8E#K^$z_|gj#q5ep3S=_{o+cEt`^W ze?G(9M>Ehr`8--endyA;*+^O}uugdOn5OEm00m^iQk~miII=a!lkeD^L z&ldD6Uj2H{+~W8$!G0Q+)jt(&S23ab8DQ2oPa{J>_fmG zjnN;%`5waWk)JQ5KKX+RyAcKNowfHlab0RMai&-3fHxGsF_O}GNZ$!nn)4$fvHpFx z2d`9K-x+Tob@Gja1GAZMZKzs>4)ibXc@x@H_KDLOUhT{rp#!ffO_{e8L)fET%&E;$ z3%f+se;S#f`$$wU?m4IN$2h$%t46bdfrYUQz*75-&P-bo{sLbnMnKw}xKE#Y$34Ng zJ0ID;w?U=?IAkWp4*5pqPP%rZw87nkB)Wk|gM^u0oR-01taTZ1hDJ?#3ymx}e4J3L z$aiMK(RQaYWZ!>#jE-al;Y)upDlh{_n2z^?WPmvoDV8hGvp&a@y-(2cx#d-Hk&=On z721>{&jbw0P&2ljP1#AGwMoegVD?U@@Ayt#+2}80Frsjm1AnNpCS?Q>c2h4f^q4~$ zN2}1c6rx!P7zd@ZKTA45u4hl)iQmmb#SqpF;&<{;zvGc^BS3Kp ztyDxo2a7Xqc~-hp|9E0}OtpmZ@y}I5s9903k+M(SA3l@Gl5l=1XR_XK20!^=Zp@H0w++0lm}1tv9a>B@fs-n z7U^ApU%)MY+7b}W5{!NJp2{m`o{+C%8(^CmT6m=)89salD|Clsuk=)@4#Ri(I4#Wx zb8wapJVE71fsBT*RtK}Su-hV0mcn(6k7uf3M_EgOj5Xjofq3s^Jt4sb74N4Ytt+>1w1<-4cg2!KWs!cLZr?~ zkEYA^8JUg@w`C5T{%y#8ywCkpWuo+c8)_!TqiLg$gaJlF2im}4#1`@2d#HdM_nqg6 z><%-<0k^7d@IPGTri2kkdr82241LVOgH;?TV(91zFA)HphJt9q_%%4W7W3E*+fI=i zSnw@OU>t$;N-*A5*5y_elUWueFCBa8p0+#wv(D9&IAc>uqEApu2LkN zggh3`lyqH#Oh#2XA;Fw-?-X0>k}Ntp!lsxWT6qrrb>lKHRQ|x*26#$RU}eBy7F^*Q zhXrI#(oaB7m>DEaJ9dG`-^qkZO+T?5m&;!H5mg^0%ItW zf3nGf)~{W)Ez>IB)Xj*yHt0fn4bnbJVn5&hP8QPCX}}q)i#Byj4vSQHa}jt=;aSri zyqprw+&2B(eaBjS6OWkr`SKb+R2aCtEy2JomgXQ0Ms2Jd9z5;t`QfjF){}o_Oi=yM zUS4lQT1sy1`?tsopO{fD`4i%CawO4n4pM7+^j-IW*$-Tv!?7H=B5uJn?%iQ1s6pXQ zV~IEuUU$loE9WHWbWij-Tj;d^JWWG`&aR@^yn)^Eg7Iu&cL^Ghh@c44i)~fMLPi-B z^Q#p%{7MwdA8i#(Ya8Vx1ukr*&-pa))kX4|0xT~v%A+&D`jLukaSwMfsdOi4qWnPL zQn9?_UV^bzVRL1QblT!_wNM}27utkFxMR*d>ss;-kv^1Nzyf4KxJV5>qKQ@VrW(se z$X9rGUwY`ESRIm@rFnXO_A~paxOcc}505;8;L7oo1+9=9Z)rF+JvIwU;V0U4@8#R{ zV19|MTQI$~wy@t^Q!~G@P(|H}ts7e@B)Pm!b6dPzm(II{-odzO9RsSVHk+e4WGRs{ zZ#I5pr^wuo`1Zu!Dsv>$s_rIQDR*da!MCVnSW&dyV3^E>=is8IYZ!8PIH`_fh^0<_ zc`sWJ2G3}IV*Rwn8g>%9oOt9!4P zR+RMbe`A%{vgNVs!A{B{|?7?qFZSq!1 zjM2krhdk6M){Vu%J^ASa7Go(He8G$z?dF#(3 z+tAo!hn}5QTp76j`Q?RZk(I)*<|u5HXcd5|*x|K_tEONj#0bL14;*&b(h!(#?sq$s z(-JRBIubgZ_bK@~HMdd^y-Q_Ur+7=2hK3ne=RR#4@krq-a}F6Ffe?JpGd*-^2y5G| zjOY`&Wq-(EwQHrYmX+KoRaRQ{+v5IQrG>;-vx6BAfd1ED1`#}mJcx2rHp~H$4^Pv@ zDu-V9%JR=uYJ}D(BujhydS=#)@Xu8<{ik0N-<>yJ2ayHAJp@eU#7p73`2 zJmg}*&}n^Z@j>Z*Wt<h;1LJJPkeNz>J4l>>jE0 z{xRymg6wPXq&}rjpTxQ%G}}V-@BkUTHkikVF;*tNgNxP;GhjGHJD%)ABdHr{DrKeB zWwdmFL-aI7QR+J?b>jEq+S}&SeO4aF0j};r08ie&I%br-41hs34Y%S4plQjkMBWh> z_kUXC{#`%-#@>412}A}zc;|!PyqWi z2Fg$H|7rWgCFSQ1FwrmijD_U_kR9V0qh|lSrSX4rw4jdRYjcIj`yD*w+sDbkv>M=* zVqpK%_O915&6={3Ey}26Q7=dcjZyr}f8L4*;?CE=>T3-e6zP*05~-H&+};HTJ|Gf2IdpQ3;ze*%C5&f_5|9>2ryBLk>116kw?r!6B^;kL#GHJB+Ch{lu z7iZPs1Z@2o)$>{TvlCd08N41R#^7D@y0rMLNrBVF6t}JKFy7ujy<1nY`N0E*2Kh}s zB1r>U&~0DbopCz(X<{N`aAF4Z>lQr*fguVLGM2)r_wzdbpEIL5EaCTyKwV|{dRbd#${1ikEU=lDf;3>}mpTkB&M9e^ zZ;suUqB34*p0;9$s$96xsR@+9HD)md6_rBe@E+2hvPi$|+CzCDN~E>Hl-com^|#AZ zeQ~98P_~L?a7rLGRKH`5SMJfb)IKC>(nX!SL1>AC3G8`i3BT~iD>p~x47#YrlV{b+ z)8;w!nM-wBOP{@G8#G6?S=dIobm?WD3-HM6MqpnVCTpfZdi+H2YzA(OsRj*c(tTR^ z)0{y^N75XIhQW|1?;McLfBRQR=`P#^Q+76fk*f?3Ui}b|}3Munhb;Q!3 zup+&=p9if%bC^?zJ7>vil{GB^Xid|j*AfGJpE?GG_7&B+`8M{g*frYteATeY+fa5< z-wPvOL+E5vtigcYXy1Ir$JMBhWftpDCe#YAa)=vp_1Vh({6VJbzH?{w9(}9j%0_VT4z(qbXSxaaJ7|<~WY~^hTO>lD&<562kA8 zhNzWshiY`Jz-2lOMyaamDk?QBxX;!KZ^?Sbq%G}!!w8*xBdwR_%$~ zAsVmu(={P?P%7x+$<|vdL8iIjXS*z34+<}WZ3Y!Gh1^_Il}M87oL8sf%^2PDvybxU z_mZ1~FiaL9SDZso9>Tkc|G*PDyHB3y^x&;HZ6@ZFXXk1Um9Rc+^@R_9uG51iaCjTx zWsp&V)RFP&&sEmYExvReN&zoDDs#lh&a*=s_&duEcrREpE(x7=o=;6pO&2F6MH5RZ zGPm9!d;zTzHF-VEY5y3Fsmb0{%=O?an+vyl&}|_DaO94qU@b+CAngC{E^Eq5yerXi zVLC!V^@*{$E(4`Y>HL|*4OsvAx^Y@_;Rl+ZHkYypd-ODm7x6m3u(i)m#am6) zy)%OskE~flbJV%AnfNQ6RfyN;O1uOFIlc^7V(O?d{2%Kl_o1}DI1I*Wy@;u#CUe#K z3hC%fYPQ;9)}7nADT16N2N}i?JGBzMkci}k(&I)#kcbh3`->#>uq2&d&}fjRS`v3I z{Zc6+K5(+11_&2j%)&Hmd-14)zlUVeFAyb5x``FRN3@*ZIk81YAHn4*n2S1_kvPJX zGwu-VT$H9^Q#VyNIu}5ENIm&63cU+90u63%v(#6EdL5>T85F=*<-|q|DU;5iqLNel zQgV>Y?azi3?$w5j2VvN^o(=W}&Mqf~AVS?=pC)t#A2Yf2rSh!AIb!~Ez~!$)y~~k& zGDka`e+;Cy>$yY^q-FzbBZm%fI?*v3h0a~RswKsaMyK$k0B>KwfsFZsTm8%D2YLG{ z``}6T^iSg7$#_FQ;o+}=gx$P|>|uq@Axjz5uRVK-RPU8PFp_6#@l7ixrAfq6{7@iI zNcXXQpfYH>-S+?TekA`QE?{_Dxe2cFknImh1Z5y(bW%GpchKh!ok_MC_7jw0&qN0N5P`qhNP0YR$L&pp9_K5LAL-&%` zs17f2-9~hxk{!ZcPr;-_w>UBIdW6H%0qYQAb}I>cs;Wes*QcE8e)dyTON=o0g61{q z$`^u8*I2^&?#ta@ysimN!pqLMMGGg=E9X#}cu*_M4j5*98bAJxdzxj%cYUfgMXD?= zG3~&{C!-RVt{z+0;x>Mq{K8mDQm3MrCJ*N4#~Za%bf?~6Bmxnag^C9}O{uB0&5BBPCyfX&=-4pmirkr@IR&u8d?{in zYigo76@9|J`xjglOTHq(!vWc8s#$PU#L!NRAjmsHD2{bq=;e9`mA(AL8f9Ws1(i&i zY^qsu@`&@qoC;HW-s!ESRZq&w7EIVb4J@#%yq;eGs+=rW9k4{ci-U0%6C0#S_Ih3< zg5uO2!c+Lbu;S5a1fq}H%`&<1wAlMbV8!EK7&>i|_BK1bv=C=$&;XNxtVg8kWOw5= zD){xWSPrYr*HSa(lm*A_sQ5{X_br{Lw>t~oW>p5=yjs|*-`_yk1!?N%qWNwO>6?t! zjI^MnA?Z=i2z*Em&iB+&q%kJsV#yn-@YfPrJmSdP<^|KO`Qmze`(=mf(NS`v81!X! z^QDLTK|kM{euxhC{&STjKlcFz&6ahY45Y&--AQ9!Y9X&*0W@o})td)#ewZMS+RkPv z=0g%wui_)k0nCTbtPhxv%M=Q!-1a5ft*CLycKmIi#$HCr;Wd_^!Md#WRK9g4>P75I z^otZUS!e>UH+JBnQcuc0cJCmp#+eOQw?~LP!F8U9kQl1X=G`h9JL;#7#xGWTu;9R z0pRz#gKwb7QPfY&sr1eG(BV00kzH8`YCSIQilsfJU+s$PxbESswo{eXYpoBt`e=`# zIM@XGlitMxrJ>s6^S4`M3axY>z5dvof0SVVMZ6BLDAXr>B5V`Wh-jo#BSC}CR|r#y z4Doa0O}q;tDEDOG+@J2dyBK=LfhMQb`lcky2FqG&tM8Z<$g){8*O;mraLyfYMf|t5 zAOJ*CTrfwM!@Gc5`#mE#XsE%qRGE#)Xv{eYjh_a%-=qdoyIA#enuFF`MeV;uW%zaU zUmm@|3jqnwF|^W!MAswpdODC9IU-%R<$B6q;gw?=%fAIjk8Lv4NqV$tgpLtc`NJXt zVU+&+qSJgdm`GFDq6;x*r&hxm)tE7i(bS+q>FK*;@l`^)- z%!v6dS@{rXYJB*OT;aDT+86X_ss>7e+E8BjBM&nmEa%%(Cxciks>8V*?#8w}uQmU$ zCRsmM^Ljk4#nzcNpL_hnGJ^lD2s@G+5fCd6ZM)R{4N1VtOkF;A*6Ds=-ftbm8rYcL z`HI7~uQSwdtO$ffpa`KkjA-0IIF2)=eEsI!rfHe%fi$*!c-@0>|A>ljz1EwpCk@(= z@8KL*L*d5hJ|)HsgQFZ`gWkl%JYynRthRA+@(neYUC(L~)^g~+=uUzv)JE71t#R&W zHcwwtI`xQ_!|XL^_Z!O~YDone{&s3A8y50>uJujTi%HJpxxP4a{7eOqGmDrUzWkte zr9W*FrJ~xy@Vv%f?Z4aOILqQ>uE6p%RkSL+iUceIAG!5qbr~NoA_?I-v56*q84g{n z%aUdsyu0kokVE_OWRpc2=+J7{YYxHDiMb7|m5#ZcjX{AK&Oxd?vUU$$4b*nFGu(HZ zG${3@7kVq{7jT5pl`ZB{aiN&i;Y+!~qnMAUrsifcZoL|kd?d&u);*be;-!;ad(*Aw z@iZmUYhYZ3OOto0*qrl||JP=cW$w<~)w*?RN>i&OB&REi6%@iOW~Niwir#hVg!^n|mxXyiukLK6H&$yU?fprRwIz+q83vmK5^O%S z1^0&;pG4x%RPa3o@Q48EXt<1v^|f_%pX;_xC0^q3Uk*N3rX%M{5$+=-{b_q1caReE zEgAEr^QcK+@u8SCDv#ypmt##_b%a}|73eAuXxpk^?m0BkR|?xQ6o3XFb&(K_u zr>ef>+#7}wW}3e%aO2W1-y`+^CQS+i4r46}Qmc;#%OzDA;Yf z-N7^YUzI%FaDT6sqU4G1oGCIPRzUs{@g}|AfxZnZ@BLxaIaQh{2&X?Rmvnq6S&FKx zA_<9+0igx|R&RYEBv$_l+lu$)wciy5fI{v=DB~LMApZ1-&L&C~`Pw!yHP{1}eRur4 z0|(!SdLm*31#&45g*OR(5;!D3L0rpHz!{-?p#K~ z=ds5z`T0Yxb8`r`w5kT4q0LnBh%4}2p{ycYgjAh+=)Vx(8O)Y~776|=cGD-E=SVd- z;t!+K>&YrfByaw&&l+-FN-y+3VHqU1f)zS5pRM1Avf%eWI3&>~zB?#db9<$bDD`cT zuc_fUwWLHDe@ZspUc#j%jOcu;FHWD!G-%DwXAJs?cRV}i*5WJB#kZ9c1XHVcFlfKd z<2}II-j6dqjg|R~bLcaSuEZVsQe9CMb;F#sB$)q9SbRyWN|Csu5+QUOarm`UA#sBQ zE@^|w>#>|CjbS1~zIK*mZzZiXq@j!6d9~B|vyoTfkW-7uhoK|dfS&PK&NC_AMO5VV z1$Yb3U-@>lDya9?#!7^PH>uP+mmG3Rwt2TfEqxzI`~y;^75IoS|MnJ-aEek* zXt7OF_F(KXnide!7@G_AH^O)q+wa}cTB;{m*2|$$J8qV;P+Oqe6E?IY-G?d{e%)XB z*f7Cvxf9tmIIy746rT%DVmwTNf-`b3t<3;$KuI;Cv(ed)|3K`}ppIpFWQ{RG=&Wgm zT|#6XoQA5=xyODB=!WdboYAJVlMrx~LR{(5LrFpTOy%lO|CWJ~vt?1Et>y!n@x*)Q zkZj{Vr{#XZYw;_BF5XA{6z<914mC@kl6|+*m`zfTp3}Lxh&*QAb5BksDIi!ORg$#n z82QCircY^_>s)*S!)0x{Hq#*~B}izW#k+_Y3%oF1!#|U`BgBc&WrG>44xlrUK63>= zQOAJLOm|XE%2J-nn+EuXJ5LB1(&(hgQOh_MP1Iggqq_pq+i|*4icS@w)l94f2f*jx66^ zBttKVO+#>Za;#&AR*Ta`=jr?S_Wd)Q0Foca^1S2}nbH+d;nCf!ED;tY^(frY+gD zi?i*{fucN+c{~8bR@iFMPjNMOdO9T^X-b4bHkNPI|3I~Sta1S{!$yZ35&XF-K1iJ2 zx*SMevPW~RA+@o-$`GLpq*i4*^4bW4kM`Ssvd0GbYLKPXQy?n}ZQ9PI>e(X#J~xAz zgWLN}_77Hym|QLCRXyJoI#qGp)#-8e#|uqHkJY7i?af8&&xNoZ7CX^JMJoed_$82I z9WL&57F_Sq+c5_Uz#QeySuGtSlPn|W8-0ZdQ|oaFW6l9|c6GTu4OBORd{G(u$}Dq& zRiB-B^^~Sr;Ol@A1om2?&ldCd$NJ??IRsXOhkvnJ*Fb4KbEQa*K7Z1nxtTraV=W62 znrbVw=;_%5%*mC|U;23IKUeK(hgKJTENlM>s3ZGHR-sG&t}+t`7CsyE{Bo^~ zTBkz-e#UZseF0nQO7;}!vcGMmi@@?{rz1Myli;{XGQ=Kna( zzbbyshkfcMc324KqX7Ua^M?xr3<)#-Q~MF;Hd+shG-7|ehLXk<$vp)7IZ9-@%2fid zO?ds|^^cwLW_(o(T{Z>GqD$*mG$Q}iuBcc4(SBw-o&NQiZt)u0-iq#c0ySu4< zqX*PE*A(l{6GH{DN!4vWZn$@@;Fu464_GksUH=}klE!p!)Qs;(z0Ts|MQ-BL^{rRk zy0-^gKEmwymXG=uBe&ILgt%c+a->7Mlw{kbJa|%~A2BjyKe+4tpk1s*x_Ot@x826d z&R4F}C{)wO2Qra)N(X9I1+Kb$G3M}}t6uzd5B%CWt|~e6_c1e!Z`XFU{AxA=lLm!` zZb?)A6~`%u@UUfrONE_VPwp-}f_O=D_Nr~n&(B=78R+YYWA))*{u)u6A2)306?I81 zp)wo)dyWBbs4BPi{d?E#B8UObC=VFfh`x&3N zjf<=N52pgT|7=YDuh|bZlf?MCC-|>8ozgho-5g_Aa>V9QdR4F5s(+;KF7AN@kZnDI zi8dj2{<?z!Ik4deR3rpTX_<$#p2*1-W)!B{1{#C>snB+=~>v z65qvpkH&1BaNvE+csVe=@tL<%Zkr4@%A)dB61=abHGg`(Xw3aH zKA9M;6JRK|n7DjHc%hxp+Am!$kC-#WEPMR~m&f~;12vXJ4$CCx!-%y!Zd3@j?7rDn z%hKxoOeNsowrFbJjB$%Xcre*Pz{=pV3;wr05v&Lg?-T*a4MQ2XCZ)E%C$)99LVF7N zspqM#k45hvHBWCIEX;4IC@F%wpP*RZ2RsVy(qoZnZ~H0cm{Nrq!W!-FQn3PaS>Yjy-o(e8iK*}(1Xi;=xbL`OV`=!$BmDmFJw=gox6uqkczqz80VMpeatOFBxF4J1; z<2@3ZNry_1%NLp}UDfREKDT~fmb!wf()HABX5yp(*$5rrci`$crBuqC5Yz+9)#nCS zqF^!cHaVfSB%e08OrK@;^CM>_^KiBB4O7o>6IZDGrm&&O!YcFislKG6E=2)1 z{@Z*!DEV&H=k}}~hA9v+WXN$vu}19CUQgc#`ElmtagoklRc8C2QpW>B3=cXgs9!;~ zJa%SN2QD*TYJ0k09%gchgfxLk+q9R2$TLo{Aszn_Q~ED)E!k{iGoCDSt#K2i4@$iqRgt~UR}Qexm#7xa|hwNO)m zz3X4S%`~pwRh{1*tc0O7Fj=NbYK(NfhI*FsO|MGARLQZUT&Es!23>XtkAqP8I0trv z;*erVzt;HM9?#OZ_r@)wgRkH4M;VNrTc*#8dOuF1N@SKE7{NHleeuC0ov12VT)&gS zgR9-7kpy|Ks<2H|AwKWA7YnV4Z=0u%xxd_u9c6Qi{e(tL$k3d>K9VB_rk>hps&hzN z$osBC*QhKyqH;bdg#b}C8Y=xntU$^QXhD$n9qKIH_4Up~WS{VKO4E>PG4K8tOW`2A zy~p7r_f-;Mei^O(0Ws%$&gq+uoaE!l#0M$0E49#q6NTR&ZaaIx=v;yVSMb_5$;oR! zl{Kb|+UIOfaG#H-g>w6i)kM+^=qXRbTAB?dPhaZKNZ5U@-KCs%{>$SPCdU^u=Jm=R zS+H0AJ)>^EV|^cqzZbSiPltIq!<_FNpmpXwey7qTPt9V$nMo`g0tz2H%W3 zxH+1aTSM3rd$zcQ2ty4{@~jJe(yw)u(!$z+SwTu_ObIcbqKIv6qe18@h{4-GJwkgR z(j1Y7%lo*>@eLwur_|_RI8!|$Cgrq$xsl`;NUe9rR*{tY^uvT2Lpb%}*a(pfXKuGo zpMugxE3BW^c%o;Xe(`p4HNE3m&R}yZ%P~+q{+T5DWe2=eiu(b>+l5oNs3_shiPThd zOUf8$tNRmJ<|Pp_DT76Q>g@7tQ&YQT+A(~kcOQec7Z(krrr0~YjJMKzb&U6*qd8x7 z%XPNyr&NenN_5MZore;*3*?~~n$8m1(n=id&t457Y69sS`1<9XQ_h^B6#b_;Q}+&# z_m!2V#*g0{If!Tp@Rr*h$ekZRU|Z(9b)~ODNihxQrRt1zWv@$7E zY85F`#ECG5R1q0sh{y~fsUjdCA|g;=Ln?zHAy9@S0wIxEKtyIq2q8g0hL8j#31sNG zyWe;3v-^JQ^sICCI^TEx`2JwUWmcZ&xrgVzulu@w13>qDk#CIL8xhMDBpR$Bp!Qh~ z?@fQGG#O5cxw73iD}*A;IiGY(xj5{sg48qzqaf|eyVI&lu(@g&E3asLz0)V z4Q|h!F=97jFTh9MTPxw)b74EACp+v04~)yasEv&yXu*}C5Up!tR9M@zReF>DZ)JMb z6fv6&|M`SJlH!tNg$|m4s;Xiw9#6W8BngJ{%NR^43W_?IQl!?JI8A-lQ|~&RnBj~A zE}7Vd900J~hi|%A3w7M`y0_Rr>b3R@=_h99W8Qr{-W^XnNqW2e|d}{e~s6W1Dh}! zjOVzeH7YB1!G!Z=zyZ&C(%&>BmM`+>Ml&}9bTT9C@f@f54P$Y{iCZ~}`A&#VZI&Sn zXD$IXu`B(QJhkdKBl+mRQ7UINR;lO=&fVYFsjvGWzqGdl>img`5(!eTJ=T{qNgh+d6I<@-BzcEaHYX}ETTL@x7DH0fji&b0ChEl zFSW6KK3sA3aob}%_LvbK95Xkw()$79kl@tM$7=I>cSq=G!M0W2U!=D`@uWO|p~Fw= zIZ5g4%NzAU=FVWGUv5O!F!%>UOMy8uF01TpP+0&+NL--#gqC(%EYf0R7^8dxHezC4y!p|R!OxT_ns#KpdNEY}%_$qHl+zE|#gkt&LUX*m+TkSO3hW6SMq zN<5Oph2yPDn2fOVXpWwo_}w9o0_A|0jj;DVZo6Oo$o-LPU`L1AXA96%^$0Tqn>pr? z?z5gHdfRI5-HK`WYS@UasGAr5kfu3--{2JxW$7;Sbw~HTSSoW$MzQM0i?MIqZh$+t`h zR7R!At(RvZtPw$J0CH3uW|m@kW{==nn7>b&HnXh3rTf+`_hmfOSh>x>SE$L;?sza_ z+aDO(sCkw;Vj5w3Fvw)ZISa?~IKPz3S5wZiq?(I?n4HE9?U0(l;);lt3!+9X$IlihIp{n`7?_~V>N_dH(ZDC5d2={?;fn(-t*x;aW* zT+EuapovDo>P|eSC4b30;SzAMfir{5LZMkpq;=ur8$@xHt;jDv(GUOT@Sti$|Leh@ z)Jn4Sib^ZgJA{9LqPkhMmlpv*jSmcqJ{8+Mmrjg&%bS;Wc4JD7Ozr>_$};!=b~=0 z2Sf3z914Kpf`?YYFYURk`Hj^><>}|E+1PnWNfP`#D$+yNDG94!*RG#I*6OCvf_T8h z(w_$EU@a+BezbIu7YH6nXt+K(k;xs$Cf%d|h?jtwY}xOL!T zN|=Dl-%}aqo3zof9!;vf+M2R+O2!IQ9bd)S{;GOZx*|%33#lFG3$k{cAqWH|+@j0_ zSi1VK9QM=GSuM7?r&*y<2J~3Iw_aHnQ8TMyS~kSQ4I`-=Vb%1aelL%Ri^~_t16kKH z_scJ2GD)A2W`L~h5$VVXsF?B%aWhB5NtKTv)fGe$>dc^(1y~#8j>TYC9=Op@HrO-X z-0x(;zOhvNL&bipH?$8*z-W~9P|-K#UW(WSoUT_yCs<7POOu{?YekF=~!WfCwL zsOMR(7dyEjH>D@(9^!C(y%dOupQ2>hZ&oOpi!!>eErGtzIog zs|`vtk|CpNuxDha=NLjl0MP&JNL{Ji8gS?cl@jZvu@*xbbWeYs@9wGBMf%u1B`@z@ z;H?-j_ zBZ!VthRjudLh410N=tQ!X6%WGf8!MNqAeJso=z%tfjg1Wbs75$67yUFXIM*%lI?s( zK)OO)p*x9bj=9xu?;$Y>#)nVIkarS6=6` zL0Fb!J(nfxsYCTNpTB@{nwuW7*<*3Mpz-Lg|AWZTzd(0(i;ziw1nS5u!{p@d7;}(c z^l5Bt$dkV!T~Bcvx5IFqosSU$gpd!w!7y3KP;T2l>2wev6LxyEx*&x=H&!wQrUy+% zSsd+-+>hOuI$C?mgC9pjoHDXfbLg?dA<0g={LMAY#lIolyw2aq8INd9`!S^nuF|xn zpRNunJy9Tj90jxvR{{%h1){^Yd+_wIS^(N(>j(h-SJ$qW^z6O-oqDMCd26WStNJV( zL^&EdS+VyffXRS7+5LnFa~{94Nh};s<27EHoV@sC{BXZPK~191K*-<{mes)Sk4rK^ zJ)BVnaHxj0J|KoDuCm-ivL8Z?CzE75MKB0kP>(<3m8%WeSZ~Ws8%qlM13&y zjg|a1<}p4?@V(=)Sg+sDR|4jd39C| zWdLzaEIe7-K8?&RX~aRtrCaPpfS1=KbN@uC$HW5!`6o)ghR7_vWA z)5}irvuu`R_pYV3CHvogtLPBFs}e3YiVEAhgLzH~%7a4dk=T$BsY5uvD1h4Ea$}f8 z!K=n3-Av^>-0fniyTe&qgxK1<>2Glj+0Gt;Q|#ILjGORVmtJSgSwoj3Ji)@UkLa4R z-$L>j;wDmkWVJzvZ^>FaUt&4tzdRa{HXHSTkN@hha{5!xHe>uAJ_}9@7TUQ;HxbA2 z%~=Va1M;BB-cf<;V5K&-J~Nu+>zahHE@+IIqH+T3UIAWGWR7F)-DkMmts|&|-AjKn zx7JI2NVD_iI;qZCP@V@JtRNJzIzz*a37O-^2coOij`!fKqz-odKS_;6cbB<>M>K8I zNBGU;2vfdMQ~k~w)K^(rY73GBk4Azm+Flk$7NXF38O7`+QglhHU{tdO|0Uh<)A$}? zSW2jNR-5Ym{Nu^3VOM$#M&!pvGCNDi6meOFxp9w+@Gf#&Ft@j@O?3E8awX2DuyWud;!QY3IZPmdVPg z72vU&bTwetC&9+`{JwIuBt5w3YMi?M{bEm_b5zbMgBvd2 zJJZBavqDcOtZ7h@Rtpo-it5z2(&eFje{et_ZM~}Rq*r7xUHfvnLyhKj+kRRvkNnxJ zvDCi!(@6LU|DYJ{S|dHyck0Ej4*T8+{5stHOr{rkmHSfJ=d}Zn)6tMvoVE~_V&yzZ z*kknY`*G|yBO%nsu=UNum7CbIb7{JxhG^&P@G+~h9%wl-(kwHQJa(%6Xe$FHW~&y6jrQ=+5#m1`sBg&#Vi`Z4UKH5Z3T!hcY4e~Jh$=Lio>bzFOPoRV^aSdEd| z@d0hxT(JV2OaTmIJXg6!9CYbb++{2u^m?Za`RDh4aA{m^kCzxQV=XVC^%k#-zEGZrxU zXG0TB8tq9wGV6hb9q-F(umJzF-4tQv>}yijnQ?rPMjs;+$ITs5cD8r_ z;CNxNNymOseN>&^tI2k|IrfL#geZnN02HrvM}ffc_5~mlkU)wE+dggM{GwQ2#pW#( zN`1OWtaHUSP>o{9ORD_!zuL=O0GnLXl(Z8Prvy>^gWe_!mB)k<|9U~3FLjW)rcJfW zf2RQtN2DAPq~I3kWR~K)Qm5zg{nBrL^~;C64*`w_Owu#sULXGeN*}4^Tt4cgoj3BE|5_& zzWG8<7gMz{BGdedm(>MEYtL7PWsFuN*m}6M#mDL#aT>i@WR%Y?t*3ZMV{&KAD&Z;ucj)KwJtyY!MchU?h zrr2aQ-I=9axeL>zTSj2G2TKy*mIzc^PRS#kh!AUNOQ-tK@XM*z{;t`&ewe{g#9RK6 z%D0TxE)tKi?_#O*NBQjXE)nMmxGnF!}YW`X?r#y(=cC*jI%fGs zI9d>6h67#=6Nk?&Rdl>Zs?d$3#=NW+sFB8pxo+*H`5?X|^k@klmg(SC1I z*BmD_vxfeyJY2|WTIS}4R*lfyND*CjsptA>u)QuW?oSIMtGkNxj<}|o)mU19){L`| zrdS{eET9EVGGfigO@!8iOlwIZ4b5Ybj4xF!|J2{VI_zN5cwXgzC3F$cnwAC>#U$$u z+;DIXmKjBC>u%52l9m*VOOZ&?7ZK4{g)j1=n1uKO<@idgkooHE(7XXZ8B=I}6mXktFP_ZbUNX@0At#<0RHYS8Q@@YAWi@LtFkrQ$HHy)ix?8R)^}tM$ zAYS!k+Tcnm870Zzlz<%3=R1#vwB>YkMet|pVgpyhunl39_NW1=k1!Z)cHot^nzvC& zKRaR|zbr<;q1Hz)Q9V4G6H9ybzxk5x+Lxp4Y>Q;eyQxzw2bQzfGU3t`B9(;wJ_NsQ z_AW9YC$1vbcc=Lnf_-eaU4!hM3|_|onkTzJS}oRQYmG>|t#)J$8eo=5mF&J~_JK9l zRTV>Al#2>2jM2)2J#y3KN`CqaHtzknj?{6|wnj8em{2(u;E%`X-iOsL*2-pncsd@P zV#w3IIm_ETGUU47p+Q& z{-{#?^$+$$SJrHO(R(P{-Gd!d1`Suvbyt~#^_%QJWZK^7%_CYmI{Zi{51Pn6(OA*B zk#N@+h@NGrQCIh+5k_2kp2^4HYhxf<82kCwABR*nG=tUg9zBB>y3)uI4#_{hjPAV0D zV2IxTVSS~@iUa^N%T(D4K$NN3i4il3jf*hBAX4tQ&hN@?07f%hR^AVXNal=`I;jf{ zy=z;?@SIYHG-qAilu<4tV6ojeHFvMTz^yFxUw;f44JUk7d-eM|27s}dtdHEK7l(Xi z6o7eF0(7B_*prJxuz$T6tXcs3qzjgE+#e{ozdM*KyJ-OGme-+J1DLDC!hd~^pAGc` z8h(4l?(7g<%lOMCcx{jE*gMzzSJ(Qx#q_`NEdTEBjrglI=bNtmmmc`@6aTvXzV?+K zvUBdsgR|J;D;_d-%v{!AboX;xKy>=ovF`ixIRD)`?%(>o`hn(`M=i>T=HsV6{^d}9 zp=|mwM?L>!MOhd7j8@#$KR4{p!)jEH0GHD{Lp&F5x-V$o7USX3ol8yK(NTTf`vD zzkeU--6di7PbhRd%pfXU5hs=0&=6y>)MapG*|vY-r^JKBuab^M*ZIH!yIAhNV@vODQBlw zwUpAzEZk|_5bTi6<^aZ*Sgl|#CoZZoRjC&dd$~W;A)5H%bMC3bR5xB$FivuJidrd( z#2d1Q=3u`W@lTZDhB?)Y1UaU{Kg`0A9BIwYP!#9ZuduCYT$K9iFIOHPnN#+tX1!fIacfXj_=X`ofIO*m$}R+8KEq2c zDG>x7qb+4aJ5EK4OOspIi>OKImj{c6tdhz7<)3BWegVACIMcNP?PeX0aX?uOzQ%Lr z{_pE#xm)pE%Ju9HP*B}n;)w~bgdo9gC?W`MH&Iz-A>zq4P zI1l;E-(r;g`844YhKrH$a*yT2LZL^^1dU5jm$xwCMMmm@6#%os-=}L08%#c(X?Mpb znI1eJX_z00_Nf`7JWOK=N(9;A=E4F#3!!sQhKOk4Vd9jUz$(jL$gWJbIR-eY*6jrY z_Ah45GWV?BrMLP#_1~Yber=2q`JuY3Pj5J*F=wy$bac8ubw?=?JynF)D>&>7uqvT` zh(ohkQ}O44dc(*=K*l;DtpR-Nc-YQc)x%c0Mf2hLC;LJg5#S<=T#P67EoSsX>4f^!kSMHhz-jb1=WZ#qcH;sJMJ}{~)@H3AH8IVyI*mAp5*DFFQ zWS^B&0dU13fZX9mmLHy_)TyNp=?lCv`Q)$h=kQi-6=bCUkyop**V}B` z<-E%_R`?g)bM(i4NJHG*VWU-ePE1%#o9LT)aH;VGl;0>t9)s?v+Ia2*On*#$2rf#1(wE+Wc zn%$*5ZfJOX*EoB6RN_kM-=7`en|sZpkh&pL5l~Wnk?m9o9zj)JmA>?=zorTR7~X`9 zucG1<@i_eef+oWo%xis-c2{RbL_!P2#igc`VpKb3;y|}Wia!|hk%AcJ>L2QNcIBv? z)DGD}T}QHr`iK<5*@z4vayi>jd;BC81~YldFcMyTlvpqtK((ZMr=apIk@i z2bOAjSA{mW80x;Z7Q;!l8$pg{A(z|cP8fWQM^w-~jJ&tVbv@_s!bPDdzd zRU>)K`nJ|j4GL~kHJk!SLHNK1=Nx!Ud1)6T;-f)SKA`rBPGThRhHrkE9VY0fP2V^! zJ8?=Jb^;JlXTNH)-Fs>M|Hprud+*^tf}+O`MYc$p>ke~Q+rSS}< z4_Z3LEml3Gp5^!G|@W2b!H4LCG{>2esq?NN@1um#6~s49=R zVxhTba=nJNCJ!cvsR_@?{k~6~tB}wbc2^vb^1XM!FC*WvFeC{&EJuU#9=z(0^4{<3 zKB*ux8A3-$EL}^42j}?@-JXN`0z*@4m>8R*WETt?9zmLoOMYB>+?wI-gS0?qWT65P zT`U*$In^2i*u+cZwo0s<|BdR1Y8a^Zbbw?h-3YIWFp68m&^U*`&x!9nLH%(D$YIWn zDiK41Ll6-ql}EbjS~U;~wVY?DvJD;=?>M#tm^ zH&7-jwy_i~CJTF;sqQlmO&zQ?f2cYkPVTNdcc}%K9|j-FzU3@O1u`_S{;2Ds4Ps9P z39D=Nym&5OMfjtax@dEj4_BZ$mm^Lq8nfyz9-~OYwX`Fcr;;t?ebDx|jFa+42|uxd zG}6vEG}|YYG~xCMv*P4hg}uU#E5lYX<+CK;+v1Aum;Q-1xIf7(hMEztNQpILZM6g$XU~MBPqS{evhBY1NbuqRE z83nAP4qupcUR{)z=LO8P8fx01L<%cCMH!MuRw&^XSCRDuZKxvjw5dwq*4u8T zd*g3~dJsNm(_J%M7RySu{ZZS?c0P!5AqyANLQ9dJwkU7!?PH!jCzwy#V}nmyXIxW5 zKBg^9K$cwIA~C5s~>MA zg`r+L(@05OvM{dgSHI#OMmV*uVIIp3Ud+2&uh0vNP#RTUAK0&D-1>NDw>{Tjt>xuk z+N=vF3T#o+T4eOJkYz~^vEzmzD(i^PPh>-Fw*QSnQk=8kf5Iuw{?T z;=D3G5B&fXjlcWn-yIxG@hfN}hd1*xc6S+j>?{yBzluEbyqU5%duKSTC)7*=(Yv9o zJ1t{FX_dV|k5XT?8NU}Bswl_%Fusg1Z_k3L0=D0$TU zaGJ;6z%d%hWXs^15kC=xC+hfSC!Kh!GraAPFXuJZHm!)Wiku=b+6&d?j(d#`5G>z> zZ@1Ke9}3T44(4kqYOvdRmHYjpHs!6za~_jYr6$d;-y}fh zWCnJN!^% zX>w;n-fx9>NRT9$FoEz0roy1oY=erVzl?)&zIlmVxr1NMAjcbDdz3}oRKGMaCc6nA zrAG0Onl1tVm*mzbEwVuDS7f?KL3sVkf(gQXsh6ies}@t{675&qr^es1kUV|ZjnxZ^ zJ(n8HD04@)48v-GX89𝔄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

    }m8 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 ZxBnC7slUGS=dx0N+2()NHjqyP{|l-0*iirg literal 0 HcmV?d00001 diff --git a/assets/zh-CN/headspace.png b/assets/zh-CN/headspace.png new file mode 100644 index 0000000000000000000000000000000000000000..25225b18ca84209eb6bfe9f7d2404cdb487201e9 GIT binary patch literal 474856 zcmeGEcT^K=_XZ4OKZ3|nY0~tFf`BxU-YgWQ_a0i579b*!0HIk>Y2hG<2m;c32_+Fi zKoN*i1tbZC1Oe$yYUsQZea?C0S>Nyb@BQap%v$0-$xPPV*X(=mYhQcMgd6B-GBa{9 zGB7YO-?^=3#K6FfVqiG2^~X`*lLv|JQNVu(yp1$(GL&@iOaeQHop0#gU|>MSFl{*; z0rrpIzisKwz;LpW{_ns`k(1mE47=HP)NYvg+0IZT4=|iR@m}J6{rHE6kplYnd^#(D zbN;XGwp{)H^%d~>;Y8Ab|NHAF```Ws&OREyfx)nk#y%RqBk=op-!}Hq_#J`Y$NRRi zkH+r^{65~djeRtJN8tDIzHRKI@jC*)kN0h3AC2D;_&qZS14*I|9Ft_ibYz zjo%UYeY|fQ`)K@*!0+S#Z`sBxoZ?tGzw$S^t>HNSyHiV5=q8!E0gh1p_m;5(jWr$l zZzEl8E-DBK4OBa5Zyvh%qUW7Flual)HY0w_;6c=!*l3?_)*p9M*y7GA*;_oVV1aS8 z?fPe(T4GFi9RwdsnGL36clZ?Q(C--T9shB`@z~x^e&733t?}gV`+LXdpFii_`^oQn z*Z(}F`1}6eap?IAu)Uw`*}I`OvuA(r&ZFo5|C)UEC%gx({X4t|j(wBf1IPXv-UG+J z)7k^azL(wu2g80?+Jnb_9Nq)RKJnNC$3F4c1IIq`*aOEt@z?{$KJnNC$3F4c1IIq` z*bB!#@z{gMKJnNC$3F4c1IIq`*aOEt@z?{$KJnNC$3F4c1IIq`*bB!#@z{gMKJnNC z$3F4c1IIq`*aOEt@z?{$KJnNC$3F4c1IPcH#N%oe2txb+{g#k#kC&&`BNs

  • }v| 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

    >v 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

    TpmF)(WlcY)bxmfx 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 ### :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")

  • +4QB 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d7eb34fe56e75720f936a15037f5311722ca03ad GIT binary patch literal 78756 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%0DUiqibSwBgF=XiIPPqo2f20c7Mj0vC;yrLoMtoQ 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

    @@ -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 )
    @@ -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)")
    From dbab226a9b0a62f19d1800ecf0a6f4a2ebfe86df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=9B=AA=E6=B6=9B?= <99980283> Date: Thu, 14 Apr 2022 17:23:20 +0800 Subject: [PATCH 443/502] doc: readme-zh-CN.md part 4.2 no image --- readme-zh-CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-zh-CN.md b/readme-zh-CN.md index 37f8dd7d..18b3ee5f 100644 --- a/readme-zh-CN.md +++ b/readme-zh-CN.md @@ -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")
    From a88a8023e64d8ff5a2442a72a4ee24c1a047b451 Mon Sep 17 00:00:00 2001 From: Drew Wilson Date: Thu, 14 Apr 2022 12:39:35 -0400 Subject: [PATCH 444/502] Adds section number `1.9` to heading for Copy code section so it matches the pattern of the other section headings --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index f5123694..66bce7ea 100644 --- a/readme.md +++ b/readme.md @@ -536,7 +536,7 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {

    -## ⚪ ️Copy code, but only what's neccessary +## ⚪ ️ 1.9 Copy code, but only what's neccessary :white_check_mark: **Do:** Include all the necessary details that affect the test result, but nothing more. As an example, consider a test that should factor 100 lines of input JSON - Pasting this in every test is tedious. Extracting it outside to transferFactory.getJSON() will leave the test vague - Without data, it's hard to correlate the test result with the cause ("why is it supposed to return 400 status?"). The classic book x-unit patterns named this pattern 'the mystery guest' - Something unseen affected our test results, we don't know what exactly. We can do better by extracting repeatable long parts outside AND mention explictly which specific details matter to the test. Going with the example above, the test can pass parameters that highlight what is important: transferFactory.getJSON({sender: undefined}). In this example, the reader should immediately infer that the empty sender field is the reason why the test should expect a validation error or any other similar adequate outcome.
    From 77c1eb687e47db37d266563d706f7220cf3d128b Mon Sep 17 00:00:00 2001 From: ramon Date: Thu, 21 Apr 2022 20:50:55 +0200 Subject: [PATCH 445/502] Fix typo in the Spanish translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces "grabes" by the adjective "graves" which means "serious", in this context "but some of the worst things that happen" Translation: "pero los errores más grabes". The word "grabes" does not make sense in this context, as it is the 2nd present indicative conjugation of verb "to record". --- readme-es.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-es.md b/readme-es.md index 3325f51b..c80b14b5 100644 --- a/readme-es.md +++ b/readme-es.md @@ -893,7 +893,7 @@ Crédito: {

    -## ⚪ ️Copy code, but only what's neccessary +## ⚪ ️ 1.9 Copy code, but only what's neccessary :white_check_mark: **Do:** Include all the necessary details that affect the test result, but nothing more. As an example, consider a test that should factor 100 lines of input JSON - Pasting this in every test is tedious. Extracting it outside to transferFactory.getJSON() will leave the test vague - Without data, it's hard to correlate the test result with the cause ("why is it supposed to return 400 status?"). The classic book x-unit patterns named this pattern 'the mystery guest' - Something unseen affected our test results, we don't know what exactly. We can do better by extracting repeatable long parts outside AND mention explictly which specific details matter to the test. Going with the example above, the test can pass parameters that highlight what is important: transferFactory.getJSON({sender: undefined}). In this example, the reader should immediately infer that the empty sender field is the reason why the test should expect a validation error or any other similar adequate outcome.
    @@ -1023,9 +1023,10 @@ beforeEach(() => { id: 1, name: 'John', }); -});``` - +}); +```
    +
    ## ⚪ ️2.10 Test the response schema, mostly when there are auto-generated fields @@ -1062,7 +1063,7 @@ beforeEach(() => {
    -## ⚪ ️2.12 Check integrations corner cases and chaos +## ⚪ ️2.11 Check integrations corner cases and chaos :white_check_mark: **Do:** When checking integrations, go beyond the happy and sad paths. Check not only errored responses (e.g., HTTP 500 error) but also network-level anomalies like slow and timed-out responses. This will prove that the code is resilient and can handle various network scenarios like taking the right path after a timeout, has no fragile race conditions, and contains a circuit breaker for retries. Reputable interceptor tools can easily simulate various network behaviors like hectic service that occasionally fail. It can even realize when the default HTTP client timeout value is longer than the simulated response time and throw a timeout exception right away without waiting @@ -1108,7 +1109,7 @@ beforeEach(() => {
    -## ⚪ ️2.13 Test the five potential outcomes +## ⚪ ️2.12 Test the five potential outcomes :white_check_mark: **Do:** When planning your tests, consider covering the five typical flow's outputs. When your test is triggering some action (e.g., API call), a reaction is happening, something meaningful occurs and calls for testing. Note that we don't care about how things work. Our focus is on outcomes, things that are noticeable from the outside and might affect the user. These outcomes/reactions can be put in 5 categories: From 3f224c1439aec11d0b3815d7b7d70b95d5399af1 Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 2 May 2022 01:38:43 +0200 Subject: [PATCH 447/502] Update readme-pl.md Initial changes & updates preparation --- readme-pl.md | 301 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 242 insertions(+), 59 deletions(-) diff --git a/readme-pl.md b/readme-pl.md index 499f5e9d..42d31596 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
    -## 📗 46+ 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,11 @@ 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) - Chcesz przetłumaczyć na swój język? Proszę skorzystaj z issue 💜

    @@ -41,7 +48,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 +56,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 +64,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 +79,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. @@ -518,12 +525,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 +538,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 poprawny: 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 @@ -951,6 +955,164 @@ it("When updating site name, get successful confirmation", async () => {
    +
    + +## ⚪ ️2.8 Choose a clear data clean-up strategy: After-all (recommended) or after-each + +:white_check_mark: **Do:** The timing when the tests clean the database determines the way the tests are being written. The two most viable options are cleaning after all the tests vs cleaning after every single test. Choosing the latter option, cleaning after every single test guarantees clean tables and builds convenient testing perks for the developer. No other records exist when the test starts, one can have certainty which data is being queried and even might be tempted to count rows during assertions. This comes with severe downsides: When running in a multi-process mode, tests are likely to interfere with each other. While process-1 purges tables, at the very moment process-2 queries for data and fail (because the DB was suddenly deleted by process-1). On top of this, It's harder to troubleshoot failing tests - Visiting the DB will show no records. + +The second option is to clean up after all the test files have finished (or even daily!). This approach means that the same DB with existing records serves all the tests and processes. To avoid stepping on each other's toes, the tests must add and act on specific records that they have added. Need to check that some record was added? Assume that there are other thousands of records and query for records that were added explicitly. Need to check that a record was deleted? Can't assume an empty table, check that this specific record is not there. This technique brings few powerful gains: It works natively in multi-process mode, when a developer wishes to understand what happened - the data is there and not deleted. It also increases the chance of finding bugs because the DB is full of records and not artificially empty. [See the full comparison table here](https://github.com/testjavascript/nodejs-integration-tests-best-practices/blob/master/graphics/db-clean-options.png). +
    + +❌ **Otherwise:** Without a strategy to separate records or clean - Tests will step on each other toes; Using transactions will work only for relational DB and likely to get complicated once there are inner transactions + +
    + +
    Code Examples + +
    + +### :clap: Cleaning after ALL the tests. Not neccesserily after every run. The more data we have while the tests are running - The more it resembles the production perks + +```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 Isolate the component from the world using HTTP interceptor + +:white_check_mark: **Do:** Isolate the component under test by intercepting any outgoing HTTP request and providing the desired response so the collaborator HTTP API won't get hit. Nock is a great tool for this mission as it provides a convenient syntax for defining external services behavior. Isolation is a must to prevent noise and slow performance but mostly to simulate various scenarios and responses - A good flight simulator is not about painting clear blue sky rather bringing safe storms and chaos. This is reinforced in a Microservice architecture where the focus should always be on a single component without involving the rest of the world. Though it's possible to simulate external service behavior using test doubles (mocking), it's preferable not to touch the deployed code and act on the network level to keep the tests pure black-box. The downside of isolation is not detecting when the collaborator component changes and not realizing misunderstandings between the two services - Make sure to compensate for this using a few contract or E2E tests +
    + +❌ **Otherwise:** Some services provide a fake version that can be deployed by the caller locally, usually using Docker - This will ease the setup and boost the performance but won't help with simulating various responses; Some services provide 'sandbox' environment, so the real service is hit but no costs or side effects are triggered - This will cut down the noise of setting up the 3rd party service but also won't allow simulating scenarios + +
    + +
    Code Examples + +
    + +### :clap: Preventing network calls to externous components allows simulating scnearios and minimizing the noise + +```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 Test the response schema, mostly when there are auto-generated fields + +:white_check_mark: **Do:** When it is impossible to assert for specific data, check for mandatory field existence and types. Sometimes, the response contains important fields with dynamic data that can't be predicted when writing the test, like dates and incrementing numbers. If the API contract promises that these fields won't be null and hold the right types, it's imperative to test it. Most assertion libraries support checking types. If the response is small, check the return data and type together within the same assertion (see code example). One more option is to verify the entire response against an OpenAPI doc (Swagger). Most test runners have community extensions that validate API responses against their documentation. + + +
    + +❌ **Otherwise:** Although the code/API caller relies on some field with dynamic data (e.g., ID, date), it will not come in return and break the contract + +
    + +
    Code Examples + +
    + +### :clap: Asserting that fields with dynamic value exist and have the right type + +```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 Check integrations corner cases and chaos + +:white_check_mark: **Do:** When checking integrations, go beyond the happy and sad paths. Check not only errored responses (e.g., HTTP 500 error) but also network-level anomalies like slow and timed-out responses. This will prove that the code is resilient and can handle various network scenarios like taking the right path after a timeout, has no fragile race conditions, and contains a circuit breaker for retries. Reputable interceptor tools can easily simulate various network behaviors like hectic service that occasionally fail. It can even realize when the default HTTP client timeout value is longer than the simulated response time and throw a timeout exception right away without waiting + + +
    + +❌ **Otherwise:** All your tests pass, it's only the production who will crash or won't report errors correctly when 3rd parties send excpetional responses + +
    + +
    Code Examples + +
    + +### :clap: Ensuring that on network failures, the circuit breaker can save the day + +```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 Test the five potential outcomes + +:white_check_mark: **Do:** When planning your tests, consider covering the five typical flow's outputs. When your test is triggering some action (e.g., API call), a reaction is happening, something meaningful occurs and calls for testing. Note that we don't care about how things work. Our focus is on outcomes, things that are noticeable from the outside and might affect the user. These outcomes/reactions can be put in 5 categories: + +• Response - The test invokes an action (e.g., via API) and gets a response. It's now concerned with checking the response data correctness, schema, and HTTP status + +• A new state - After invoking an action, some **publicly accessible** data is probably modified + +• External calls - After invoking an action, the app might call an external component via HTTP or any other transport. For example, a call to send SMS, email or charge a credit card + +• Message queues - The outcome of a flow might be a message in a queue + +• Observability - Some things must be monitored, like errors or remarkable business events. When a transaction fails, not only we expect the right response but also correct error handling and proper logging/metrics. This information goes directly to a very important user - The ops user (i.e., production SRE/admin) + +
    +

    # Sekcja 3️⃣: Frontend Testing @@ -1918,38 +2080,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

    🌍 💻
    - + From 87514dbe622f1abf227d57f329e833fae97cdb70 Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 2 May 2022 01:39:43 +0200 Subject: [PATCH 448/502] Update readme-pl.md Quick fix for Spanish --- readme-pl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-pl.md b/readme-pl.md index 42d31596..74f20436 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -35,7 +35,7 @@ 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) +- 🇪🇸[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) From 592224962b1a2c4b07b894f2021fa5fb2aee11c1 Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 2 May 2022 15:39:07 +0200 Subject: [PATCH 449/502] Update readme-pl.md Update for new added recently subpoints --- readme-pl.md | 56 ++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/readme-pl.md b/readme-pl.md index 74f20436..b53df5ef 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -957,22 +957,22 @@ it("When updating site name, get successful confirmation", async () => {
    -## ⚪ ️2.8 Choose a clear data clean-up strategy: After-all (recommended) or after-each +## ⚪ ️2.8 Wybierz przejrzystą strategię czyszczenia danych: po wszystkim (zalecane) lub po każdym -:white_check_mark: **Do:** The timing when the tests clean the database determines the way the tests are being written. The two most viable options are cleaning after all the tests vs cleaning after every single test. Choosing the latter option, cleaning after every single test guarantees clean tables and builds convenient testing perks for the developer. No other records exist when the test starts, one can have certainty which data is being queried and even might be tempted to count rows during assertions. This comes with severe downsides: When running in a multi-process mode, tests are likely to interfere with each other. While process-1 purges tables, at the very moment process-2 queries for data and fail (because the DB was suddenly deleted by process-1). On top of this, It's harder to troubleshoot failing tests - Visiting the DB will show no records. +: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. -The second option is to clean up after all the test files have finished (or even daily!). This approach means that the same DB with existing records serves all the tests and processes. To avoid stepping on each other's toes, the tests must add and act on specific records that they have added. Need to check that some record was added? Assume that there are other thousands of records and query for records that were added explicitly. Need to check that a record was deleted? Can't assume an empty table, check that this specific record is not there. This technique brings few powerful gains: It works natively in multi-process mode, when a developer wishes to understand what happened - the data is there and not deleted. It also increases the chance of finding bugs because the DB is full of records and not artificially empty. [See the full comparison table here](https://github.com/testjavascript/nodejs-integration-tests-best-practices/blob/master/graphics/db-clean-options.png). +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).
    -❌ **Otherwise:** Without a strategy to separate records or clean - Tests will step on each other toes; Using transactions will work only for relational DB and likely to get complicated once there are inner transactions +❌ **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
    -
    Code Examples +
    Przykłady kodu
    -### :clap: Cleaning after ALL the tests. Not neccesserily after every run. The more data we have while the tests are running - The more it resembles the production perks +### :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) @@ -989,20 +989,20 @@ module.exports = async () => {
    -## ⚪ ️2.9 Isolate the component from the world using HTTP interceptor +## ⚪ ️2.9 Odizoluj komponent od świata za pomocą interceptora HTTP -:white_check_mark: **Do:** Isolate the component under test by intercepting any outgoing HTTP request and providing the desired response so the collaborator HTTP API won't get hit. Nock is a great tool for this mission as it provides a convenient syntax for defining external services behavior. Isolation is a must to prevent noise and slow performance but mostly to simulate various scenarios and responses - A good flight simulator is not about painting clear blue sky rather bringing safe storms and chaos. This is reinforced in a Microservice architecture where the focus should always be on a single component without involving the rest of the world. Though it's possible to simulate external service behavior using test doubles (mocking), it's preferable not to touch the deployed code and act on the network level to keep the tests pure black-box. The downside of isolation is not detecting when the collaborator component changes and not realizing misunderstandings between the two services - Make sure to compensate for this using a few contract or E2E tests +: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
    -❌ **Otherwise:** Some services provide a fake version that can be deployed by the caller locally, usually using Docker - This will ease the setup and boost the performance but won't help with simulating various responses; Some services provide 'sandbox' environment, so the real service is hit but no costs or side effects are triggered - This will cut down the noise of setting up the 3rd party service but also won't allow simulating scenarios +❌ **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
    -
    Code Examples +
    Przykłady kodu
    -### :clap: Preventing network calls to externous components allows simulating scnearios and minimizing the noise +### :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 @@ -1016,22 +1016,22 @@ beforeEach(() => {

    -## ⚪ ️2.10 Test the response schema, mostly when there are auto-generated fields +## ⚪ ️2.10 Przetestuj schemat odpowiedzi, głównie w przypadku pól generowanych automatycznie -:white_check_mark: **Do:** When it is impossible to assert for specific data, check for mandatory field existence and types. Sometimes, the response contains important fields with dynamic data that can't be predicted when writing the test, like dates and incrementing numbers. If the API contract promises that these fields won't be null and hold the right types, it's imperative to test it. Most assertion libraries support checking types. If the response is small, check the return data and type together within the same assertion (see code example). One more option is to verify the entire response against an OpenAPI doc (Swagger). Most test runners have community extensions that validate API responses against their documentation. +: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ę.
    -❌ **Otherwise:** Although the code/API caller relies on some field with dynamic data (e.g., ID, date), it will not come in return and break the contract +❌ **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
    -
    Code Examples +
    Przykłady kodu
    -### :clap: Asserting that fields with dynamic value exist and have the right type +### :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 () => { @@ -1051,22 +1051,22 @@ beforeEach(() => {
    -## ⚪ ️2.11 Check integrations corner cases and chaos +## ⚪ ️2.11 Sprawdź corner cases integracji i chaos -:white_check_mark: **Do:** When checking integrations, go beyond the happy and sad paths. Check not only errored responses (e.g., HTTP 500 error) but also network-level anomalies like slow and timed-out responses. This will prove that the code is resilient and can handle various network scenarios like taking the right path after a timeout, has no fragile race conditions, and contains a circuit breaker for retries. Reputable interceptor tools can easily simulate various network behaviors like hectic service that occasionally fail. It can even realize when the default HTTP client timeout value is longer than the simulated response time and throw a timeout exception right away without waiting +: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
    -❌ **Otherwise:** All your tests pass, it's only the production who will crash or won't report errors correctly when 3rd parties send excpetional responses +❌ **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
    -
    Code Examples +
    Przykłady kodu
    -### :clap: Ensuring that on network failures, the circuit breaker can save the day +### :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 () => { @@ -1097,19 +1097,19 @@ beforeEach(() => {
    -## ⚪ ️2.12 Test the five potential outcomes +## ⚪ ️2.12 Przetestuj pięć potencjalnych wyników -:white_check_mark: **Do:** When planning your tests, consider covering the five typical flow's outputs. When your test is triggering some action (e.g., API call), a reaction is happening, something meaningful occurs and calls for testing. Note that we don't care about how things work. Our focus is on outcomes, things that are noticeable from the outside and might affect the user. These outcomes/reactions can be put in 5 categories: +: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: -• Response - The test invokes an action (e.g., via API) and gets a response. It's now concerned with checking the response data correctness, schema, and HTTP status +• Odpowiedź — test wywołuje akcję (np. przez API) i otrzymuje odpowiedź. Teraz zajmuje się sprawdzaniem poprawności danych odpowiedzi, schematu i statusu HTTP -• A new state - After invoking an action, some **publicly accessible** data is probably modified +• Nowy stan — po wywołaniu akcji niektóre **publicznie dostępne** dane są prawdopodobnie modyfikowane -• External calls - After invoking an action, the app might call an external component via HTTP or any other transport. For example, a call to send SMS, email or charge a credit card +• 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 -• Message queues - The outcome of a flow might be a message in a queue +• Kolejki wiadomości — wynikiem przepływu może być wiadomość w kolejce -• Observability - Some things must be monitored, like errors or remarkable business events. When a transaction fails, not only we expect the right response but also correct error handling and proper logging/metrics. This information goes directly to a very important user - The ops user (i.e., production SRE/admin) +• 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)
    From b94e189101474c5be7696d483b096b2d4520f264 Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 2 May 2022 16:05:35 +0200 Subject: [PATCH 450/502] Update readme-pl.md Update with new items that have recently appeared --- readme-pl.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/readme-pl.md b/readme-pl.md index b53df5ef..e929ce62 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -147,6 +147,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 @@ -558,7 +563,7 @@ test("When no credit, then the transfer is declined", async() => {
    -### :clap: Przykład poprawny: Test wskazuje, co jest przyczyną wyniku testu +### :clap: Przykład robienia tego prawidłowo: Test wskazuje, co jest przyczyną wyniku testu ```javascript @@ -771,6 +776,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 From 358514e1be64ad966638be1e5708c569b7fc48d8 Mon Sep 17 00:00:00 2001 From: saseungmin Date: Mon, 9 May 2022 23:09:05 +0900 Subject: [PATCH 451/502] [Fix] Typo in README.kr.md - Remove unknown words - Fix typo in code block - Correct the wrong korean --- readme.kr.md | 334 +++++++++++++++++++++++---------------------------- 1 file changed, 153 insertions(+), 181 deletions(-) diff --git a/readme.kr.md b/readme.kr.md index a4dd1383..f2f90843 100644 --- a/readme.kr.md +++ b/readme.kr.md @@ -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(`
    @@ -1161,40 +1162,36 @@ cy.wait('@products')// wait for route to appear ```javascript // @testing-library/dom -test('movie title appears', async () => { - // 요소는 초기에 존재 하지 않음... - - // 출현을 대기 - await wait(() => { - expect(getByText('the lion king')).toBeInTheDocument() - }) +test("영화 제목이 나타난다", async () => { + // 요소는 초기에 존재 하지 않음... - // 출현을 기다린 후 요소를 리턴 - const movie = await waitForElement(() => getByText('the lion king')) -}) + // 출현을 대기 + await wait(() => { + expect(getByText("the lion king")).toBeInTheDocument(); + }); + // 출현을 기다린 후 요소를 리턴 + const movie = await waitForElement(() => getByText("the lion king")); +}); ``` ### :thumbsdown: 잘못된 예: 사용자 정의 슬립 코드 ```javascript +test("영화 제목이 나타난다", async () => { + // 초기에 요소가 존재 하지 않음... + + // 사용자 정의 대기 로직 (주의: 매우 단순, 타임아웃이 아님) + const interval = setInterval(() => { + const found = getByText("the lion king"); + if (found) { + clearInterval(interval); + expect(getByText("the lion king")).toBeInTheDocument(); + } + }, 100); -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')) -}) - + // 출현을 기다린 후 요소를 리턴 + const movie = await waitForElement(() => getByText("the lion king")); +}); ```
    @@ -1247,38 +1244,36 @@ test('movie title appears', async () => { "Examples with React") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with react-testing-library") -```javascript - +```jsx // unit under test -export default function ProductsList() { - const [products, setProducts] = useState(false) +export default function ProductsList() { + const [products, setProducts] = useState(false); - const fetchProducts = async() => { - const products = await axios.get('api/products') - setProducts(products); - } + const fetchProducts = async () => { + const products = await axios.get("api/products"); + setProducts(products); + }; - useEffect(() => { - fetchProducts(); - }, []); + useEffect(() => { + fetchProducts(); + }, []); - return products ?
    {products}
    :
    No products
    + return products ?
    {products}
    :
    No products
    ; } // test -test('products가 없는 경우, 적절한 메시지 표시하기', () => { - // Arrange - nock("api") - .get(`/products`) - .reply(404); +test("products가 없는 경우, 적절한 메시지 표시한다", () => { + // Arrange + nock("api") + .get(`/products`) + .reply(404); - // Act - const {getByTestId} = render(); + // Act + const { getByTestId } = render(); - // Assert - expect(getByTestId('no-products-message')).toBeTruthy(); + // Assert + expect(getByTestId("no-products-message")).toBeTruthy(); }); - ```
    @@ -1360,7 +1355,7 @@ beforeEach(setUser => () { ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") ```javascript -it('모든 페이지를 smoke 테스트 할때, 페이지들이 정상적으로 로드 되어야 한다', () => { +it('모든 페이지를 smoke 테스트 할 때, 페이지들이 정상적으로 로드되어야 한다', () => { // Cypress를 이용한 예제 입니다 // 다른 E2E 도구로도 쉽게 구현이 가능합니다 cy.visit('https://mysite.com/home'); @@ -1480,44 +1475,21 @@ paths: "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'); - -}); - +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"); + }); }); ``` @@ -1603,7 +1575,7 @@ mutation 기반의 테스트는 단순한 '방문'이 아닌 실제로 테스트
    -❌ **그렇지 않으면:** 85%의 커버리지는 테스트에서 코드의 85%에서 버그를 감지한다는 의미입니다. +❌ **그렇지 않으면:** 85%의 커버리지는 테스트에서 코드의 85%에서 버그를 감지한다는 의미입니다.
    @@ -1611,22 +1583,22 @@ mutation 기반의 테스트는 단순한 '방문'이 아닌 실제로 테스트
    -### :thumbsdown: 올바르지 않은 예: 100% 커버리지, 0% 테스트 +### :thumbsdown: 올바르지 않은 예: 100% 커버리지, 0% 테스트 ![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Cypress to illustrate the idea") ```javascript function addNewOrder(newOrder) { - logger.log(`Adding new order ${newOrder}`); - DB.save(newOrder); - Mailer.sendMail(newOrder.assignee, `A new order was places ${newOrder}`); + logger.log(`Adding new order ${newOrder}`); + DB.save(newOrder); + Mailer.sendMail(newOrder.assignee, `A new order was places ${newOrder}`); - return {approved: true}; + return { approved: true }; } -it("Test addNewOrder, don't use such test names", () => { - addNewOrder({asignee: "John@mailer.com",price: 120}); +it("addNewOrder를 테스트하고, 이러한 테스트 이름을 사용하지 마십시오.", () => { + addNewOrder({ assignee: "John@mailer.com", price: 120 }); }); // 100% 커버리지가 나오지만 아무것도 확인하지 않습니다. ``` @@ -1718,7 +1690,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test ### :clap: 올바른 예: 코드 품질 검사를 수행하는 npm 스크립트는 요청 시 또는 개발자가 새 코드를 푸시하려고 할 때 모두 병렬로 실행됩니다. -```javascript +```json "scripts": { "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", "inspect:lint": "eslint .", From 93e0d5de863637bf354a72ffe4dbe1a4661fcd87 Mon Sep 17 00:00:00 2001 From: Wralith <75392169+wralith@users.noreply.github.com> Date: Sat, 14 May 2022 06:26:06 +0300 Subject: [PATCH 452/502] Fix missing chatper 2.10 Fix missing end of the line character in line 1026 that causing chapter 2.10 to show inside the code block. --- readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index f5123694..f5dcca64 100644 --- a/readme.md +++ b/readme.md @@ -1014,7 +1014,7 @@ module.exports = async () => {
    -### :clap: Preventing network calls to externous components allows simulating scnearios and minimizing the noise +### :clap: Preventing network calls to externous components allows simulating scenarios and minimizing the noise ```javascript // Intercept requests for 3rd party APIs and return a predefined response @@ -1023,7 +1023,8 @@ beforeEach(() => { id: 1, name: 'John', }); -});``` +}); +```
    From 77fa951c3ca7d0a9ae341a6db95e94ad4188e0b3 Mon Sep 17 00:00:00 2001 From: wenqingl <86453680+wenqingl@users.noreply.github.com> Date: Fri, 22 Jul 2022 23:09:38 -0700 Subject: [PATCH 453/502] Update readme.md --- readme.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/readme.md b/readme.md index f5123694..2aa9f24e 100644 --- a/readme.md +++ b/readme.md @@ -14,15 +14,15 @@ ## 📗 50+ best practices: Super-comprehensive and exhaustive -This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and curates for you dozens of the best blog posts, books and tools the market has to offer +This is a guide for JavaScript & Node.js reliability from A-Z. It summarizes and curates for you dozens of the best blog posts, books, and tools the market has to offer ## 🚢 Advanced: Goes 10,000 miles beyond the basics -Hop into a journey that travels way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average +Hop into a journey that travels way beyond the basics into advanced topics like testing in production, mutation testing, property-based testing, and many other strategic & professional tools. Should you read every word in this guide your testing skills are likely to go way above the average ## 🌐 Full-stack: front, backend, CI, anything -Start by understanding the ubiquitous testing practices that are the foundation for any application tier. Then, delve into your area of choice: frontend/UI, backend, CI or maybe all of them? +Start by understanding the ubiquitous testing practices that are the foundation for any application tier. Then, delve into your area of choice: frontend/UI, backend, CI, or maybe all of them?
    @@ -88,9 +88,9 @@ Testing code is not production-code - Design it to be short, dead-simple, flat, See, our minds are already occupied with our main job - the production code. There is no 'headspace' for additional complexity. Should we try to squeeze yet another sus-system into our poor brain it will slow the team down which works against the reason we do testing. Practically this is where many teams just abandon testing. -The tests are an opportunity for something else - a friendly assistant, co-pilot, that delivers great value for a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 which is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). +The tests are an opportunity for something else - a friendly assistant, co-pilot, that delivers great value for a small investment. Science tells us that we have two brain systems: system 1 is used for effortless activities like driving a car on an empty road and system 2 is meant for complex and conscious operations like solving a math equation. Design your test for system 1, when looking at test code it should _feel_ as easy as modifying an HTML document and not like solving 2X(17 × 24). -This can be achieved by selectively cherry-picking techniques, tools and test targets that are cost-effective and provide great ROI. Test only as much as needed, strive to keep it nimble, sometimes it's even worth dropping some tests and trade reliability for agility and simplicity. +This can be achieved by selectively cherry-picking techniques, tools, and test targets that are cost-effective and provide great ROI. Test only as much as needed, and strive to keep it nimble, sometimes it's even worth dropping some tests and trading reliability for agility and simplicity. ![alt text](/assets/headspace.png "We have no head room for additional complexity") @@ -164,7 +164,7 @@ describe('Products Service', function() { :white_check_mark: **Do:** Structure your tests with 3 well-separated sections Arrange, Act & Assert (AAA). Following this structure guarantees that the reader spends no brain-CPU on understanding the test plan: -1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects and any other preparation code +1st A - Arrange: All the setup code to bring the system to the scenario the test aims to simulate. This might include instantiating the unit under test constructor, adding DB records, mocking/stubbing on objects, and any other preparation code 2nd A - Act: Execute the unit under test. Usually 1 line of code @@ -172,7 +172,7 @@ describe('Products Service', function() {
    -❌ **Otherwise:** Not only do you spend hours understanding the main code, but what should have been the simplest part of the day (testing) stretches your brain +❌ **Otherwise:** Not only do you spend hours understanding the main code but what should have been the simplest part of the day (testing) stretches your brain
    @@ -323,7 +323,7 @@ it("White-box test: When the internal methods get 0 vat, it return 0 response", :white_check_mark: **Do:** Test doubles are a necessary evil because they are coupled to the application internals, yet some provide immense value (
    [Read here a reminder about test doubles: mocks vs stubs vs spies](https://martinfowler.com/articles/mocksArentStubs.html)). -Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If no, it’s a white-box testing smell. +Before using test doubles, ask a very simple question: Do I use it to test functionality that appears, or could appear, in the requirements document? If not, it’s a white-box testing smell. For example, if you want to test that your app behaves reasonably when the payment service is down, you might stub the payment service and trigger some ‘No Response’ return to ensure that the unit under test returns the right value. This checks our application behavior/response/outcome under certain scenarios. You might also use a spy to assert that an email was sent when that service is down — this is again a behavioral check which is likely to appear in a requirements doc (“Send an email if payment couldn’t be saved”). On the flip side, if you mock the Payment service and ensure that it was called with the right JavaScript types — then your test is focused on internal things that have nothing to do with the application functionality and are likely to change frequently
    @@ -380,7 +380,7 @@ it("When a valid product is about to be deleted, ensure an email is sent", async ## ⚪ ️1.6 Don’t “foo”, use realistic input data -:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Chance](https://github.com/chancejs/chancejs) or [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +:white_check_mark: **Do:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Chance](https://github.com/chancejs/chancejs) or [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit cards, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers' data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing).
    ❌ **Otherwise:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” @@ -434,7 +434,7 @@ it("Better: When adding new valid product, get successful confirmation", async ( ## ⚪ ️ 1.7 Test many input combinations using Property-based testing -:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet [‘Don’t foo’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +:white_check_mark: **Do:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet [‘Don’t foo’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained
    ❌ **Otherwise:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs @@ -473,12 +473,12 @@ describe("Product service", () => { :white_check_mark: **Do:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. -On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much +On the other hand, ‘classic snapshots’ tutorials and tools encourage storing big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test runs to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment, or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes
    -❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **Otherwise:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the original document to the current received one - a single space character was added to the markdown...
    @@ -538,7 +538,7 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => { ## ⚪ ️Copy code, but only what's neccessary -:white_check_mark: **Do:** Include all the necessary details that affect the test result, but nothing more. As an example, consider a test that should factor 100 lines of input JSON - Pasting this in every test is tedious. Extracting it outside to transferFactory.getJSON() will leave the test vague - Without data, it's hard to correlate the test result with the cause ("why is it supposed to return 400 status?"). The classic book x-unit patterns named this pattern 'the mystery guest' - Something unseen affected our test results, we don't know what exactly. We can do better by extracting repeatable long parts outside AND mention explictly which specific details matter to the test. Going with the example above, the test can pass parameters that highlight what is important: transferFactory.getJSON({sender: undefined}). In this example, the reader should immediately infer that the empty sender field is the reason why the test should expect a validation error or any other similar adequate outcome. +:white_check_mark: **Do:** Include all the necessary details that affect the test result, but nothing more. As an example, consider a test that should factor 100 lines of input JSON - Pasting this in every test is tedious. Extracting it outside to transferFactory.getJSON() will leave the test vague - Without data, it's hard to correlate the test result with the cause ("why is it supposed to return 400 status?"). The classic book x-unit patterns named this pattern 'the mystery guest' - Something unseen affected our test results, we don't know what exactly. We can do better by extracting repeatable long parts outside AND mentioning explicitly which specific details matter to the test. Going with the example above, the test can pass parameters that highlight what is important: transferFactory.getJSON({sender: undefined}). In this example, the reader should immediately infer that the empty sender field is the reason why the test should expect a validation error or any other similar adequate outcome.
    ❌ **Otherwise:** Copying 500 JSON lines in will leave your tests unmaintainable and unreadable. Moving everything outside will end with vague tests that are hard to understand @@ -675,11 +675,11 @@ describe("Order service", function() { ## ⚪ ️ 1.12 Categorize tests under at least 2 levels -:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see code examples and print screen below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **Do:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for an additional level of categorization like the scenario or custom categories (see code examples and the print screen below). Doing so will also greatly improve the test reports: The reader will easily infer the test categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for the test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway)
    -❌ **Otherwise:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in a hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause +❌ **Otherwise:** When looking at a report with a flat and long list of tests, the reader has to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the text of the failing to see how they relate to each other. However, in a hierarchical report, all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause
    @@ -731,9 +731,9 @@ test("Then there should not be a new transfer record", () => {}); ## ⚪ ️1.13 Other generic good testing hygiene -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +:white_check_mark: **Do:** This post is focused on testing advice that is related to or at least can be exemplified with Node JS. This bullet, however, groups a few non-Node related tips that are well-known -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satisfies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, and let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satisfies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc)
    ❌ **Otherwise:** You‘ll miss pearls of wisdom that were collected for decades @@ -744,13 +744,13 @@ Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-dr ## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid -:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **Do:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategies. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? -Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IoT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. +Don’t get me wrong, in 2019 the testing pyramid, TDD, and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IoT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increases (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. -It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks +It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest a few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that builds a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks -A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] +A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, and others think it’s the devil. Everyone who speaks in absolutes is wrong :]
    @@ -778,9 +778,9 @@ A word of caution: the TDD argument in the software world takes a typical false- ## ⚪ ️2.2 Component testing might be your best affair -:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +:white_check_mark: **Do:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best of both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. -Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +Component tests focus on the Microservice ‘unit’, they work against the API and don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outward to inward and gain great confidence in a reasonable amount of time. [We have a full guide that is solely dedicated to writing component tests in the right way](https://github.com/testjavascript/nodejs-integration-tests-best-practices) @@ -806,7 +806,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ## ⚪ ️2.3 Ensure new releases don’t break the API using contract tests -:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. There are a spectrum of techniques that can mitigate the contract problem, some are simple, other are more feature-rich and demand a steeper learning curve. In a simple and recommended approach, the API provider publishes npm package with the API typing (e.g. JSDoc, TypeScript). Then the consumers can fetch this library and benefit from codign time intellisense and validation. A fancier approach it to use [PACT](https://docs.pact.io/) which were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +:white_check_mark: **Do:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. There is a spectrum of techniques that can mitigate the contract problem, some are simple, other are more feature-rich and demand a steeper learning curve. In a simple and recommended approach, the API provider publishes npm package with the API typing (e.g. JSDoc, TypeScript). Then the consumers can fetch this library and benefit from codign time intellisense and validation. A fancier approach is to use [PACT](https://docs.pact.io/) which was born to formalize this process with a very disruptive approach — not the server defines the test plan itself rather the client defines the tests of the… server! PACT can record the client expectation and put it in a shared location, “broker”, so the server can pull the expectations and run on every build using the PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration
    ❌ **Otherwise:** The alternatives are exhausting manual testing or deployment fear From 21c178135b4cab802fb92213e35845aced2bae4d Mon Sep 17 00:00:00 2001 From: Serhii Shramko Date: Sun, 31 Jul 2022 14:56:04 +0200 Subject: [PATCH 454/502] =?UTF-8?q?feat(wip):=20add=20Ukrainian=20?= =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20translation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme-ua.md | 2138 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2138 insertions(+) create mode 100644 readme-ua.md diff --git a/readme-ua.md b/readme-ua.md new file mode 100644 index 00000000..aa5b9552 --- /dev/null +++ b/readme-ua.md @@ -0,0 +1,2138 @@ + + +# 👇 Чому цей посібник може вивести ваші навички тестування на новий рівень + +
    + +## 📗 50+ найкращих практик: надзвичайно комплексний і вичерпний + +Це посібник із надійності JavaScript і Node.js від А до Я. Він узагальнює та курує для вас десятки найкращих публікацій у блогах, книг та інструментів, які може запропонувати ринок + +## 🚢 Просунутий: виходить на 10 000 миль за межі основ + +Вирушайте в подорож, яка виходить далеко за межі основ і переходить до складних тем, як тестування у виробництві, мутаційне тестування, тестування на основі властивостей та багато інших стратегічних і професійних інструментів. Якщо ви прочитаєте кожне слово в цьому посібнику, ваші навички тестування, швидше за все, будуть набагато вище середнього + +## 🌐 Full-stack: front, backend, CI, та інше + +Почніть із розуміння практик тестування, які є основою для будь-якого рівня програми. Потім заглибтеся в обрану область: frontend + +
    + +### Написав Yoni Goldberg + +- JavaScript & Node.js консультант +- 📗 [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) - Мій комплексний онлайн-курс із більш ніж [7 годинами відео](https://www.testjavascript.com), 14 типів тестів і більше 40 кращих практик +- [Follow me on 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) +- 🇺🇦[Ukrainian](readme-ua.md) - З люб'язного дозволу [Serhii Shramko](https://github.com/Shramkoweb) +- 🇪🇸[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) +- Хочете перекласти на свою рідну мову? будь ласка, відкрийте 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 "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 не містить потрібного твердження, і воно дуже повторюване, подумайте над [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 "Examples with 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 Don’t “foo”, use realistic input data + +:white_check_mark: **Роби:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Chance](https://github.com/chancejs/chancejs) or [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +
    + +❌ **Інакше:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data + +![](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:Doing It Right Example: Randomizing realistic input + +```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 Test many input combinations using Property-based testing + +:white_check_mark: **Роби:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet [‘Don’t foo’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +
    + +❌ **Інакше:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Testing many input permutations with “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 If needed, use only short & inline snapshots + +:white_check_mark: **Роби:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. + +On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much + +It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes +
    + +❌ **Інакше:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code + +![](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: Doing It Right Example: Expectations are visible and focused + +```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
    • +
    +`); +}); +``` + +
    + +

    + +## ⚪ ️Copy code, but only what's neccessary + +:white_check_mark: **Роби:** Include all the necessary details that affect the test result, but nothing more. As an example, consider a test that should factor 100 lines of input JSON - Pasting this in every test is tedious. Extracting it outside to transferFactory.getJSON() will leave the test vague - Without data, it's hard to correlate the test result with the cause ("why is it supposed to return 400 status?"). The classic book x-unit patterns named this pattern 'the mystery guest' - Something unseen affected our test results, we don't know what exactly. We can do better by extracting repeatable long parts outside AND mention explictly which specific details matter to the test. Going with the example above, the test can pass parameters that highlight what is important: transferFactory.getJSON({sender: undefined}). In this example, the reader should immediately infer that the empty sender field is the reason why the test should expect a validation error or any other similar adequate outcome. +
    + +❌ **Інакше:** Copying 500 JSON lines in will leave your tests unmaintainable and unreadable. Moving everything outside will end with vague tests that are hard to understand + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Anti-Pattern Example: The test failure is unclear because all the cause is external and hides within huge 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() //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: Doing It Right Example: The test highlights what is the cause of the test result + +```javascript + +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}); + + // 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 Don’t catch errors, expect them + +:white_check_mark: **Роби:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations + +A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +
    + +❌ **Інакше:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with 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: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical 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 Tag your tests + +:white_check_mark: **Роби:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +
    + +❌ **Інакше:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) + +![](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 Categorize tests under at least 2 levels + +:white_check_mark: **Роби:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see Приклади коду and print screen below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) + +
    + +❌ **Інакше:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in a hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below + +![](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: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests + +![](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 Other generic good testing hygiene + +:white_check_mark: **Роби:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satisfies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +
    + +❌ **Інакше:** You‘ll miss pearls of wisdom that were collected for decades + +

    + +# Section 2️⃣: Backend Testing + +## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid + +:white_check_mark: **Роби:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? + +Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IoT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. + +It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks + +A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] + +
    + +❌ **Інакше:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing 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 Component testing might be your best affair + +:white_check_mark: **Роби:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. + +Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. + +[We have a full guide that is solely dedicated to writing component tests in the right way](https://github.com/testjavascript/nodejs-integration-tests-best-practices) + +
    + +❌ **Інакше:** You may spend long days on writing unit tests to find out that you got only 20% system coverage + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) + +![](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 Ensure new releases don’t break the API using contract tests + +:white_check_mark: **Роби:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. There are a spectrum of techniques that can mitigate the contract problem, some are simple, other are more feature-rich and demand a steeper learning curve. In a simple and recommended approach, the API provider publishes npm package with the API typing (e.g. JSDoc, TypeScript). Then the consumers can fetch this library and benefit from codign time intellisense and validation. A fancier approach it to use [PACT](https://docs.pact.io/) which were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +
    + +❌ **Інакше:** The alternatives are exhausting manual testing or deployment fear + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: + +![](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 Test your middlewares in isolation + +:white_check_mark: **Роби:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +
    + +❌ **Інакше:** A bug in Express middleware === a bug in all or most requests + +
    + +
    Приклади коду + +
    + +### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine + +![](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 Measure and refactor using static analysis tools + +:white_check_mark: **Роби:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [SonarQube](https://www.sonarqube.org/) (4,900+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (2,000+ [stars](https://github.com/codeclimate/codeclimate)) + +Credit: [Keith Holliday](https://github.com/TheHollidayInn) + +
    + +❌ **Інакше:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: CodeClimate, a commercial tool that can identify complex methods: + +![](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: **Роби:** Weirdly, most software testings are about logic & data only, but some of the worst things that happen (and are really hard to mitigate) are infrastructural issues. For example, did you ever test what happens when your process memory is overloaded, or when the server/process dies, or does your monitoring system realizes when the API becomes 50% slower?. To test and mitigate these type of bad things — [Chaos engineering](https://principlesofchaos.org/) was born by Netflix. It aims to provide awareness, frameworks and tools for testing our app resiliency for chaotic issues. For example, one of its famous tools, [the chaos monkey](https://github.com/Netflix/chaosmonkey), randomly kills servers to ensure that our service can still serve users and not relying on a single server (there is also a Kubernetes version, [kube-monkey](https://github.com/asobti/kube-monkey), that kills pods). All these tools work on the hosting/platform level, but what if you wish to test and generate pure Node chaos like check how your Node process copes with uncaught errors, unhandled promise rejection, v8 memory overloaded with the max allowed of 1.7GB or whether your UX remains satisfactory when the event loop gets blocked often? to address this I’ve written, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) which provides all sort of Node-related chaotic acts +
    + +❌ **Інакше:** No escape here, Murphy’s law will hit your production without mercy + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: : Node-chaos can generate all sort of Node.js pranks so you can test how resilience is your app to 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 Avoid global test fixtures and seeds, add data per-test + +:white_check_mark: **Роби:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +
    + +❌ **Інакше:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Anti-Pattern Example: tests are not independent and rely on some global hook to feed global DB data + +![](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: Doing It Right Example: We can stay within the test, each test acts on its own set of data + +```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); +}); +``` + +
    + +
    + +## ⚪ ️2.8 Choose a clear data clean-up strategy: After-all (recommended) or after-each + +:white_check_mark: **Роби:** The timing when the tests clean the database determines the way the tests are being written. The two most viable options are cleaning after all the tests vs cleaning after every single test. Choosing the latter option, cleaning after every single test guarantees clean tables and builds convenient testing perks for the developer. No other records exist when the test starts, one can have certainty which data is being queried and even might be tempted to count rows during assertions. This comes with severe downsides: When running in a multi-process mode, tests are likely to interfere with each other. While process-1 purges tables, at the very moment process-2 queries for data and fail (because the DB was suddenly deleted by process-1). On top of this, It's harder to troubleshoot failing tests - Visiting the DB will show no records. + +The second option is to clean up after all the test files have finished (or even daily!). This approach means that the same DB with existing records serves all the tests and processes. To avoid stepping on each other's toes, the tests must add and act on specific records that they have added. Need to check that some record was added? Assume that there are other thousands of records and query for records that were added explicitly. Need to check that a record was deleted? Can't assume an empty table, check that this specific record is not there. This technique brings few powerful gains: It works natively in multi-process mode, when a developer wishes to understand what happened - the data is there and not deleted. It also increases the chance of finding bugs because the DB is full of records and not artificially empty. [See the full comparison table here](https://github.com/testjavascript/nodejs-integration-tests-best-practices/blob/master/graphics/db-clean-options.png). +
    + +❌ **Інакше:** Without a strategy to separate records or clean - Tests will step on each other toes; Using transactions will work only for relational DB and likely to get complicated once there are inner transactions + +
    + +
    Приклади коду + +
    + +### :clap: Cleaning after ALL the tests. Not neccesserily after every run. The more data we have while the tests are running - The more it resembles the production perks + +```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 Isolate the component from the world using HTTP interceptor + +:white_check_mark: **Роби:** Isolate the component under test by intercepting any outgoing HTTP request and providing the desired response so the collaborator HTTP API won't get hit. Nock is a great tool for this mission as it provides a convenient syntax for defining external services behavior. Isolation is a must to prevent noise and slow performance but mostly to simulate various scenarios and responses - A good flight simulator is not about painting clear blue sky rather bringing safe storms and chaos. This is reinforced in a Microservice architecture where the focus should always be on a single component without involving the rest of the world. Though it's possible to simulate external service behavior using test doubles (mocking), it's preferable not to touch the deployed code and act on the network level to keep the tests pure black-box. The downside of isolation is not detecting when the collaborator component changes and not realizing misunderstandings between the two services - Make sure to compensate for this using a few contract or E2E tests +
    + +❌ **Інакше:** Some services provide a fake version that can be deployed by the caller locally, usually using Docker - This will ease the setup and boost the performance but won't help with simulating various responses; Some services provide 'sandbox' environment, so the real service is hit but no costs or side effects are triggered - This will cut down the noise of setting up the 3rd party service but also won't allow simulating scenarios + +
    + +
    Приклади коду + +
    + +### :clap: Preventing network calls to externous components allows simulating scnearios and minimizing the noise + +```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 Test the response schema, mostly when there are auto-generated fields + +:white_check_mark: **Роби:** When it is impossible to assert for specific data, check for mandatory field existence and types. Sometimes, the response contains important fields with dynamic data that can't be predicted when writing the test, like dates and incrementing numbers. If the API contract promises that these fields won't be null and hold the right types, it's imperative to test it. Most assertion libraries support checking types. If the response is small, check the return data and type together within the same assertion (see code example). One more option is to verify the entire response against an OpenAPI doc (Swagger). Most test runners have community extensions that validate API responses against their documentation. + + +
    + +❌ **Інакше:** Although the code/API caller relies on some field with dynamic data (e.g., ID, date), it will not come in return and break the contract + +
    + +
    Приклади коду + +
    + +### :clap: Asserting that fields with dynamic value exist and have the right type + +```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 Check integrations corner cases and chaos + +:white_check_mark: **Роби:** When checking integrations, go beyond the happy and sad paths. Check not only errored responses (e.g., HTTP 500 error) but also network-level anomalies like slow and timed-out responses. This will prove that the code is resilient and can handle various network scenarios like taking the right path after a timeout, has no fragile race conditions, and contains a circuit breaker for retries. Reputable interceptor tools can easily simulate various network behaviors like hectic service that occasionally fail. It can even realize when the default HTTP client timeout value is longer than the simulated response time and throw a timeout exception right away without waiting + + +
    + +❌ **Інакше:** All your tests pass, it's only the production who will crash or won't report errors correctly when 3rd parties send excpetional responses + +
    + +
    Приклади коду + +
    + +### :clap: Ensuring that on network failures, the circuit breaker can save the day + +```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 Test the five potential outcomes + +:white_check_mark: **Роби:** When planning your tests, consider covering the five typical flow's outputs. When your test is triggering some action (e.g., API call), a reaction is happening, something meaningful occurs and calls for testing. Note that we don't care about how things work. Our focus is on outcomes, things that are noticeable from the outside and might affect the user. These outcomes/reactions can be put in 5 categories: + +• Response - The test invokes an action (e.g., via API) and gets a response. It's now concerned with checking the response data correctness, schema, and HTTP status + +• A new state - After invoking an action, some **publicly accessible** data is probably modified + +• External calls - After invoking an action, the app might call an external component via HTTP or any other transport. For example, a call to send SMS, email or charge a credit card + +• Message queues - The outcome of a flow might be a message in a queue + +• Observability - Some things must be monitored, like errors or remarkable business events. When a transaction fails, not only we expect the right response but also correct error handling and proper logging/metrics. This information goes directly to a very important user - The ops user (i.e., production SRE/admin) + +
    + +

    + +# Section 3️⃣: Frontend Testing + +## ⚪ ️ 3.1 Separate UI from functionality + +:white_check_mark: **Роби:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI + +
    + +❌ **Інакше:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Separating out the UI details + +![](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: Anti-Pattern Example: Assertion mix UI details and data + +```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 HTML elements based on attributes that are unlikely to change + +:white_check_mark: **Роби:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed + +
    + +❌ **Інакше:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Querying an element using a dedicated attribute for testing + +![](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: Anti-Pattern Example: Relying on 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 Whenever possible, test with a realistic and fully rendered component + +:white_check_mark: **Роби:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet ['Favour blackbox testing'](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-14-stick-to-black-box-testing-test-only-public-methods)). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake + +With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children + +
    + +❌ **Інакше:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Working realistically with a fully rendered component + +![](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: Anti-Pattern Example: Mocking the reality with 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 Don't sleep, use frameworks built-in support for async events. Also try to speed things up + +:white_check_mark: **Роби:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +
    + +❌ **Інакше:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (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: Doing It Right Example: Testing library that waits for DOM elements + +```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: Anti-Pattern Example: custom sleep code + +```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 Watch how the content is served over the network + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") + +✅ **Роби:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 + +
    + +❌ **Інакше:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration + +
    + +
    Приклади коду + +### :clap: Doing It Right Example: Lighthouse page load inspection report + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + +
    + +
    + +## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs + +:white_check_mark: **Роби:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests + +
    + +❌ **Інакше:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower + +
    + +
    Приклади коду + +
    + +### :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 +// 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 Have very few end-to-end tests that spans the whole system + +:white_check_mark: **Роби:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment + +
    + +❌ **Інакше:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected + +
    + +## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials + +:white_check_mark: **Роби:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)). + +
    + +❌ **Інакше:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Logging-in before-all and not 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 Have one E2E smoke test that just travels across the site map + +:white_check_mark: **Роби:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector + +
    + +❌ **Інакше:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Smoke travelling across all 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.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: **Роби:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. + +❌ **Інакше:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Describing tests in human-language using 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: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook + +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 Detect visual issues with automated tools + +:white_check_mark: **Роби:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge significant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by eliminating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/CSS changes that led to the issue + +
    + +❌ **Інакше:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Anti-Pattern Example: A typical visual regression - right content that is served badly + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + +### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots + +![](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: Doing It Right Example: Using Applitools to get snapshot comparison and other advanced features + +![](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️⃣: Measuring Test Effectiveness + +

    + +## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number + +:white_check_mark: **Роби:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. + +Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets + +
    + +❌ **Інакше:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down + +
    + +
    Приклади коду + +
    + +### :clap: Example: A typical coverage report + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: Doing It Right Example: Setting up coverage per component (using 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 Inspect coverage reports to detect untested areas and other oddities + +:white_check_mark: **Роби:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +
    + +❌ **Інакше:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Anti-Pattern Example: 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?") + +
    + +

    + +## ⚪ ️ 4.3 Measure logical coverage using mutation testing + +:white_check_mark: **Роби:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. + +Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: + +(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations + +(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. + +Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +
    + +❌ **Інакше:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Anti-Pattern Example: 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: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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 Preventing test code issues with Test linters + +:white_check_mark: **Роби:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) + +
    + +❌ **Інакше:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation + +
    +
    Приклади коду + +
    + +### :thumbsdown: Anti-Pattern Example: A test case full of errors, luckily all are caught by 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 and Other Quality Measures + +

    + +## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues + +:white_check_mark: **Роби:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +
    + +❌ **Інакше:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5 min linter setup could detect this TYPO and save your day + +
    + +
    Приклади коду + +
    + +### :thumbsdown: Anti-Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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 Shorten the feedback loop with local developer-CI + +:white_check_mark: **Роби:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. + +Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +
    + +❌ **Інакше:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new 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 Perform e2e testing over a true production-mirror + +:white_check_mark: **Роби:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of FaaS code. + +The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +
    + +❌ **Інакше:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated + +
    + +
    Приклади коду + +
    + +### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([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 Parallelize test execution + +:white_check_mark: **Роби:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes + +❌ **Інакше:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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 Stay away from legal issues using license and plagiarism check + +:white_check_mark: **Роби:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stack Overflow and apparently violates some copyrights + +❌ **Інакше:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues + +
    + +
    Приклади коду + +
    + +### :clap: Doing It Right Example: + +```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 Constantly inspect for vulnerable dependencies + +:white_check_mark: **Роби:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build + +❌ **Інакше:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious + +
    + +
    Приклади коду + +
    + +### :clap: Example: NPM Audit result + +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + +

    + +## ⚪ ️5.7 Automate dependency updates + +:white_check_mark: **Роби:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: + +(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. + +(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). + +An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +
    + +❌ **Інакше:** Your production will run packages that have been explicitly tagged by their author as risky + +
    + +
    Приклади коду + +
    + +### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest 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 Other, non-Node related, CI tips + +:white_check_mark: **Роби:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +
    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
    +
    + +❌ **Інакше:** You‘ll miss years of wisdom + +

    + +## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions + +:white_check_mark: **Роби:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +
    + +❌ **Інакше:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? + +
    + +
    Приклади коду + +
    + +### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions + +
    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. + +## 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

    🌍 💻
    + + + + + From f9f0a51b82d36103ee85da7e0daf604cdce4372a Mon Sep 17 00:00:00 2001 From: Serhii Shramko Date: Sun, 31 Jul 2022 16:41:07 +0200 Subject: [PATCH 455/502] feat: implement 1 chapter --- readme-ua.md | 231 +++++++++++++++++++++++++-------------------------- 1 file changed, 115 insertions(+), 116 deletions(-) diff --git a/readme-ua.md b/readme-ua.md index aa5b9552..56f5fd4b 100644 --- a/readme-ua.md +++ b/readme-ua.md @@ -21,8 +21,8 @@ ### Написав Yoni Goldberg - JavaScript & Node.js консультант -- 📗 [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) - Мій комплексний онлайн-курс із більш ніж [7 годинами відео](https://www.testjavascript.com), 14 типів тестів і більше 40 кращих практик -- [Follow me on Twitter](https://twitter.com/goldbergyoni/) +- 📗 [Тестування Node.js і JavaScript від А до Я](https://www.testjavascript.com) - Мій комплексний онлайн-курс із більш ніж [7 годинами відео](https://www.testjavascript.com), 14 типів тестів і більше 40 кращих практик +- [Слідкуйте за мною в Twitter](https://twitter.com/goldbergyoni/)
    @@ -174,7 +174,7 @@ describe('Products Service', function() { ### :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") +![](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", () => { @@ -220,7 +220,7 @@ test("Should be classified as premium", () => {
    Приклади коду
    -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Приклади з Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Приклади з Mocha & Chai") ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") ### :thumbsdown: Приклад антишаблону: читач повинен проглянути довгий код і імперативний код, щоб зрозуміти суть @@ -370,12 +370,12 @@ it("When a valid product is about to be deleted, ensure an email is sent", async

    -## ⚪ ️1.6 Don’t “foo”, use realistic input data +## ⚪ ️1.6 Використовуйте реалістичні вхідні дані -:white_check_mark: **Роби:** Often production bugs are revealed under some very specific and surprising input — the more realistic the test input is, the greater the chances are to catch bugs early. Use dedicated libraries like [Chance](https://github.com/chancejs/chancejs) or [Faker](https://www.npmjs.com/package/faker) to generate pseudo-real data that resembles the variety and form of production data. For example, such libraries can generate realistic phone numbers, usernames, credit card, company names, and even ‘lorem ipsum’ text. You may also create some tests (on top of unit tests, not as a replacement) that randomize fakers data to stretch your unit under test or even import real data from your production environment. Want to take it to the next level? See the next bullet (property-based testing). +:white_check_mark: **Роби:** Часто production помилки виявляються під час дуже специфічних і несподіваних вхідних даних — ««чим реалістичнішими є тестові вхідні дані, тим більші шанси виявити помилки на ранній стадії. Використовуйте спеціальні бібліотеки, як-от [Chance](https://github.com/chancejs/chancejs) або [Faker](https://www.npmjs.com/package/faker), щоб генерувати псевдореальні дані. Наприклад, такі бібліотеки можуть створювати реалістичні номери телефонів, імена користувачів, кредитні картки, назви компаній і навіть текст «lorem ipsum». Ви також можете створити деякі тести (на додаток до модульних тестів, а не як заміну), які рандомізують дані фальсифікаторів, щоб розширити тестований блок або навіть імпортувати реальні дані з вашого робочого середовища. Хочете перейти на новий рівень? Дивіться наступний пункт (тестування на основі властивостей).
    -❌ **Інакше:** All your development testing will falsely show green when you use synthetic inputs like “Foo”, but then production might turn red when a hacker passes-in a nasty string like “@3e2ddsf . ##’ 1 fdsfds . fds432 AAAA” +❌ **Інакше:** Усе ваше тестування розробки хибно показуватиме зелений колір, якщо ви використовуєте синтетичні вхідні дані, як-от «Foo», але тоді production може стати червоним, коли хакер передасть неприємний рядок, як-от «@3e2ddsf». ##’ 1 fdsfds . fds432 AAAA”
    @@ -383,40 +383,40 @@ it("When a valid product is about to be deleted, ensure an email is sent", async
    -### :thumbsdown: Anti-Pattern Example: A test suite that passes due to non-realistic data +### :thumbsdown: Приклад антишаблону: набір тестів, який проходить через нереалістичні дані -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") ```javascript const addProduct = (name, price) => { - const productNameRegexNoSpace = /^\S*$/; //no white-space allowed + const productNameRegexNoSpace = /^\S*$/; // white-space не допускаються - if (!productNameRegexNoSpace.test(name)) return false; //this path never reached due to dull input + if (!productNameRegexNoSpace.test(name)) return false; // цей шлях не досягнуто через рінній вихід - //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 + // Рядок "Foo", який використовується в усіх тестах, ніколи не викликає хибний результат 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:Doing It Right Example: Randomizing realistic input +### :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} + // Згенеровані випадкові данні: {'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! + // Тест провалився, випадковий вхід ініціював певний шлях, який ми не планували. + // Ми рано виявили помилку! }); ``` @@ -424,12 +424,12 @@ it("Better: When adding new valid product, get successful confirmation", async (

    -## ⚪ ️ 1.7 Test many input combinations using Property-based testing +## ⚪ ️ 1.7 Перевірте багато вхідних комбінацій за допомогою тестування на основі властивостей -:white_check_mark: **Роби:** Typically we choose a few input samples for each test. Even when the input format resembles real-world data (see bullet [‘Don’t foo’](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F16-dont-foo-use-realistic-input-data)), we cover only a few input combinations (method(‘’, true, 1), method(“string” , false , 0)), However, in production, an API that is called with 5 parameters can be invoked with thousands of different permutations, one of them might render our process down ([see Fuzz Testing](https://en.wikipedia.org/wiki/Fuzzing)). What if you could write a single test that sends 1000 permutations of different inputs automatically and catches for which input our code fails to return the right response? Property-based testing is a technique that does exactly that: by sending all the possible input combinations to your unit under test it increases the serendipity of finding a bug. For example, given a method — addNewProduct(id, name, isDiscount) — the supporting libraries will call this method with many combinations of (number, string, boolean) like (1, “iPhone”, false), (2, “Galaxy”, true). You can run property-based testing using your favorite test runner (Mocha, Jest, etc) using libraries like [js-verify](https://github.com/jsverify/jsverify) or [testcheck](https://github.com/leebyron/testcheck-js) (much better documentation). Update: Nicolas Dubien suggests in the comments below to [checkout fast-check](https://github.com/dubzzz/fast-check#readme) which seems to offer some additional features and also to be actively maintained +: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) який, здається, пропонує деякі додаткові функції та також активно підтримується
    -❌ **Інакше:** Unconsciously, you choose the test inputs that cover only code paths that work well. Unfortunately, this decreases the efficiency of testing as a vehicle to expose bugs +❌ **Інакше:** Несвідомо ви обираєте тестові вхідні дані, які охоплюють лише шляхи коду, які добре працюють. На жаль, це знижує ефективність тестування як засобу виявлення помилок
    @@ -437,16 +437,16 @@ it("Better: When adding new valid product, get successful confirmation", async (
    -### :clap: Doing It Right Example: Testing many input permutations with “fast-check” +### :clap: Приклад правильного виконання: Тестування багатьох вхідних перестановок за допомогою “fast-check” -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") ```javascript import fc from "fast-check"; describe("Product service", () => { describe("Adding new", () => { - //this will run 100 times with different random properties + // буде виконано 100 разів з різними випадковими властивостями it("Add new product with random yet valid properties, always successful", () => fc.assert( fc.property(fc.integer(), fc.string(), (id, name) => { @@ -461,16 +461,15 @@ describe("Product service", () => {

    -## ⚪ ️ 1.8 If needed, use only short & inline snapshots +## ⚪ ️ 1.8 За потреби використовуйте лише короткі та вбудовані знімки -:white_check_mark: **Роби:** When there is a need for [snapshot testing](https://jestjs.io/docs/en/snapshot-testing), use only short and focused snapshots (i.e. 3-7 lines) that are included as part of the test ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) and not within external files. Keeping this guideline will ensure your tests remain self-explanatory and less fragile. +:white_check_mark: **Роби:** Коли є потреба в [снепшот тестуванні](https://jestjs.io/docs/en/snapshot-testing), використовуйте лише короткі та цілеспрямовані снепшоти (3-7 рядків) які входять до складу тесту ([Inline Snapshot](https://jestjs.io/docs/en/snapshot-testing#inline-snapshots)) а не в зовнішніх файлах. Дотримуючись цієї вказівки, ваші тести залишатимуться зрозумілими та менш крихкими. -On the other hand, ‘classic snapshots’ tutorials and tools encourage to store big files (e.g. component rendering markup, API JSON result) over some external medium and ensure each time when the test run to compare the received result with the saved version. This, for example, can implicitly couple our test to 1000 lines with 3000 data values that the test writer never read and reasoned about. Why is this wrong? By doing so, there are 1000 reasons for your test to fail - it’s enough for a single line to change for the snapshot to get invalid and this is likely to happen a lot. How frequently? for every space, comment or minor CSS/HTML change. Not only this, the test name wouldn’t give a clue about the failure as it just checks that 1000 lines didn’t change, also it encourages to the test writer to accept as the desired true a long document he couldn’t inspect and verify. All of these are symptoms of obscure and eager test that is not focused and aims to achieve too much +З іншого боку, навчальні посібники та інструменти «класичних знімків» заохочують зберігати великі файли (наприклад, розмітку візуалізації компонентів, результат JSON API) на зовнішньому носії та забезпечувати порівняння отриманого результату зі збереженою версією під час кожного тестового запуску. Це, наприклад, може неявно поєднати наш тест із 1000 рядками з 3000 значеннями даних, які автор тесту ніколи не читав і не міркував. Чому це неправильно? Таким чином, є 1000 причин невдачі вашого тесту - достатньо змінити один рядок, щоб знімок став недійсним, і це, ймовірно, станеться часто. Як часто? для кожного пробілу, коментаря або незначної зміни CSS/HTML. Крім того, назва тесту не дасть підказки про помилку, оскільки вона лише перевіряє, чи не змінилися 1000 рядків, а також заохочує автора тесту прийняти за бажаний істинний довгий документ, який він не міг перевірити та перевірити. Усе це симптоми незрозумілого та жадібного випробування, яке не є зосередженим і спрямоване на досягнення занадто багато -It’s worth noting that there are few cases where long & external snapshots are acceptable - when asserting on schema and not data (extracting out values and focusing on fields) or when the received document rarely changes -
    +Варто зазначити, що є кілька випадків, коли довгі та зовнішні снепшоти є прийнятними - коли стверджується на схемі, а не на даних (вилучення значень і фокусування на полях) або коли отриманий документ рідко змінюється
    -❌ **Інакше:** A UI test fails. The code seems right, the screen renders perfect pixels, what happened? your snapshot testing just found a difference from the origin document to current received one - a single space character was added to the markdown... +❌ **Інакше:** UI тести впали. Код виглядає правильним, екран відображає ідеально все, що сталося? Ваше тестування снепшотів щойно виявило різницю між вихідним документом і поточним отриманим документом – до розмітки додано один пробіл...
    @@ -478,9 +477,9 @@ It’s worth noting that there are few cases where long & external snapshots are
    -### :thumbsdown: Anti-Pattern Example: Coupling our test to unseen 2000 lines of code +### :thumbsdown: Приклад антишаблону: Поєднання нашого тесту з невидимими 2000 рядками коду -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") ```javascript it("TestJavaScript.com is renderd correctly", () => { @@ -493,14 +492,14 @@ it("TestJavaScript.com is renderd correctly", () => { //Assert expect(receivedPage).toMatchSnapshot(); - //We now implicitly maintain a 2000 lines long document - //every additional line break or comment - will break this test + //Тепер ми неявно підтримуємо документ довжиною 2000 рядків + //кожен додатковий розрив рядка або коментар - порушить цей тест }); ```
    -### :clap: Doing It Right Example: Expectations are visible and focused +### :clap: Приклад правильного виконання: Очікування помітні та цілеспрямовані ```javascript it("When visiting TestJavaScript.com home page, a menu is displayed", () => { @@ -528,12 +527,12 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {

    -## ⚪ ️Copy code, but only what's neccessary +## ⚪ ️Скопіюйте код, але тільки необхідний -:white_check_mark: **Роби:** Include all the necessary details that affect the test result, but nothing more. As an example, consider a test that should factor 100 lines of input JSON - Pasting this in every test is tedious. Extracting it outside to transferFactory.getJSON() will leave the test vague - Without data, it's hard to correlate the test result with the cause ("why is it supposed to return 400 status?"). The classic book x-unit patterns named this pattern 'the mystery guest' - Something unseen affected our test results, we don't know what exactly. We can do better by extracting repeatable long parts outside AND mention explictly which specific details matter to the test. Going with the example above, the test can pass parameters that highlight what is important: transferFactory.getJSON({sender: undefined}). In this example, the reader should immediately infer that the empty sender field is the reason why the test should expect a validation error or any other similar adequate outcome. +:white_check_mark: **Роби:** Включіть усі необхідні деталі, які впливають на результат тесту, але не більше того. Як приклад, розглянемо тест, який має розраховувати 100 рядків вхідного JSON - Вставляти це в кожен тест утомливо. Якщо витягти його за межі transferFactory.getJSON(), тест залишиться невизначеним - Без даних важко співвіднести результат тесту з причиною («чому він має повертати статус 400?»). Класичні книжкові шаблони x-unit назвали цей шаблон «таємничим гостем» - Щось невидиме вплинуло на наші результати тестування, ми не знаємо, що саме. Ми можемо досягти кращих результатів, витягнувши повторювані довгі частини назовні І чітко зазначивши, які конкретні деталі мають значення для тесту. Переходячи до наведеного вище прикладу, тест може передавати параметри, які підкреслюють важливість: transferFactory.getJSON({sender: undefined}). У цьому прикладі читач повинен негайно зробити висновок, що порожнє поле відправника є причиною, чому тест повинен очікувати помилку перевірки або будь-який інший подібний адекватний результат.
    -❌ **Інакше:** Copying 500 JSON lines in will leave your tests unmaintainable and unreadable. Moving everything outside will end with vague tests that are hard to understand +❌ **Інакше:** Копіювання 500 рядків JSON зробить ваші тести непридатними для обслуговування та читання. Перенесення всього назовні закінчиться нечіткими тестами, які важко зрозуміти
    @@ -541,40 +540,40 @@ it("When visiting TestJavaScript.com home page, a menu is displayed", () => {
    -### :thumbsdown: Anti-Pattern Example: The test failure is unclear because all the cause is external and hides within huge JSON +### :thumbsdown: Приклад антишаблону: Помилка тесту незрозуміла, оскільки вся причина зовнішня і ховається у величезному JSON -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") +![](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() //get back 200 lines of JSON; + const transferRequest = testHelpers.factorMoneyTransfer() // повертає 200 рядків 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 🤔 + expect(transferResponse.status).toBe(409);// Але чому ми очікуємо невдачі: у тесті все здається абсолютно дійсним 🤔 }); ```
    -### :clap: Doing It Right Example: The test highlights what is the cause of the test result +### :clap: Приклад правильного виконання: Тест підкреслює, що є причиною результату тесту ```javascript 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 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); // Obviously if the user has no credit it should fail + expect(transferResponse.status).toBe(409); // Очевидно, що якщо у користувача немає кредиту, тест впаде }); ``` @@ -582,14 +581,14 @@ test("When no credit, then the transfer is declined ", async() => {

    -## ⚪ ️ 1.10 Don’t catch errors, expect them +## ⚪ ️ 1.10 Не ловіть помилки, очікуйте їх -:white_check_mark: **Роби:** When trying to assert that some input triggers an error, it might look right to use try-catch-finally and asserts that the catch clause was entered. The result is an awkward and verbose test case (example below) that hides the simple test intent and the result expectations +:white_check_mark: **Роби:** Під час спроби стверджувати, що якийсь вхід викликає помилку, може виглядати правильним використання try-catch-finally і підтверджує, що було введено пропозицію catch. Результатом є незручний і багатослівний тестовий приклад (приклад нижче), який приховує простий намір тесту та очікування результату -A more elegant alternative is the using the one-line dedicated Chai assertion: expect(method).to.throw (or in Jest: expect(method).toThrow()). It’s absolutely mandatory to also ensure the exception contains a property that tells the error type, otherwise given just a generic error the application won’t be able to do much rather than show a disappointing message to the user +Більш елегантною альтернативою є використання однорядкового виділеного твердження Chai: expect(method).to.throw (або Jest: expect(method).toThrow()). Абсолютно обовʼязково також переконатися, що виняток містить властивість, яка повідомляє тип помилки, інакше, враховуючи лише загальну помилку і покаже користувачеві невтішне повідомлення
    -❌ **Інакше:** It will be challenging to infer from the test reports (e.g. CI reports) what went wrong +❌ **Інакше:** Буде складно зробити висновок зі звітів про випробування (наприклад, звітів CI), що пішло не так
    @@ -597,9 +596,9 @@ A more elegant alternative is the using the one-line dedicated Chai assertion: e
    -### :thumbsdown: Anti-pattern Example: A long test case that tries to assert the existence of error with try-catch +### :thumbsdown: Приклад антишаблону: Довгий тестовий приклад, який намагається підтвердити існування помилки за допомогою try-catch -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") +![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Приклади з Mocha") ```javascript it("When no product name, it throws error 400", async () => { @@ -611,14 +610,14 @@ it("When no product name, it throws error 400", async () => { 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: Doing It Right Example: A human-readable expectation that could be understood easily, maybe even by QA or technical PM +### :clap: Приклад правильного виконання: Очікування, зрозумілі людині, які можуть бути легко зрозумілі, можливо, навіть спеціалістам QA або PM ```javascript it("When no product name, it throws error 400", async () => { @@ -632,12 +631,12 @@ it("When no product name, it throws error 400", async () => {

    -## ⚪ ️ 1.11 Tag your tests +## ⚪ ️ 1.11 Позначте свої тести -:white_check_mark: **Роби:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with Mocha: mocha — grep ‘sanity’ +:white_check_mark: **Роби:** Різні тести повинні виконуватися за різними сценаріями: quick smoke, без IO, тести мають запускатися, коли розробник зберігає або фіксує файл, повні наскрізні тести (e2e) зазвичай запускаються, коли надсилається новий запит на отримання тощо. Цього можна досягти позначаючи тести ключовими словами, як-от #cold #api #sanity, щоб ви могли працювати зі своїм пакетом тестування та викликати потрібну підмножину. Наприклад, ось як ви можете викликати лише групу перевірки розумності за допомогою Mocha: mocha — grep ‘sanity’
    -❌ **Інакше:** Running all the tests, including tests that perform dozens of DB queries, any time a developer makes a small change can be extremely slow and keeps developers away from running tests +❌ **Інакше:** Виконання всіх тестів, у тому числі тестів, які виконують десятки запитів до БД, щоразу, коли розробник вносить невеликі зміни, може бути надзвичайно повільним і утримувати розробників від виконання тестівВиконання всіх тестів, у тому числі тестів, які виконують десятки запитів до БД, щоразу, коли розробник вносить невеликі зміни, може бути надзвичайно повільним і утримувати розробників від виконання тестів
    @@ -645,17 +644,17 @@ it("When no product name, it throws error 400", async () => {
    -### :clap: Doing It Right Example: Tagging tests as ‘#cold-test’ allows the test runner to execute only fast tests (Cold===quick tests that are doing no IO and can be executed frequently even as the developer is typing) +### :clap: Приклад правильного виконання: Позначення тестів як «#cold-test» дозволяє тестувальнику виконувати лише швидкі тести (Cold===швидкі тести, які не виконують введення-виведення (IO) та можуть виконуватися часто, навіть коли розробник вводить текст) -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") ```javascript -//this test is fast (no DB) and we're tagging it correspondigly -//now the user/CI can run it frequently +// цей тест швидкий (без БД), і ми позначаємо його відповідним чином +// тепер користувач/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() { - //code logic here + // Логіка тут }); }); }); @@ -665,13 +664,13 @@ describe("Order service", function() {

    -## ⚪ ️ 1.12 Categorize tests under at least 2 levels +## ⚪ ️ 1.12 Класифікуйте тести принаймні за 2 рівнями -:white_check_mark: **Роби:** Apply some structure to your test suite so an occasional visitor could easily understand the requirements (tests are the best documentation) and the various scenarios that are being tested. A common method for this is by placing at least 2 'describe' blocks above your tests: the 1st is for the name of the unit under test and the 2nd for additional level of categorization like the scenario or custom categories (see Приклади коду and print screen below). Doing so will also greatly improve the test reports: The reader will easily infer the tests categories, delve into the desired section and correlate failing tests. In addition, it will get much easier for a developer to navigate through the code of a suite with many tests. There are multiple alternative structures for test suite that you may consider like [given-when-then](https://github.com/searls/jasmine-given) and [RITE](https://github.com/ericelliott/riteway) +:white_check_mark: **Роби:** Застосуйте певну структуру до свого набору тестів, щоб випадковий відвідувач міг легко зрозуміти вимоги (тести — найкраща документація) і різні сценарії, які тестуються. Звичайним методом для цього є розміщення принаймні 2 блоків «describe» над вашими тестами: 1-й призначений для назви тестованого блоку, а 2-й для додаткового рівня категоризації, як-от сценарій або спеціальні категорії (дивись Приклади коду). Це також значно покращить звіти про тести: читач легко визначить категорії тестів, заглибиться в потрібний розділ і порівнює невдалі тести. Крім того, розробнику стане набагато легше орієнтуватися в коді набору з багатьма тестами. Існує кілька альтернативних структур для набору тестів, які ви можете розглянути, наприклад [given-when-then](https://github.com/searls/jasmine-given) і [RITE](https://github.com/ericelliott/riteway)
    -❌ **Інакше:** When looking at a report with flat and long list of tests, the reader have to skim-read through long texts to conclude the major scenarios and correlate the commonality of failing tests. Consider the following case: When 7/100 tests fail, looking at a flat list will demand reading the failing tests text to see how they relate to each other. However, in a hierarchical report all of them could be under the same flow or category and the reader will quickly infer what or at least where is the root failure cause +❌ **Інакше:** Дивлячись на звіт із плоским і довгим списком тестів, читач повинен побіжно прочитати довгі тексти, щоб зробити висновок про основні сценарії та співвіднести загальність невдалих тестів. Розглянемо наступний випадок: коли 7/100 тестів не вдаються, перегляд плоского списку потребує прочитання тексту невдалих тестів, щоб побачити, як вони пов’язані один з одним. Однак в ієрархічному звіті всі вони можуть входити до одного потоку або категорії, і читач швидко зрозуміє, що або принаймні де є основна причина збою
    @@ -679,19 +678,19 @@ describe("Order service", function() {
    -### :clap: Doing It Right Example: Structuring suite with the name of unit under test and scenarios will lead to the convenient report that is shown below +### :clap: Приклад правильного виконання: Структурування набору з назвою блоку, що тестується, і сценаріями призведе до зручного звіту, який показано нижче -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з 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", () => {}); }); }); @@ -701,9 +700,9 @@ describe("Transfer service", () => {
    -### :thumbsdown: Anti-pattern Example: A flat list of tests will make it harder for the reader to identify the user stories and correlate failing tests +### :thumbsdown: Приклад антишаблону: Плоский список тестів ускладнить читачеві ідентифікацію історій користувачів і співвіднесення невдалих тестів -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Mocha") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Mocha") ```javascript test("Then the response status should decline", () => {}); @@ -721,14 +720,14 @@ test("Then there should not be a new transfer record", () => {});

    -## ⚪ ️1.13 Other generic good testing hygiene +## ⚪ ️1.13 Інша загальна хороша гігієна тестування -:white_check_mark: **Роби:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +:white_check_mark: **Роби:** Ця публікація зосереджена на порадах щодо тестування, які пов’язані з Node JS або, принаймні, можуть бути представлені на прикладі Node JS. Однак у цьому розділі згруповано кілька добре відомих порад, не повʼязаних з Node -Learn and practice [TDD principles](https://www.sm-cloud.com/book-review-test-driven-development-by-example-a-tldr/) — they are extremely valuable for many but don’t get intimidated if they don’t fit your style, you’re not the only one. Consider writing the tests before the code in a [red-green-refactor style](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html), ensure each test checks exactly one thing, when you find a bug — before fixing write a test that will detect this bug in the future, let each test fail at least once before turning green, start a module by writing a quick and simplistic code that satisfies the test - then refactor gradually and take it to a production grade level, avoid any dependency on the environment (paths, OS, etc) +Навчайтеся і практикуйтеся [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), переконайтеся, що кожен тест перевіряє саме одну річ, якщо ви знайдете помилку — перед виправленням напишіть тест, який виявить цю помилку в майбутньому, дайте кожному тесту принаймні один раз помилитися, перш ніж стати зеленим, запустіть модуль, написавши швидкий і спрощений код, який задовольняє тест - потім поступово рефакторинг і переведіть його до рівня виробництва, уникайте будь-якої залежності від середовища (шляхи, ОС тощо)
    -❌ **Інакше:** You‘ll miss pearls of wisdom that were collected for decades +❌ **Інакше:** Ви пропустите перлини мудрості, які збиралися десятиліттями

    @@ -754,7 +753,7 @@ A word of caution: the TDD argument in the software world takes a typical false-
    -### :clap: Doing It Right Example: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the same way’ +### :clap: Приклад правильного виконання: Cindy Sridharan suggests a rich testing portfolio in her amazing 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’") @@ -786,9 +785,9 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap: Doing It Right Example: Supertest allows approaching Express API in-process (fast and cover many layers) +### :clap: Приклад правильного виконання: Supertest allows approaching Express API in-process (fast and cover many layers) -![](https://img.shields.io/badge/🔧%20Example%20using%20Mocha-blue.svg "Examples with Mocha") +![](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) allows approaching Express API in-process (fast and cover many layers)") @@ -809,7 +808,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap: Doing It Right Example: +### :clap: Приклад правильного виконання: ![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") @@ -832,9 +831,9 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap:Doing It Right Example: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :clap:Приклад правильного виконання: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine -![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Examples with Jest") +![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") ```javascript //the middleware we want to test @@ -875,7 +874,7 @@ Credit: { @@ -944,7 +943,7 @@ it("When querying by site name, get the right site", async () => {
    -### :clap: Doing It Right Example: We can stay within the test, each test acts on its own set of data +### :clap: Приклад правильного виконання: We can stay within the test, each test acts on its own set of data ```javascript it("When updating site name, get successful confirmation", async () => { @@ -1134,7 +1133,7 @@ beforeEach(() => {
    -### :clap: Doing It Right Example: Separating out the UI details +### :clap: Приклад правильного виконання: Separating out the UI details ![](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") @@ -1155,7 +1154,7 @@ test("When users-list is flagged to show only VIP, should display only VIP membe
    -### :thumbsdown: Anti-Pattern Example: Assertion mix UI details and data +### :thumbsdown: Приклад антишаблону: Assertion mix UI details and data ```javascript test("When flagging to show only VIP, should display only VIP members", () => { @@ -1188,7 +1187,7 @@ test("When flagging to show only VIP, should display only VIP members", () => {
    -### :clap: Doing It Right Example: Querying an element using a dedicated attribute for testing +### :clap: Приклад правильного виконання: Querying an element using a dedicated attribute for testing ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") @@ -1217,7 +1216,7 @@ test("Whenever no data is passed to metric, show 0 as default", () => {
    -### :thumbsdown: Anti-Pattern Example: Relying on CSS attributes +### :thumbsdown: Приклад антишаблону: Relying on CSS attributes ```html @@ -1254,7 +1253,7 @@ With all that said, a word of caution is in order: this technique works for smal
    -### :clap: Doing It Right Example: Working realistically with a fully rendered component +### :clap: Приклад правильного виконання: Working realistically with a fully rendered component ![](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") @@ -1286,7 +1285,7 @@ test("Realistic approach: When clicked to show filters, filters are displayed", }); ``` -### :thumbsdown: Anti-Pattern Example: Mocking the reality with shallow rendering +### :thumbsdown: Приклад антишаблону: Mocking the reality with shallow rendering ```javascript test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { @@ -1323,7 +1322,7 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) +### :clap: Приклад правильного виконання: E2E API that resolves only when the async operations is done (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") @@ -1335,7 +1334,7 @@ cy.wait("@products"); // wait for route to appear // this line will get executed only when the route is ready ``` -### :clap: Doing It Right Example: Testing library that waits for DOM elements +### :clap: Приклад правильного виконання: Testing library that waits for DOM elements ```javascript // @testing-library/dom @@ -1352,7 +1351,7 @@ test("movie title appears", async () => { }); ``` -### :thumbsdown: Anti-Pattern Example: custom sleep code +### :thumbsdown: Приклад антишаблону: custom sleep code ```javascript test("movie title appears", async () => { @@ -1390,7 +1389,7 @@ test("movie title appears", async () => {
    Приклади коду -### :clap: Doing It Right Example: Lighthouse page load inspection report +### :clap: Приклад правильного виконання: Lighthouse page load inspection report ![](/assets/lighthouse2.png "Lighthouse page load inspection report") @@ -1412,7 +1411,7 @@ test("movie title appears", async () => {
    -### :clap: Doing It Right Example: Stubbing or intercepting API calls +### :clap: Приклад правильного виконання: 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") @@ -1476,7 +1475,7 @@ test("When no products exist, show the appropriate message", () => {
    -### :clap: Doing It Right Example: Logging-in before-all and not before-each +### :clap: Приклад правильного виконання: Logging-in before-all and not before-each ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") @@ -1524,7 +1523,7 @@ beforeEach(setUser => () {
    -### :clap: Doing It Right Example: Smoke travelling across all pages +### :clap: Приклад правильного виконання: Smoke travelling across all pages ![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") @@ -1557,7 +1556,7 @@ it("When doing smoke testing over all page, should load them all successfully",
    -### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js +### :clap: Приклад правильного виконання: Describing tests in human-language using cucumber-js ![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") @@ -1578,7 +1577,7 @@ Feature: Twitter new tweet ``` -### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook +### :clap: Приклад правильного виконання: Visualizing our components, their various states and inputs using Storybook ![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") @@ -1602,13 +1601,13 @@ Feature: Twitter new tweet
    -### :thumbsdown: Anti-Pattern Example: A typical visual regression - right content that is served badly +### :thumbsdown: Приклад антишаблону: A typical visual regression - right content that is served badly ![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks")
    -### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots +### :clap: Приклад правильного виконання: Configuring wraith to capture and compare UI snapshots ![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") @@ -1637,7 +1636,7 @@ paths: path: /subscribe ``` -### :clap: Doing It Right Example: Using Applitools to get snapshot comparison and other advanced features +### :clap: Приклад правильного виконання: Using Applitools to get snapshot comparison and other advanced features ![](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") @@ -1690,7 +1689,7 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :clap: Doing It Right Example: Setting up coverage per component (using Jest) +### :clap: Приклад правильного виконання: Setting up coverage per component (using Jest) ![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") @@ -1713,7 +1712,7 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :thumbsdown: Anti-Pattern Example: What’s wrong with this coverage report? +### :thumbsdown: Приклад антишаблону: 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) @@ -1744,7 +1743,7 @@ Knowing that all or most of the mutations were killed gives much higher confiden
    -### :thumbsdown: Anti-Pattern Example: 100% coverage, 0% testing +### :thumbsdown: Приклад антишаблону: 100% coverage, 0% testing ![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") @@ -1764,7 +1763,7 @@ it("Test addNewOrder, don't use such test names", () => {
    -### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :clap: Приклад правильного виконання: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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)") @@ -1785,7 +1784,7 @@ it("Test addNewOrder, don't use such test names", () => {
    -### :thumbsdown: Anti-Pattern Example: A test case full of errors, luckily all are caught by Linters +### :thumbsdown: Приклад антишаблону: A test case full of errors, luckily all are caught by Linters ```javascript describe("Too short description", () => { @@ -1822,7 +1821,7 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    -### :thumbsdown: Anti-Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug +### :thumbsdown: Приклад антишаблону: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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") @@ -1845,7 +1844,7 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com
    -### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: Приклад правильного виконання: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code ```javascript "scripts": { @@ -1905,7 +1904,7 @@ The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for
    -### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :clap: Приклад правильного виконання: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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)") @@ -1925,7 +1924,7 @@ The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for
    -### :clap: Doing It Right Example: +### :clap: Приклад правильного виконання: ```javascript //install license-checker in your CI environment or also locally From fb28fdb58f1653ee74fc1923c968aa7c5a02817d Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Mon, 22 Aug 2022 19:18:10 +0300 Subject: [PATCH 456/502] Update readme.md --- readme.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index f5123694..57a818c0 100644 --- a/readme.md +++ b/readme.md @@ -1,13 +1,8 @@ -## 🎊 April 2022 Announcement: A new edition was just released with 5 new best practices, many more code examples and 4 new language translations - -## 👨‍🏫 Next workshop: Verona, Italy 🇮🇹, April 20th. [Tickets and more info here](https://2022.jsday.it/workshop/nodejs_testing.html)
    - - # 👇 Why this guide can take your testing skills to the next level
    @@ -26,6 +21,8 @@ Start by understanding the ubiquitous testing practices that are the foundation
    +## 🚀 We have an [official Node.js starter - Practica.js](https://github.com/practicajs/practica). Use it to generate a new solution skeleton with testing baked in, Or just it to learn by testing code examples + ### Written By Yoni Goldberg - A JavaScript & Node.js consultant From 09795e10d894cf59b820addc84796f7a5a056f12 Mon Sep 17 00:00:00 2001 From: Serhii Shramko Date: Sat, 3 Sep 2022 14:31:58 +0200 Subject: [PATCH 457/502] feat: implement 2 chapter --- readme-ua.md | 166 +++++++++++++++++++++++++-------------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/readme-ua.md b/readme-ua.md index 56f5fd4b..05ec2ac6 100644 --- a/readme-ua.md +++ b/readme-ua.md @@ -720,7 +720,7 @@ test("Then there should not be a new transfer record", () => {});

    -## ⚪ ️1.13 Інша загальна хороша гігієна тестування +## ⚪ ️1.13 Інша загальна гігієна тестування :white_check_mark: **Роби:** Ця публікація зосереджена на порадах щодо тестування, які пов’язані з Node JS або, принаймні, можуть бути представлені на прикладі Node JS. Однак у цьому розділі згруповано кілька добре відомих порад, не повʼязаних з Node @@ -731,21 +731,20 @@ test("Then there should not be a new transfer record", () => {});

    -# Section 2️⃣: Backend Testing +# Section 2️⃣: Backend тестування -## ⚪ ️2.1 Enrich your testing portfolio: Look beyond unit tests and the pyramid +## ⚪ ️2.1 Збагачуйте своє портфоліо тестування: подивіться за межі модульних тестів і піраміди -:white_check_mark: **Роби:** The [testing pyramid](https://martinfowler.com/bliki/TestPyramid.html), though 10> years old, is a great and relevant model that suggests three testing types and influences most developers’ testing strategy. At the same time, more than a handful of shiny new testing techniques emerged and are hiding in the shadows of the testing pyramid. Given all the dramatic changes that we’ve seen in the recent 10 years (Microservices, cloud, serverless), is it even possible that one quite-old model will suit *all* types of applications? shouldn’t the testing world consider welcoming new testing techniques? +:white_check_mark: **Роби:** [Піраміда тестування](https://martinfowler.com/bliki/TestPyramid.html), Хоча їй понад 10 років, це чудова та релевантна модель, яка пропонує три типи тестування та впливає на стратегію тестування більшості розробників. Водночас з’явилося більше кількох блискучих нових методів тестування, які ховаються в тіні піраміди тестування. Враховуючи всі драматичні зміни, які ми спостерігаємо за останні 10 років (мікросервіси, хмарні сервіси, безсерверний доступ), чи можливо, що одна досить стара модель підійде для *всіх* типів програм? Чи не варто світу тестування розглянути можливість вітати нові методи тестування? -Don’t get me wrong, in 2019 the testing pyramid, TDD and unit tests are still a powerful technique and are probably the best match for many applications. Only like any other model, despite its usefulness, [it must be wrong sometimes](https://en.wikipedia.org/wiki/All_models_are_wrong). For example, consider an IoT application that ingests many events into a message-bus like Kafka/RabbitMQ, which then flow into some data-warehouse and are eventually queried by some analytics UI. Should we really spend 50% of our testing budget on writing unit tests for an application that is integration-centric and has almost no logic? As the diversity of application types increase (bots, crypto, Alexa-skills) greater are the chances to find scenarios where the testing pyramid is not the best match. +Не зрозумійте мене неправильно, у 2019 році піраміда тестування, TDD і модульні тести все ще залишаються потужною технікою та, мабуть, найкраще підходять для багатьох програм. Тільки, як і будь-яка інша модель, попри її корисність, [інколи вона може помилятися](https://en.wikipedia.org/wiki/All_models_are_wrong). Наприклад, розглянемо IoT-додаток, який приймає багато подій у шину повідомлень, як-от Kafka/RabbitMQ, які потім надходять у якесь сховище даних і в кінцевому підсумку запитуються деяким інтерфейсом користувача аналітики. Чи справді ми повинні витрачати 50% нашого бюджету на тестування на написання модульних тестів для програми, яка орієнтована на інтеграцію та майже не містить логіки? Зі збільшенням різноманітності типів додатків (ботів, крипто, навичок Alexa) зростає ймовірність знайти сценарії, де піраміда тестування не найкраще підходить. -It’s time to enrich your testing portfolio and become familiar with more testing types (the next bullets suggest few ideas), mind models like the testing pyramid but also match testing types to real-world problems that you’re facing (‘Hey, our API is broken, let’s write consumer-driven contract testing!’), diversify your tests like an investor that build a portfolio based on risk analysis — assess where problems might arise and match some prevention measures to mitigate those potential risks - -A word of caution: the TDD argument in the software world takes a typical false-dichotomy face, some preach to use it everywhere, others think it’s the devil. Everyone who speaks in absolutes is wrong :] +Настав час збагатити ваше портфоліо тестування та ознайомитися з більшою кількістю типів тестування (наступні пункти пропонують кілька ідей), моделями розуму, як-от піраміда тестування, а також зіставити типи тестування з реальними проблемами, з якими ви стикаєтесь («Гей, наш API зламано, напишімо тестування контрактів, орієнтоване на споживача!'), диверсифікуйте свої тести, як інвестор, який створює портфель на основі аналізу ризиків — оцініть, де можуть виникнути проблеми, і підберіть деякі запобіжні заходи для помʼякшення цих потенційних ризиків +Застереження: аргумент TDD у світі програмного забезпечення приймає типове обличчя хибної дихотомії, деякі проповідують використовувати його всюди, інші вважають, що це диявол. Кожен, хто говорить в абсолюті, помиляється :]
    -❌ **Інакше:** You’re going to miss some tools with amazing ROI, some like Fuzz, lint, and mutation can provide value in 10 minutes +❌ **Інакше:** Ви пропустите деякі інструменти з неймовірною рентабельністю інвестицій, деякі, як-от Fuzz, lint і mutation, можуть надати цінність за 10 хвилин
    @@ -753,31 +752,31 @@ A word of caution: the TDD argument in the software world takes a typical false-
    -### :clap: Приклад правильного виконання: Cindy Sridharan suggests a rich testing portfolio in her amazing post ‘Testing Microservices — the same way’ +### :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/bp-12-rich-testing.jpeg "Cindy Sridharan пропонує багате портфоліо тестування у своїй дивовижній публікації «Тестування мікросервісів — таким же чином»") -☺️Example:
    [YouTube: “Beyond Unit Tests: 5 Shiny Node.JS Test Types (2018)” (Yoni Goldberg)](https://www.youtube.com/watch?v=-2zP494wdUY&feature=youtu.be) +☺️Приклад: [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 "A test name that constitutes 3 parts") +![alt text](assets/bp-12-Yoni-Goldberg-Testing.jpeg "Назва тесту, яка складається з 3 частин")


    -## ⚪ ️2.2 Component testing might be your best affair +## ⚪ ️2.2 Тестування компонентів може бути вашим найкращим заходом -:white_check_mark: **Роби:** Each unit test covers a tiny portion of the application and it’s expensive to cover the whole, whereas end-to-end testing easily covers a lot of ground but is flaky and slower, why not apply a balanced approach and write tests that are bigger than unit tests but smaller than end-to-end testing? Component testing is the unsung song of the testing world — they provide the best from both worlds: reasonable performance and a possibility to apply TDD patterns + realistic and great coverage. +:white_check_mark: **Роби:** Кожен модульний тест охоплює невелику частину програми, і це дорого, щоб охопити цілу, тоді як наскрізне(end-to-end) тестування легко охоплює велику частину, але є нестабільним і повільним, чому б не застосувати збалансований підхід і написати тести, більші за модульні тести, але менші, ніж наскрізне тестування? Тестування компонентів – це неоспівана пісня світу тестування — вони забезпечують найкраще з обох світів: прийнятну продуктивність і можливість застосовувати шаблони TDD + реалістичне та велике покриття. -Component tests focus on the Microservice ‘unit’, they work against the API, don’t mock anything which belongs to the Microservice itself (e.g. real DB, or at least the in-memory version of that DB) but stub anything that is external like calls to other Microservices. By doing so, we test what we deploy, approach the app from outwards to inwards and gain great confidence in a reasonable amount of time. +Тести компонентів зосереджуються на «блоку» мікросервісу, вони працюють проти API, не мокають(mock) нічого, що належить самому мікросервісу (наприклад, справжню БД або принаймні версію цієї БД у пам’яті), але заглушують усе, що є зовнішнім як виклики до інших мікросервісів. Роблячи це, ми перевіряємо те, що розгортаємо, підходимо до програми ззовні всередину та отримуємо велику впевненість за прийнятний проміжок часу. -[We have a full guide that is solely dedicated to writing component tests in the right way](https://github.com/testjavascript/nodejs-integration-tests-best-practices) +[У нас є повний посібник, присвячений виключно правильному написанню компонентних тестів](https://github.com/testjavascript/nodejs-integration-tests-best-practices)
    -❌ **Інакше:** You may spend long days on writing unit tests to find out that you got only 20% system coverage +❌ **Інакше:** Ви можете витратити довгі дні на написання модульних тестів, щоб дізнатися, що ви отримали лише 20% покриття системи
    @@ -785,22 +784,22 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap: Приклад правильного виконання: Supertest allows approaching Express API in-process (fast and cover many layers) +### :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) allows approaching Express API in-process (fast and cover many layers)") +![alt text](assets/bp-13-component-test-yoni-goldberg.png " [Supertest](https://www.npmjs.com/package/supertest) дозволяє підходити до Express API в процесі (швидко та охоплює багато рівнів)")


    -## ⚪ ️2.3 Ensure new releases don’t break the API using contract tests +## ⚪ ️2.3 Переконайтеся, що нові релізи не порушують API, використовуючи тести контракту -:white_check_mark: **Роби:** So your Microservice has multiple clients, and you run multiple versions of the service for compatibility reasons (keeping everyone happy). Then you change some field and ‘boom!’, some important client who relies on this field is angry. This is the Catch-22 of the integration world: It’s very challenging for the server side to consider all the multiple client expectations — On the other hand, the clients can’t perform any testing because the server controls the release dates. There are a spectrum of techniques that can mitigate the contract problem, some are simple, other are more feature-rich and demand a steeper learning curve. In a simple and recommended approach, the API provider publishes npm package with the API typing (e.g. JSDoc, TypeScript). Then the consumers can fetch this library and benefit from codign time intellisense and validation. A fancier approach it to use [PACT](https://docs.pact.io/) which were born to formalize this process with a very disruptive approach — not the server defines the test plan of itself rather the client defines the tests of the… server! PACT can record the client expectation and put in a shared location, “broker”, so the server can pull the expectations and run on every build using PACT library to detect broken contracts — a client expectation that is not met. By doing so, all the server-client API mismatches are caught early during build/CI and might save you a great deal of frustration +:white_check_mark: **Роби:** Отже, ваш мікросервіс має кілька клієнтів, і ви запускаєте кілька версій сервісу з міркувань сумісності (щоб усі були задоволені). Потім ви змінюєте якесь поле і «бум!», якийсь важливий клієнт, який покладається на це поле, сердиться. Це 22-й підступ світу інтеграції: серверній стороні дуже складно врахувати всі численні очікування клієнтів — З іншого боку, клієнти не можуть проводити жодного тестування, оскільки сервер контролює дати випуску. Існує цілий спектр методів, які можуть помʼякшити проблему контракту, деякі з них прості, інші є багатшими на функції та вимагають крутішої кривої навчання. У простому та рекомендованому підході постачальник API публікує пакет npm із типом API (наприклад, JSDoc, TypeScript). Тоді споживачі зможуть отримати цю бібліотеку та отримати вигоду від аналізу часу кодування та перевірки. Вигадливіший підхід — використання [PACT](https://docs.pact.io/), створеного для формалізації цього процесу за допомогою дуже руйнівного підходу —«не сервер сам визначає план тестування, а клієнт визначає тести. PACT може записувати очікування клієнта та розміщувати в спільному місці, «посереднику», щоб сервер міг отримати очікування та запускати кожну збірку за допомогою бібліотеки PACT для виявлення порушених контрактів — очікування клієнта, яке не відповідають. Завдяки цьому всі невідповідності API сервера та клієнта виявляються на ранній стадії збирання/CI, що може позбавити вас від розчарування
    -❌ **Інакше:** The alternatives are exhausting manual testing or deployment fear +❌ **Інакше:** Альтернативами є виснажливе ручне тестування або страх розгортання
    @@ -810,7 +809,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API, ### :clap: Приклад правильного виконання: -![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Examples with PACT") +![](https://img.shields.io/badge/🔧%20Example%20using%20PACT-blue.svg "Приклад з PACT") ![alt text](assets/bp-14-testing-best-practices-contract-flow.png) @@ -818,12 +817,12 @@ Component tests focus on the Microservice ‘unit’, they work against the API,

    -## ⚪ ️ 2.4 Test your middlewares in isolation +## ⚪ ️ 2.4 Тестуйте мідлвари(middlewares) окремо -:white_check_mark: **Роби:** Many avoid Middleware testing because they represent a small portion of the system and require a live Express server. Both reasons are wrong — Middlewares are small but affect all or most of the requests and can be tested easily as pure functions that get {req,res} JS objects. To test a middleware function one should just invoke it and spy ([using Sinon for example](https://www.npmjs.com/package/sinon)) on the interaction with the {req,res} objects to ensure the function performed the right action. The library [node-mock-http](https://www.npmjs.com/package/node-mocks-http) takes it even further and factors the {req,res} objects along with spying on their behavior. For example, it can assert whether the http status that was set on the res object matches the expectation (See example below) +: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, відповідає очікуванням (див. приклад нижче)
    -❌ **Інакше:** A bug in Express middleware === a bug in all or most requests +❌ **Інакше:** Помилка в мідлварі Express === помилка в усіх або більшості запитів
    @@ -831,7 +830,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API,
    -### :clap:Приклад правильного виконання: Testing middleware in isolation without issuing network calls and waking-up the entire Express machine +### :clap:Приклад правильного виконання: Тестування мідлвари в ізоляції без здійснення мережевих викликів і пробудження всієї машини Express ![](https://img.shields.io/badge/🔧%20Example%20using%20Jest-blue.svg "Приклади з Jest") @@ -839,7 +838,7 @@ Component tests focus on the Microservice ‘unit’, they work against the API, //the middleware we want to test const unitUnderTest = require("./middleware"); const httpMocks = require("node-mocks-http"); -//Jest syntax, equivelant to describe() & it() in Mocha +// Синтаксис Jest, еквівалентний describe() і it() у Mocha test("A request without authentication header, should return http status 403", () => { const request = httpMocks.createRequest({ method: "GET", @@ -858,15 +857,15 @@ test("A request without authentication header, should return http status 403", (

    -## ⚪ ️2.5 Measure and refactor using static analysis tools +## ⚪ ️2.5 Вимірювання та ре факторинг за допомогою інструментів статичного аналізу -:white_check_mark: **Роби:** Using static analysis tools helps by giving objective ways to improve code quality and keep your code maintainable. You can add static analysis tools to your CI build to abort when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity) and follow the history and progress of code issues. Two examples of tools you can use are [SonarQube](https://www.sonarqube.org/) (4,900+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (2,000+ [stars](https://github.com/codeclimate/codeclimate)) +: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)
    -❌ **Інакше:** With poor code quality, bugs and performance will always be an issue that no shiny new library or state of the art features can fix +❌ **Інакше:** З низькою якістю коду помилки та продуктивність завжди будуть проблемою, яку не зможе вирішити жодна блискуча нова бібліотека чи сучасні функції
    @@ -874,22 +873,22 @@ Credit:

    -## ⚪ ️ 2.6 Check your readiness for Node-related chaos +## ⚪ ️ 2.6 Перевірте свою готовність до хаосу, пов’язаного з Node -:white_check_mark: **Роби:** Weirdly, most software testings are about logic & data only, but some of the worst things that happen (and are really hard to mitigate) are infrastructural issues. For example, did you ever test what happens when your process memory is overloaded, or when the server/process dies, or does your monitoring system realizes when the API becomes 50% slower?. To test and mitigate these type of bad things — [Chaos engineering](https://principlesofchaos.org/) was born by Netflix. It aims to provide awareness, frameworks and tools for testing our app resiliency for chaotic issues. For example, one of its famous tools, [the chaos monkey](https://github.com/Netflix/chaosmonkey), randomly kills servers to ensure that our service can still serve users and not relying on a single server (there is also a Kubernetes version, [kube-monkey](https://github.com/asobti/kube-monkey), that kills pods). All these tools work on the hosting/platform level, but what if you wish to test and generate pure Node chaos like check how your Node process copes with uncaught errors, unhandled promise rejection, v8 memory overloaded with the max allowed of 1.7GB or whether your UX remains satisfactory when the event loop gets blocked often? to address this I’ve written, [node-chaos](https://github.com/i0natan/node-chaos-monkey) (alpha) which provides all sort of Node-related chaotic acts +: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
    -❌ **Інакше:** No escape here, Murphy’s law will hit your production without mercy +❌ **Інакше:** Тут немає виходу, закон Мерфі вдарить по вашому виробництву без жалю
    @@ -897,20 +896,20 @@ Credit:

    -## ⚪ ️2.7 Avoid global test fixtures and seeds, add data per-test +## ⚪ ️2.7 Уникайте глобальних фікстур сідів, додавайте данні для кожного тесту -:white_check_mark: **Роби:** Going by the golden rule (bullet 0), each test should add and act on its own set of DB rows to prevent coupling and easily reason about the test flow. In reality, this is often violated by testers who seed the DB with data before running the tests (also known as ‘test fixture’) for the sake of performance improvement. While performance is indeed a valid concern — it can be mitigated (see “Component testing” bullet), however, test complexity is a much painful sorrow that should govern other considerations most of the time. Practically, make each test case explicitly add the DB records it needs and act only on those records. If performance becomes a critical concern — a balanced compromise might come in the form of seeding the only suite of tests that are not mutating data (e.g. queries) +:white_check_mark: **Роби:** Дотримуючись золотого правила (Розділ 0), кожен тест повинен додавати власний набір рядків БД і діяти на них, щоб запобігти зчепленню та легко обґрунтувати потік тесту. Насправді це часто порушується тестувальниками, які заповнюють БД даними перед запуском тестів (також відомих як «test fixture») заради підвищення продуктивності. Хоча продуктивність справді є обґрунтованою проблемою —«її можна пофіксити (див. пункт «Тестування компонентів»), однак складність тесту є дуже болісним сумом, який у більшості випадків повинен керувати іншими міркуваннями. На практиці зробіть так, щоб кожен тестовий приклад явно додавав необхідні записи БД і діяв лише з цими записами. Якщо продуктивність стає критичною проблемою — збалансований компроміс може прийти у формі заповнення єдиного набору тестів, які не змінюють дані (наприклад, запити)
    -❌ **Інакше:** Few tests fail, a deployment is aborted, our team is going to spend precious time now, do we have a bug? let’s investigate, oh no — it seems that two tests were mutating the same seed data +❌ **Інакше:** Кілька тестів зазнають невдач, розгортання перервано, наша команда витрачатиме дорогоцінний час зараз, у нас є помилка? дослідім, о ні — схоже, що два тести мутували однакові вихідні дані
    @@ -918,36 +917,36 @@ Credit:
    { - //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework + //додавання даних про сайти та адмінів до нашої БД. Де дані? назовні. У якомусь зовнішньому фреймворку json або міграції 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 :[ + expect(siteToCheck.name).to.be.equal("Portal"); //Невдача! Попередній тест змінив назву :[ }); ```
    -### :clap: Приклад правильного виконання: We can stay within the test, each test acts on its own set of data +### :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" }); @@ -960,14 +959,14 @@ it("When updating site name, get successful confirmation", async () => {
    -## ⚪ ️2.8 Choose a clear data clean-up strategy: After-all (recommended) or after-each +## ⚪ ️2.8 Виберіть чітку стратегію очищення даних: після всього (рекомендовано) або після кожного -:white_check_mark: **Роби:** The timing when the tests clean the database determines the way the tests are being written. The two most viable options are cleaning after all the tests vs cleaning after every single test. Choosing the latter option, cleaning after every single test guarantees clean tables and builds convenient testing perks for the developer. No other records exist when the test starts, one can have certainty which data is being queried and even might be tempted to count rows during assertions. This comes with severe downsides: When running in a multi-process mode, tests are likely to interfere with each other. While process-1 purges tables, at the very moment process-2 queries for data and fail (because the DB was suddenly deleted by process-1). On top of this, It's harder to troubleshoot failing tests - Visiting the DB will show no records. +:white_check_mark: **Роби:** Час, коли тести очищають базу даних, визначає спосіб написання тестів. Два найбільш життєздатних варіанти: очищення після всіх тестів і очищення після кожного тесту. Вибираючи останній варіант, очищення після кожного окремого тесту гарантує чисті таблиці та створює зручні переваги тестування для розробника. На момент початку тесту не існує інших записів, можна бути впевненим, які дані запитуються, і навіть може виникнути спокуса підрахувати рядки під час тверджень. Це має серйозні недоліки: під час роботи в багатопроцесному режимі тести можуть заважати один одному. Поки процес-1 очищає таблиці, у той самий момент процес-2 запитує дані та зазнає помилки (оскільки БД раптово видалено процесом-1). Крім того, важче усунути невдалі тести – відвідування БД не покаже жодних записів. -The second option is to clean up after all the test files have finished (or even daily!). This approach means that the same DB with existing records serves all the tests and processes. To avoid stepping on each other's toes, the tests must add and act on specific records that they have added. Need to check that some record was added? Assume that there are other thousands of records and query for records that were added explicitly. Need to check that a record was deleted? Can't assume an empty table, check that this specific record is not there. This technique brings few powerful gains: It works natively in multi-process mode, when a developer wishes to understand what happened - the data is there and not deleted. It also increases the chance of finding bugs because the DB is full of records and not artificially empty. [See the full comparison table here](https://github.com/testjavascript/nodejs-integration-tests-best-practices/blob/master/graphics/db-clean-options.png). +Другий варіант — очищення після завершення всіх тестових файлів (або навіть щодня!). Такий підхід означає, що та сама БД з наявними записами обслуговує всі тести та процеси. Щоб не наступати один одному на ноги, тести повинні додавати та діяти на основі конкретних записів, які вони додали. Потрібно перевірити, чи додано якийсь запис? Припустімо, що є інші тисячі записів і запит на записи, які були додані явно. Потрібно перевірити, чи видалено запис? Неможливо припустити, що таблиця порожня, перевірте, чи немає цього конкретного запису. Ця техніка дає кілька значних переваг: вона працює нативно в багатопроцесному режимі, коли розробник хоче зрозуміти, що сталося – дані є, а не видаляються. Це також збільшує ймовірність виявлення помилок, оскільки БД заповнена записами, а не штучно порожня. [Перегляньте повну порівняльну таблицю тут](https://github.com/testjavascript/nodejs-integration-tests-best-practices/blob/master/graphics/db-clean-options.png).
    -❌ **Інакше:** Without a strategy to separate records or clean - Tests will step on each other toes; Using transactions will work only for relational DB and likely to get complicated once there are inner transactions +❌ **Інакше:** Без стратегії розділення записів або очищення - Тести будуть наступати один на одного; Використання транзакцій працюватиме лише для реляційної БД і, ймовірно, ускладниться, коли з’являться внутрішні транзакції
    @@ -975,11 +974,11 @@ The second option is to clean up after all the test files have finished (or even
    -### :clap: Cleaning after ALL the tests. Not neccesserily after every run. The more data we have while the tests are running - The more it resembles the production perks +### :clap: Прибирання після ВСІХ перевірок. Не обов'язково після кожного запуску. Чим більше даних ми маємо під час виконання тестів, тим більше це нагадує переваги виробництва ```javascript - // After-all clean up (recommended) -// global-teardown.js + // Після всього прибрати (рекомендовано) + // global-teardown.js module.exports = async () => { // ... if (Math.ceil(Math.random() * 10) === 10) { @@ -992,12 +991,12 @@ module.exports = async () => {
    -## ⚪ ️2.9 Isolate the component from the world using HTTP interceptor +## ⚪ ️2.9 Ізолюйте компонент від світу за допомогою перехоплювача HTTP -:white_check_mark: **Роби:** Isolate the component under test by intercepting any outgoing HTTP request and providing the desired response so the collaborator HTTP API won't get hit. Nock is a great tool for this mission as it provides a convenient syntax for defining external services behavior. Isolation is a must to prevent noise and slow performance but mostly to simulate various scenarios and responses - A good flight simulator is not about painting clear blue sky rather bringing safe storms and chaos. This is reinforced in a Microservice architecture where the focus should always be on a single component without involving the rest of the world. Though it's possible to simulate external service behavior using test doubles (mocking), it's preferable not to touch the deployed code and act on the network level to keep the tests pure black-box. The downside of isolation is not detecting when the collaborator component changes and not realizing misunderstandings between the two services - Make sure to compensate for this using a few contract or E2E tests +:white_check_mark: **Роби:** Ізолюйте тестований компонент, перехоплюючи будь-який вихідний HTTP-запит і надаючи потрібну відповідь, щоб HTTP API співавтора не постраждав. Nock є чудовим інструментом для цієї місії, оскільки він забезпечує зручний синтаксис для визначення поведінки зовнішніх служб. Ізоляція є обов’язковою для запобігання шуму та повільної продуктивності, але здебільшого для імітації різних сценаріїв і реакцій. Хороший симулятор польоту — це не малювання чистого блакитного неба, а створення безпечних штормів і хаосу. Це посилюється в архітектурі мікросервісу, де фокус завжди має бути зосереджений на одному компоненті без залучення решти світу. Хоча можна імітувати поведінку зовнішнього сервісу за допомогою повторюваних тестів (mocking), бажано не торкатися розгорнутого коду та діяти на рівні мережі, щоб тести залишалися чисто чорною скринькою. Негативною стороною ізоляції є відсутність виявлення змін компонента співавтора та непорозуміння між двома службами. Обов’язково компенсуйте це за допомогою кількох контрактів або тестів E2E
    -❌ **Інакше:** Some services provide a fake version that can be deployed by the caller locally, usually using Docker - This will ease the setup and boost the performance but won't help with simulating various responses; Some services provide 'sandbox' environment, so the real service is hit but no costs or side effects are triggered - This will cut down the noise of setting up the 3rd party service but also won't allow simulating scenarios +❌ **Інакше:** Деякі служби надають фальшиву версію, яку абонент може розгорнути локально, зазвичай за допомогою Docker. Це полегшить налаштування та підвищить продуктивність, але не допоможе з симуляцією різних відповідей; Деякі служби надають середовище «пісочниці», тому справжня служба зазнає удару, але не спричиняє жодних витрат або побічних ефектів. Це зменшить шум під час налаштування сторонньої служби, але також не дозволить імітувати сценарії
    @@ -1005,27 +1004,28 @@ module.exports = async () => {
    -### :clap: Preventing network calls to externous components allows simulating scnearios and minimizing the noise +### :clap: Запобігання мережевим викликам зовнішніх компонентів дозволяє імітувати сценарії та мінімізувати шум ```javascript -// Intercept requests for 3rd party APIs and return a predefined response +// Перехоплювати запити для сторонніх API і повертати попередньо визначену відповідь beforeEach(() => { nock('http://localhost/user/').get(`/1`).reply(200, { id: 1, name: 'John', }); -});``` +}); +```
    -## ⚪ ️2.10 Test the response schema, mostly when there are auto-generated fields +## ⚪ ️2.10 Перевірте схему відповіді, коли є автоматично згенеровані поля -:white_check_mark: **Роби:** When it is impossible to assert for specific data, check for mandatory field existence and types. Sometimes, the response contains important fields with dynamic data that can't be predicted when writing the test, like dates and incrementing numbers. If the API contract promises that these fields won't be null and hold the right types, it's imperative to test it. Most assertion libraries support checking types. If the response is small, check the return data and type together within the same assertion (see code example). One more option is to verify the entire response against an OpenAPI doc (Swagger). Most test runners have community extensions that validate API responses against their documentation. +:white_check_mark: **Роби:** Якщо неможливо підтвердити певні дані, перевірте наявність і типи обов’язкових полів. Іноді відповідь містить важливі поля з динамічними даними, які неможливо передбачити під час написання тесту, як-от дати та числа, що збільшуються. Якщо договір API обіцяє, що ці поля не будуть нульовими і містять правильні типи, обов’язково перевірте це. Більшість бібліотек тверджень підтримують типи перевірки. Якщо відповідь невелика, перевірте дані, що повертаються, і введіть разом в одному твердженні (див. приклад коду). Ще один варіант — перевірити всю відповідь за документом OpenAPI (Swagger). Більшість тестувальників мають розширення спільноти, які перевіряють відповіді API на їхню документацію.
    -❌ **Інакше:** Although the code/API caller relies on some field with dynamic data (e.g., ID, date), it will not come in return and break the contract +❌ **Інакше:** Хоча програма виклику коду/API покладається на деякі поля з динамічними даними (наприклад, ідентифікатор, дата), вона не прийде у відповідь і не порушить контракт
    @@ -1033,16 +1033,16 @@ beforeEach(() => {
    -### :clap: Asserting that fields with dynamic value exist and have the right type +### :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 + id: expect.any(Number), // Будь-яке число задовольняє цей тест mode: 'approved', }, }); @@ -1053,14 +1053,14 @@ beforeEach(() => {
    -## ⚪ ️2.12 Check integrations corner cases and chaos +## ⚪ ️2.12 Перевірте крайні випадки інтеграції та хаос -:white_check_mark: **Роби:** When checking integrations, go beyond the happy and sad paths. Check not only errored responses (e.g., HTTP 500 error) but also network-level anomalies like slow and timed-out responses. This will prove that the code is resilient and can handle various network scenarios like taking the right path after a timeout, has no fragile race conditions, and contains a circuit breaker for retries. Reputable interceptor tools can easily simulate various network behaviors like hectic service that occasionally fail. It can even realize when the default HTTP client timeout value is longer than the simulated response time and throw a timeout exception right away without waiting +:white_check_mark: **Роби:** Перевіряючи інтеграції, виходьте за рамки щасливих і сумних шляхів. Перевіряйте не лише відповіді з помилками (наприклад, помилка HTTP 500), а й аномалії на рівні мережі, як-от повільні відповіді та відповіді, що закінчилися. Це доведе, що код стійкий і може обробляти різні мережеві сценарії, як-от вибір правильного шляху після тайм-ауту, відсутність крихких умов змагання та містить автоматичний вимикач для повторних спроб. Інструменти перехоплення з авторитетним досвідом можуть легко імітувати різні поведінки мережі, як-от неспокійне обслуговування, яке час від часу дає збій. Він навіть може зрозуміти, коли значення тайм-ауту HTTP-клієнта за замовчуванням перевищує змодельований час відповіді, і відразу, не чекаючи, викликати виняток часу очікування
    -❌ **Інакше:** All your tests pass, it's only the production who will crash or won't report errors correctly when 3rd parties send excpetional responses +❌ **Інакше:** Усі ваші тести проходять успішно, тільки виробництво буде аварійно або неправильно повідомляти про помилки, коли треті сторони надсилатимуть виняткові відповіді
    @@ -1068,11 +1068,11 @@ beforeEach(() => {
    -### :clap: Ensuring that on network failures, the circuit breaker can save the day +### :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') @@ -1086,10 +1086,10 @@ beforeEach(() => { mode: 'approved', }; - //Act + // Дія const response = await axiosAPIClient.post('/order', orderToAdd); - //Assert + // Стверджування expect(response.status).toBe(200); }); ``` @@ -1099,19 +1099,19 @@ beforeEach(() => {
    -## ⚪ ️2.13 Test the five potential outcomes +## ⚪ ️2.13 Перевірте п’ять потенційних результатів -:white_check_mark: **Роби:** When planning your tests, consider covering the five typical flow's outputs. When your test is triggering some action (e.g., API call), a reaction is happening, something meaningful occurs and calls for testing. Note that we don't care about how things work. Our focus is on outcomes, things that are noticeable from the outside and might affect the user. These outcomes/reactions can be put in 5 categories: +:white_check_mark: **Роби:** Плануючи свої тести, подумайте про охоплення п’яти типових виходів потоку. Коли ваш тест запускає певну дію (наприклад, виклик API), відбувається реакція, відбувається щось значуще та вимагає тестування. Зауважте, що нам байдуже, як все працює. Ми зосереджені на результатах, речах, які помітні ззовні та можуть вплинути на користувача. Ці результати/реакції можна розділити на 5 категорій: -• Response - The test invokes an action (e.g., via API) and gets a response. It's now concerned with checking the response data correctness, schema, and HTTP status +• Відповідь - Тест викликає дію (наприклад, через API) і отримує відповідь. Тепер він займається перевіркою правильності даних відповіді, схеми та статусу HTTP -• A new state - After invoking an action, some **publicly accessible** data is probably modified +• Новий стан - Після виклику дії деякі **загальнодоступні** дані, ймовірно, змінюються -• External calls - After invoking an action, the app might call an external component via HTTP or any other transport. For example, a call to send SMS, email or charge a credit card +• Зовнішні виклики - Після виклику дії програма може викликати зовнішній компонент через HTTP або будь-який інший транспорт. Наприклад, дзвінок для відправки SMS, електронної пошти або стягнення коштів з кредитної картки -• Message queues - The outcome of a flow might be a message in a queue +• Message queues - Результатом потоку може бути повідомлення в черзі -• Observability - Some things must be monitored, like errors or remarkable business events. When a transaction fails, not only we expect the right response but also correct error handling and proper logging/metrics. This information goes directly to a very important user - The ops user (i.e., production SRE/admin) +• Спостережливість - Деякі речі необхідно відстежувати, як-от помилки чи значні ділові події. Коли транзакція зазнає невдачі, ми очікуємо не лише правильної відповіді, але й правильної обробки помилок і належного журналювання/метрик. Ця інформація надходить безпосередньо до дуже важливого користувача – користувача ops (SRE/адміністратора)
    From 0044847cca0bdb00ba204ad4d49b3362a6e17b99 Mon Sep 17 00:00:00 2001 From: Serhii Shramko Date: Sat, 3 Sep 2022 14:38:56 +0200 Subject: [PATCH 458/502] docs: add ukrainian links --- readme-es.md | 1 + readme-fr.md | 3 ++- readme-pl.md | 1 + readme-pt-br.md | 1 + readme-ua.md | 18 +++++++++--------- readme-zh-TW.md | 3 ++- readme.md | 1 + 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/readme-es.md b/readme-es.md index 3325f51b..920b71d9 100644 --- a/readme-es.md +++ b/readme-es.md @@ -35,6 +35,7 @@ Empieza por comprender las técnicas de testing ubicuas que son la base de cualq - 🇵🇱[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 💜

    diff --git a/readme-fr.md b/readme-fr.md index fb8011e8..e03b122f 100644 --- a/readme-fr.md +++ b/readme-fr.md @@ -35,6 +35,7 @@ Commence par comprendre les pratiques de tests omniprésentes qui sont à la bas - 🇵🇱[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 💜

    @@ -1963,4 +1964,4 @@ Merci à ces merveilleuses personnes qui ont contribué à ce repo! - \ No newline at end of file + diff --git a/readme-pl.md b/readme-pl.md index 499f5e9d..019ec2c4 100644 --- a/readme-pl.md +++ b/readme-pl.md @@ -33,6 +33,7 @@ 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) +- 🇺🇦[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 💜

    diff --git a/readme-pt-br.md b/readme-pt-br.md index e127ab7d..225e7410 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 💜 diff --git a/readme-ua.md b/readme-ua.md index 05ec2ac6..86019764 100644 --- a/readme-ua.md +++ b/readme-ua.md @@ -28,15 +28,15 @@ ### Переклади - читайте своєю мовою -- 🇨🇳[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) -- 🇺🇦[Ukrainian](readme-ua.md) - З люб'язного дозволу [Serhii Shramko](https://github.com/Shramkoweb) -- 🇪🇸[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) +- 🇨🇳[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 💜

    diff --git a/readme-zh-TW.md b/readme-zh-TW.md index 2b20f0f0..d45796a7 100644 --- a/readme-zh-TW.md +++ b/readme-zh-TW.md @@ -38,6 +38,7 @@ - 🇪🇸[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 💜

    @@ -1901,4 +1902,4 @@ Took care to revise, improve, lint and polish all the texts **Role:** Helps keep this project running, and reviews security related practices -**About:** Loves working on Node.js projects and web application security. \ No newline at end of file +**About:** Loves working on Node.js projects and web application security. diff --git a/readme.md b/readme.md index f5123694..f3d9bb03 100644 --- a/readme.md +++ b/readme.md @@ -45,6 +45,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇫🇷[French](readme-fr.md) - Courtesy of [Mathilde El Mouktafi](https://github.com/mel-mouk) - 🇯🇵[Japanese (draft)](https://github.com/yuichkun/javascript-testing-best-practices/blob/master/readme-jp.md) - Courtesy of [Yuichi Yogo](https://github.com/yuichkun) and [ryo](https://github.com/kawamataryo) - 🇹🇼[Traditional Chinese](readme-zh-TW.md) - Courtesy of [Yubin Hsu](https://github.com/yubinTW) +- 🇺🇦[Ukrainian](readme-ua.md) - Courtesy of [Serhii Shramko](https://github.com/Shramkoweb) - Want to translate to your own language? please open an issue 💜

    From e68cb0eaca484422440e298fcc16020cab78073c Mon Sep 17 00:00:00 2001 From: Serhii Shramko Date: Sat, 3 Sep 2022 15:17:45 +0200 Subject: [PATCH 459/502] feat: implement 3 chapter --- readme-ua.md | 188 +++++++++++++++++++++++++-------------------------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/readme-ua.md b/readme-ua.md index 86019764..f73b5783 100644 --- a/readme-ua.md +++ b/readme-ua.md @@ -1117,15 +1117,15 @@ beforeEach(() => {

    -# Section 3️⃣: Frontend Testing +# Section 3️⃣: Frontend Тестування -## ⚪ ️ 3.1 Separate UI from functionality +## ⚪ ️ 3.1 Відокремте інтерфейс від функціональності -:white_check_mark: **Роби:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI +:white_check_mark: **Роби:** Коли ви зосереджуєтеся на тестуванні логіки компонентів, деталі інтерфейсу користувача стають шумом, який слід виділити, щоб ваші тести могли зосередитися на чистих даних. На практиці витягуйте потрібні дані з розмітки абстрактним способом, який не надто пов’язаний із графічною реалізацією, затверджуйте лише чисті дані (на відміну від графічних деталей HTML/CSS) і вимикайте анімацію, яка сповільнюється. У вас може виникнути спокуса уникнути візуалізації та протестувати лише задню частину інтерфейсу користувача (наприклад, служби, дії, магазин), але це призведе до вигаданих тестів, які не схожі на реальність, і не виявлять випадків, коли правильні дані навіть не потрапляє в інтерфейс користувача
    -❌ **Інакше:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation +❌ **Інакше:** Чисті обчислені дані вашого тесту можуть бути готові через 10 мс, але тоді весь тест триватиме 500 мс (100 тестів = 1 хв) через деяку фантастичну та нерелевантну анімацію
    @@ -1133,9 +1133,9 @@ beforeEach(() => {
    -### :clap: Приклад правильного виконання: Separating out the UI details +### :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") +![](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", () => { @@ -1145,7 +1145,7 @@ test("When users-list is flagged to show only VIP, should display only VIP membe // Act const { getAllByTestId } = render(); - // Assert - Extract the data from the UI first + // 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 @@ -1154,7 +1154,7 @@ test("When users-list is flagged to show only VIP, should display only VIP membe
    -### :thumbsdown: Приклад антишаблону: Assertion mix UI details and data +### :thumbsdown: Приклад антишаблону: Деталі та дані інтерфейсу користувача змішані ```javascript test("When flagging to show only VIP, should display only VIP members", () => { @@ -1164,7 +1164,7 @@ test("When flagging to show only VIP, should display only VIP members", () => { // Act const { getAllByTestId } = render(); - // Assert - Mix UI & data in assertion + // Assert - Змішуйте інтерфейс і дані в твердженні expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); }); ``` @@ -1173,13 +1173,13 @@ test("When flagging to show only VIP, should display only VIP members", () => {

    -## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change +## ⚪ ️ 3.2 Запит елементів HTML на основі атрибутів, які навряд чи зміняться -:white_check_mark: **Роби:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +:white_check_mark: **Роби:** Виконуйте запит до HTML-елементів на основі атрибутів, які, ймовірно, переживуть графічні зміни, на відміну від селекторів CSS і міток форм. Якщо призначений елемент не має таких атрибутів, створіть спеціальний тестовий атрибут, наприклад «test-id-submit-button». Виконання цього шляху не тільки гарантує, що ваші функціональні/логічні тести ніколи не зламаються через зміни зовнішнього вигляду, але також стає зрозумілим для всієї команди, що цей елемент і атрибут використовуються тестами, і їх не слід видаляти
    -❌ **Інакше:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **Інакше:** Ви хочете перевірити функціональність входу, яка охоплює багато компонентів, логіку та служби, все налаштовано ідеально – заглушки, шпигуни, виклики Ajax ізольовані. Все здається ідеальним. Тоді тест не вдається, тому що дизайнер змінив клас CSS div з 'thick-border' на 'thin-border'
    @@ -1187,22 +1187,22 @@ test("When flagging to show only VIP, should display only VIP members", () => {
    -### :clap: Приклад правильного виконання: Querying an element using a dedicated attribute for testing +### :clap: Приклад правильного виконання: Запит елемента за допомогою спеціального атрибута для тестування -![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") +![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Приклад з React") ```html -// the markup code (part of React component) +// Код розмітки (React component)

    {value} - +

    ``` ```javascript -// this example is using react-testing-library +// у цьому прикладі використовується бібліотека react-testing-library test("Whenever no data is passed to metric, show 0 as default", () => { // Arrange const metricValue = undefined; @@ -1216,16 +1216,16 @@ test("Whenever no data is passed to metric, show 0 as default", () => {
    -### :thumbsdown: Приклад антишаблону: Relying on CSS attributes +### :thumbsdown: Приклад антишаблону: Покладаючись на атрибути ```html - + {value} - + ``` ```javascript -// this exammple is using enzyme +// Приклад з Еnzyme test("Whenever no data is passed, error metric shows zero", () => { // ... @@ -1237,15 +1237,15 @@ test("Whenever no data is passed, error metric shows zero", () => {
    -## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component +## ⚪ ️ 3.3 За можливості тестуйте з реалістичним і повністю відтвореним компонентом -:white_check_mark: **Роби:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet ['Favour blackbox testing'](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-14-stick-to-black-box-testing-test-only-public-methods)). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +: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)). Якщо один із дочірніх компонентів значно уповільнює (наприклад, анімація) або ускладнює налаштування, подумайте про явну заміну його на підробку -With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children +З огляду на все сказане, варто зробити одне застереження: ця техніка працює для малих/середніх компонентів, які містять дочірні компоненти розумного розміру. Повне відтворення компонента із занадто великою кількістю дочірніх компонентів ускладнить обґрунтування помилок тестування (аналіз першопричини) і може працювати надто повільно. У таких випадках напишіть лише кілька тестів проти цього жирного батьківського компонента та більше тестів щодо його дочірніх компонентів
    -❌ **Інакше:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? +❌ **Інакше:** Під час перегляду внутрішніх компонентів, викликаючи його приватні методи та перевіряючи внутрішній стан, вам доведеться виконати рефакторинг усіх тестів під час рефакторингу реалізації компонентів. Чи справді у вас є можливості для такого рівня обслуговування?
    @@ -1253,9 +1253,9 @@ With all that said, a word of caution is in order: this technique works for smal
    -### :clap: Приклад правильного виконання: Working realistically with a fully rendered component +### :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") +![](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 { @@ -1264,14 +1264,14 @@ class Calendar extends React.Component { render() { return (
    - A filters panel with a button to hide/show filters + /* Панель фільтрів із кнопкою, щоб приховати/показати фільтри */
    ); } } -//Examples use React & Enzyme +// Приклад з React & Enzyme test("Realistic approach: When clicked to show filters, filters are displayed", () => { // Arrange const wrapper = mount(); @@ -1281,11 +1281,11 @@ test("Realistic approach: When clicked to show filters, filters are displayed", // Assert expect(wrapper.text().includes("Choose Filter")); - // This is how the user will approach this element: by text + // Ось як користувач підійде до цього елемента: за текстом }); ``` -### :thumbsdown: Приклад антишаблону: Mocking the reality with shallow rendering +### :thumbsdown: Приклад антишаблону: Мокінг за допомогою неглибокого(shallow) рендерінгу ```javascript test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { @@ -1297,11 +1297,11 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display .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? + // що, якщо ми змінимо ім’я пропу або не передамо нічого відповідного? }); ``` @@ -1309,12 +1309,12 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up +## ⚪ ️ 3.4 Не спіть, використовуйте вбудовану підтримку фреймворків для асинхронних подій. Також спробуйте прискорити процес -:white_check_mark: **Роби:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +: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), можуть допомогти з напів - детерміноване рішення
    -❌ **Інакше:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance +❌ **Інакше:** При тривалому сні тести будуть на порядок повільніше. Під час спроби переходу в режим сну для невеликих чисел тест не вдасться, якщо тестований пристрій не відповів своєчасно. Отже, це зводиться до компромісу між нестабільністю та поганою продуктивністю
    @@ -1322,42 +1322,42 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -### :clap: Приклад правильного виконання: E2E API that resolves only when the async operations is done (Cypress) +### :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") +![](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 -// 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 +// Використання Cypress +cy.get("#show-products").click(); // Навігація +cy.wait("@products"); // зачекайте, поки з'явиться маршрут +// цей рядок буде виконано лише тоді, коли маршрут буде готовий ``` -### :clap: Приклад правильного виконання: Testing library that waits for DOM elements +### :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: Приклад антишаблону: custom sleep code +### :thumbsdown: Приклад антишаблону: спеціальний код сну ```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) { @@ -1366,7 +1366,7 @@ test("movie title appears", async () => { } }, 100); - // wait for appearance and return the element + // дочекатися появи і повернути елемент const movie = await waitForElement(() => getByText("the lion king")); }); ``` @@ -1375,31 +1375,31 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.5 Watch how the content is served over the network +## ⚪ ️ 3.5 Подивіться, як вміст подається в мережі -![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Приклад з Lighthouse") -✅ **Роби:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 +✅ **Роби:** Застосуйте деякий активний монітор, який гарантує оптимізацію завантаження сторінки в реальній мережі - це стосується будь-яких проблем з 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
    -❌ **Інакше:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration +❌ **Інакше:** Мабуть, прикро усвідомлювати, що після такої ретельної роботи над створенням інтерфейсу користувача, проходження 100% функціональних тестів і складного об’єднання – UX жахливий і повільний через неправильну конфігурацію CDN
    Приклади коду -### :clap: Приклад правильного виконання: Lighthouse page load inspection report +### :clap: Приклад правильного виконання: Звіт про перевірку завантаження сторінки Lighthouse -![](/assets/lighthouse2.png "Lighthouse page load inspection report") +![](/assets/lighthouse2.png "Lighthouse звіт завантаження сторінки")

    -## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs +## ⚪ ️ 3.6 Нестабільні та повільні ресурси, як-от серверні API -:white_check_mark: **Роби:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +:white_check_mark: **Роби:** Під час кодування основних тестів (не тестів E2E) уникайте залучення будь-яких ресурсів, які знаходяться поза межами вашої відповідальності та контролю, як-от серверний API, і використовуйте натомість заглушки (тобто test double). На практиці замість реальних мережевих викликів API використовуйте якусь тестову подвійну бібліотеку (наприклад, [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble) тощо) для заглушки відповіді API. Основною перевагою є запобігання нестабільності — API тестування або проміжної обробки за визначенням не є дуже стабільними і час від часу не проходять ваші тести, хоча ВАШ компонент поводиться нормально (виробниче середовище не призначене для тестування, і зазвичай воно гальмує запити). Це дозволить симулювати різні дії API, які повинні керувати поведінкою вашого компонента, наприклад, коли дані не знайдено або випадок, коли API видає помилку. І останнє, але не менш важливе, мережеві виклики значно сповільнюють тести
    @@ -1413,7 +1413,7 @@ test("movie title appears", async () => { ### :clap: Приклад правильного виконання: 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") +![](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 @@ -1451,23 +1451,23 @@ test("When no products exist, show the appropriate message", () => {
    -## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system +## ⚪ ️ 3.7 Майте дуже мало наскрізних тестів(e2e), які охоплюють всю систему -:white_check_mark: **Роби:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +: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, швидше за все, виявить тип проблем, на які вони націлені – помилки розгортання та інтеграції. Бажано запускати їх у середовищі, схожому на виробництво
    -❌ **Інакше:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected +❌ **Інакше:** Інтерфейс користувача може багато вкладати в тестування своєї функціональності, але дуже пізно зрозуміє, що корисне навантаження (схема даних, з якою має працювати інтерфейс користувача) сильно відрізняється від очікуваного.
    -## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials +## ⚪ ️ 3.8 Прискоріть тестування E2E, повторно використовуючи облікові дані для входу -:white_check_mark: **Роби:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)). +: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)).
    -❌ **Інакше:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again +❌ **Інакше:** Надано 200 тестів і припущення, що login=100ms=20 секунд лише для входу знову і знову
    @@ -1475,14 +1475,14 @@ test("When no products exist, show the appropriate message", () => {
    -### :clap: Приклад правильного виконання: Logging-in before-all and not before-each +### :clap: Приклад правильного виконання: Вхід перед усіма, а не перед кожним -![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") +![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Використання Cypress для ілюстраціі ідеї") ```javascript let authenticationToken; -// happens before ALL tests run +// відбувається перед виконанням УСІХ тестів before(() => { cy.request('POST', 'http://localhost:3000/login', { username: Cypress.env('username'), @@ -1494,8 +1494,8 @@ before(() => { }) }) -// happens before EACH test -beforeEach(setUser => () { +// відбувається перед КОЖНИМ тестом +beforeEach(setUser => { cy.visit('/home', { onBeforeLoad (win) { win.localStorage.setItem('token', JSON.stringify(authenticationToken)) @@ -1509,13 +1509,13 @@ beforeEach(setUser => () {
    -## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map +## ⚪ ️ 3.9 Проведіть один тест "smoke" E2E, який просто пройде по карті сайту -:white_check_mark: **Роби:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector +:white_check_mark: **Роби:** Для моніторингу виробництва та перевірки працездатності під час розробки запустіть єдиний тест E2E, який відвідує всі/більшість сторінок сайту та гарантує, що ніхто не зламався. Цей тип тесту забезпечує високу окупність інвестицій, оскільки його дуже легко писати та підтримувати, але він може виявити будь-які збої, включаючи функціональні проблеми, проблеми з мережею та розгортання. Інші стилі перевірки диму та працездатності не такі надійні та вичерпні — деякі оперативні команди просто перевіряють домашню сторінку (виробництво) або розробники, які запускають багато інтеграційних тестів, які не виявляють проблем з пакуванням і браузером. Само собою зрозуміло, що димовий тест не замінює функціональні тести, а лише служить швидким детектором диму
    -❌ **Інакше:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering +❌ **Інакше:** Все може здатися ідеальним, усі тести пройшли, перевірка робочого стану також позитивна, але платіжний компонент мав деякі проблеми з упаковкою, і лише маршрут /Payment не відображається
    @@ -1523,14 +1523,14 @@ beforeEach(setUser => () {
    -### :clap: Приклад правильного виконання: Smoke travelling across all pages +### :clap: Приклад правильного виконання: "Smoke" тест по всіх сторінках -![](https://img.shields.io/badge/🔨%20Example%20using%20Cypress-blue.svg "Using Cypress to illustrate the idea") +![](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", () => { - // exemplified using Cypress but can be implemented easily - // using any E2E suite + // Smoke тест на всіх сторінках + // використовуючи будь-який пакет E2E cy.visit("https://mysite.com/home"); cy.contains("Home"); cy.visit("https://mysite.com/Login"); @@ -1544,11 +1544,11 @@ it("When doing smoke testing over all page, should load them all successfully",
    -## ⚪ ️ 3.10 Expose the tests as a live collaborative document +## ⚪ ️ 3.10 Виставте тести як живий спільний документ -:white_check_mark: **Роби:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. +: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/), дозволяє виставляти компоненти інтерфейсу користувача як графічний каталог, де можна переглядати різні стани кожного компонента (наприклад, візуалізувати сітку без фільтрів). , візуалізуйте цю сітку з кількома рядками або без жодного тощо), подивіться, як це виглядає та як активувати цей стан - це також може сподобатися спеціалістам із продуктів, але здебільшого служить живою документацією для розробників, які використовують ці компоненти. -❌ **Інакше:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value +❌ **Інакше:** Після інвестування найкращих ресурсів у тестування просто шкода не використати ці інвестиції та отримати велику цінність
    @@ -1556,12 +1556,12 @@ it("When doing smoke testing over all page, should load them all successfully",
    -### :clap: Приклад правильного виконання: Describing tests in human-language using cucumber-js +### :clap: Приклад правильного виконання: Опис тестів людською мовою за допомогою cucumber-js -![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") +![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Приклад з Cucumber") ```javascript -// this is how one can describe tests using cucumber: plain language that allows anyone to understand and collaborate +// ось як можна описати тести за допомогою Cucumber: проста мова, яка дозволяє будь-кому розуміти та співпрацювати Feature: Twitter new tweet @@ -1577,9 +1577,9 @@ Feature: Twitter new tweet ``` -### :clap: Приклад правильного виконання: Visualizing our components, their various states and inputs using Storybook +### :clap: Приклад правильного виконання: Візуалізація наших компонентів, їх різних станів і вхідних даних за допомогою Storybook -![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "StoryBook") ![alt text](assets/story-book.jpg "Storybook") @@ -1587,13 +1587,13 @@ Feature: Twitter new tweet

    -## ⚪ ️ 3.11 Detect visual issues with automated tools +## ⚪ ️ 3.11 Виявляйте візуальні проблеми за допомогою автоматизованих інструментів -:white_check_mark: **Роби:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge significant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by eliminating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/CSS changes that led to the issue +: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 зміни, які призвели до проблеми
    -❌ **Інакше:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? +❌ **Інакше:** Наскільки якісною є сторінка з вмістом, яка відображає чудовий вміст (пройдено 100% тестів), завантажується миттєво, але половина області вмісту прихована?
    @@ -1601,13 +1601,13 @@ Feature: Twitter new tweet
    -### :thumbsdown: Приклад антишаблону: A typical visual regression - right content that is served badly +### :thumbsdown: Приклад антишаблону: Типова візуальна регресія – правильний контент, який подається погано -![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") +![alt text](assets/amazon-visual-regression.jpeg "Amazon - зламана сторніка")
    -### :clap: Приклад правильного виконання: Configuring wraith to capture and compare UI snapshots +### :clap: Приклад правильного виконання: Налаштування wraith для захоплення та порівняння знімків інтерфейсу користувача ![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") @@ -1636,9 +1636,9 @@ paths: path: /subscribe ``` -### :clap: Приклад правильного виконання: Using Applitools to get snapshot comparison and other advanced features +### :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") +![](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"; From b40b41246bd4146211ac30fee630684796376e5f Mon Sep 17 00:00:00 2001 From: Yugo Sakamoto Date: Fri, 16 Sep 2022 10:54:09 -0300 Subject: [PATCH 460/502] =?UTF-8?q?Adicionado=20espa=C3=A7o=20entre=20pala?= =?UTF-8?q?vras?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adicionado espaço entre palavras no título da seção 5.3 --- readme-pt-br.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-pt-br.md b/readme-pt-br.md index e127ab7d..b97984c6 100644 --- a/readme-pt-br.md +++ b/readme-pt-br.md @@ -1800,7 +1800,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. From 7f2761135f8085f87951d9d2db0194a79914691c Mon Sep 17 00:00:00 2001 From: Ali Azmoodeh Date: Sat, 17 Sep 2022 20:25:42 +0430 Subject: [PATCH 461/502] Fix syntax bug in readme file In this url https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F29-isolate-the-component-from-the-world-using-http-interceptor There is a syntax error in the Code Examples section that causes the next item to be included in the code example. --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 57a818c0..df0e4c54 100644 --- a/readme.md +++ b/readme.md @@ -1020,7 +1020,8 @@ beforeEach(() => { id: 1, name: 'John', }); -});``` +}); +```
    From 426c325e73a1b3c2a966245b070a04e16168c197 Mon Sep 17 00:00:00 2001 From: Ali Azmoodeh Date: Sun, 18 Sep 2022 19:47:20 +0430 Subject: [PATCH 462/502] Fix syntax bug in readme file The code sample type was changed and js and json syntax bugs were fixed --- readme.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/readme.md b/readme.md index df0e4c54..5af1226e 100644 --- a/readme.md +++ b/readme.md @@ -1120,7 +1120,6 @@ beforeEach(() => { • Observability - Some things must be monitored, like errors or remarkable business events. When a transaction fails, not only we expect the right response but also correct error handling and proper logging/metrics. This information goes directly to a very important user - The ops user (i.e., production SRE/admin) -


    @@ -1502,14 +1501,13 @@ before(() => { }) // happens before EACH test -beforeEach(setUser => () { - cy.visit('/home', { - onBeforeLoad (win) { +beforeEach(setUser => { + cy.visit('/home', () => { + onBeforeLoad (win => { win.localStorage.setItem('token', JSON.stringify(authenticationToken)) - }, + }) }) }) - ```
    @@ -1567,8 +1565,8 @@ it("When doing smoke testing over all page, should load them all successfully", ![](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 +```text +This is how one can describe tests using cucumber: plain language that allows anyone to understand and collaborate Feature: Twitter new tweet @@ -1581,7 +1579,6 @@ Feature: Twitter new tweet Given I type "Hello followers!" in the textbox Given I click on "Submit" button Then I see message "Tweet saved" - ``` ### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook @@ -1803,7 +1800,7 @@ it.skip("Test name", () => {// *error:no-skipped-tests, error:error:no-global-te expect("somevalue"); // error:no-assert }); -it("Test name", () => {*//error:no-identical-title. Assign unique titles to tests +it("Test name", () => {// *error:no-identical-title. Assign unique titles to tests }); ``` @@ -1853,14 +1850,14 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com ### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code -```javascript -"scripts": { +```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": { @@ -1868,8 +1865,8 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com "precommit": "npm run inspect:all", "prepush": "npm run inspect:all" } + } } - ```
    @@ -1933,13 +1930,12 @@ The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for ### :clap: Doing It Right Example: -```javascript -//install license-checker in your CI environment or also locally +```shell +# 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 +# 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 - ```
    From 9bfce031c796e05d562b853811c73a8d7c9c1b16 Mon Sep 17 00:00:00 2001 From: Ali Azmoodeh Date: Sun, 18 Sep 2022 19:51:56 +0430 Subject: [PATCH 463/502] added chapter 1 and 2 --- readme-pr-fr.md | 2055 +++++++++++++++++++++++++++++++++++++++++++++++ readme.md | 1 + 2 files changed, 2056 insertions(+) create mode 100644 readme-pr-fr.md diff --git a/readme-pr-fr.md b/readme-pr-fr.md new file mode 100644 index 00000000..89c4fe43 --- /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(` +
      +
    • Home
    • +
    • About
    • +
    • Contact
    • +
    +`); +}); +``` + +
    + +

    + +## ⚪ ️کد را کپی کنید، اما فقط آنچه لازم است + +: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) + + +

    + +# Section 3️⃣: Frontend Testing + +## ⚪ ️ 3.1 Separate UI from functionality + +:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI + +
    + +❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Separating out the UI details + +![](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: Anti-Pattern Example: Assertion mix UI details and data + +```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 HTML elements based on attributes that are unlikely to change + +:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed + +
    + +❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Querying an element using a dedicated attribute for testing + +![](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: Anti-Pattern Example: Relying on 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 Whenever possible, test with a realistic and fully rendered component + +:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet ['Favour blackbox testing'](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-14-stick-to-black-box-testing-test-only-public-methods)). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake + +With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children + +
    + +❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Working realistically with a fully rendered component + +![](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: Anti-Pattern Example: Mocking the reality with 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 Don't sleep, use frameworks built-in support for async events. Also try to speed things up + +:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +
    + +❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (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: Doing It Right Example: Testing library that waits for DOM elements + +```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: Anti-Pattern Example: custom sleep code + +```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 Watch how the content is served over the network + +![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") + +✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 + +
    + +❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration + +
    + +
    Code Examples + +### :clap: Doing It Right Example: Lighthouse page load inspection report + +![](/assets/lighthouse2.png "Lighthouse page load inspection report") + +
    + +
    + +## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs + +:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests + +
    + +❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower + +
    + +
    Code Examples + +
    + +### :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 +// 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 Have very few end-to-end tests that spans the whole system + +:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment + +
    + +❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected + +
    + +## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials + +:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)). + +
    + +❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Logging-in before-all and not 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 Have one E2E smoke test that just travels across the site map + +:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector + +
    + +❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Smoke travelling across all 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.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: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. + +❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js + +![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") + +```text +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: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook + +![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") + +![alt text](assets/story-book.jpg "Storybook") + +
    + +

    + +## ⚪ ️ 3.11 Detect visual issues with automated tools + +:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge significant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by eliminating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/CSS changes that led to the issue + +
    + +❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A typical visual regression - right content that is served badly + +![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks") + +
    + +### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots + +![](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: Doing It Right Example: Using Applitools to get snapshot comparison and other advanced features + +![](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️⃣: Measuring Test Effectiveness + +

    + +## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number + +:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. + +Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets + +
    + +❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down + +
    + +
    Code Examples + +
    + +### :clap: Example: A typical coverage report + +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") + +
    + +### :clap: Doing It Right Example: Setting up coverage per component (using 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 Inspect coverage reports to detect untested areas and other oddities + +:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +
    + +❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: 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?") + +
    + +

    + +## ⚪ ️ 4.3 Measure logical coverage using mutation testing + +:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. + +Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: + +(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations + +(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. + +Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +
    + +❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: 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: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (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 Preventing test code issues with Test linters + +:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) + +
    + +❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation + +
    +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: A test case full of errors, luckily all are caught by 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 and Other Quality Measures + +

    + +## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues + +:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +
    + +❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5 min linter setup could detect this TYPO and save your day + +
    + +
    Code Examples + +
    + +### :thumbsdown: Anti-Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production 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 Shorten the feedback loop with local developer-CI + +:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. + +Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +
    + +❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code + +```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 Perform e2e testing over a true production-mirror + +:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of FaaS code. + +The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +
    + +❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated + +
    + +
    Code Examples + +
    + +### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([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 Parallelize test execution + +:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes + +❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([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 Stay away from legal issues using license and plagiarism check + +:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stack Overflow and apparently violates some copyrights + +❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues + +
    + +
    Code Examples + +
    + +### :clap: Doing It Right Example: + +```shell +# 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 Constantly inspect for vulnerable dependencies + +:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build + +❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious + +
    + +
    Code Examples + +
    + +### :clap: Example: NPM Audit result + +![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") + +
    + +

    + +## ⚪ ️5.7 Automate dependency updates + +:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: + +(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. + +(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). + +An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +
    + +❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky + +
    + +
    Code Examples + +
    + +### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest 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 Other, non-Node related, CI tips + +:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known + +
    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
    +
    + +❌ **Otherwise:** You‘ll miss years of wisdom + +

    + +## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions + +:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +
    + +❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? + +
    + +
    Code Examples + +
    + +### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions + +
    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.md b/readme.md index 57a818c0..3c0269d7 100644 --- a/readme.md +++ b/readme.md @@ -42,6 +42,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇫🇷[French](readme-fr.md) - Courtesy of [Mathilde El Mouktafi](https://github.com/mel-mouk) - 🇯🇵[Japanese (draft)](https://github.com/yuichkun/javascript-testing-best-practices/blob/master/readme-jp.md) - Courtesy of [Yuichi Yogo](https://github.com/yuichkun) and [ryo](https://github.com/kawamataryo) - 🇹🇼[Traditional Chinese](readme-zh-TW.md) - Courtesy of [Yubin Hsu](https://github.com/yubinTW) +- 🇹🇼[Persian](readme-pr-fr.md) - Courtesy of [Ali Azmoodeh](https://github.com/TREER00T) - Want to translate to your own language? please open an issue 💜

    From 53f041d161f498ef019a68bacfae1bb586682671 Mon Sep 17 00:00:00 2001 From: Ali Azmoodeh Date: Sun, 18 Sep 2022 19:57:52 +0430 Subject: [PATCH 464/502] fix writing bug --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 07507247..71ef9d96 100644 --- a/readme.md +++ b/readme.md @@ -42,7 +42,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇫🇷[French](readme-fr.md) - Courtesy of [Mathilde El Mouktafi](https://github.com/mel-mouk) - 🇯🇵[Japanese (draft)](https://github.com/yuichkun/javascript-testing-best-practices/blob/master/readme-jp.md) - Courtesy of [Yuichi Yogo](https://github.com/yuichkun) and [ryo](https://github.com/kawamataryo) - 🇹🇼[Traditional Chinese](readme-zh-TW.md) - Courtesy of [Yubin Hsu](https://github.com/yubinTW) -- 🇹🇼[Persian](readme-pr-fr.md) - Courtesy of [Ali Azmoodeh](https://github.com/TREER00T) +- 🇮🇷[Persian](readme-pr-fr.md) - Courtesy of [Ali Azmoodeh](https://github.com/TREER00T) - Want to translate to your own language? please open an issue 💜

    From 8bef8a158a8073e6342c9aef32dfc47af8b520f4 Mon Sep 17 00:00:00 2001 From: Ali Azmoodeh Date: Sun, 18 Sep 2022 23:33:27 +0430 Subject: [PATCH 465/502] started chapter 3 --- readme-pr-fr.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/readme-pr-fr.md b/readme-pr-fr.md index 89c4fe43..4ee19591 100644 --- a/readme-pr-fr.md +++ b/readme-pr-fr.md @@ -1060,7 +1060,7 @@ beforeEach(() => { ```javascript test('هنگامی که سرویس کاربران یک بار با 503 پاسخ می دهد و مکانیسم امتحان مجدد اعمال می شود، سفارش با موفقیت اضافه می شود', async () => { - //مفدار دهی کردن + //مقدار دهی کردن nock.removeInterceptor(userServiceNock.interceptors[0]) nock('http://localhost/user/') .get('/1') @@ -1104,54 +1104,54 @@ beforeEach(() => {

    -# Section 3️⃣: Frontend Testing +# بخش 3️⃣: Frontend تست کردن -## ⚪ ️ 3.1 Separate UI from functionality +## ⚪ ️ 3.1 UI را از لاجیک برنامه جدا کنید -:white_check_mark: **Do:** When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI +:white_check_mark: **انجام دادن:** هنگام تمرکز بر روی تست منطق مؤلفه، جزئیات UI تبدیل به نویز می شود که باید استخراج شود، بنابراین آزمایشات شما می توانند بر روی داده های خالص تمرکز کنند. عملاً داده‌های مورد نظر را از نشانه‌گذاری به روشی انتزاعی استخراج کنید که خیلی با پیاده‌سازی گرافیکی همراه نباشد، فقط روی داده‌های خالص (در مقابل جزئیات گرافیکی HTML/CSS) ادعا کنید و انیمیشن‌هایی را که کند می‌شوند غیرفعال کنید. ممکن است وسوسه شوید که از رندر کردن خودداری کنید و فقط قسمت پشتی رابط کاربری (مانند سرویس‌ها، اقدامات، فروشگاه) را آزمایش کنید، اما این منجر به آزمایش‌های تخیلی می‌شود که به واقعیت شباهت ندارند و مواردی را که داده‌های مناسب وجود ندارد را نشان نمی‌دهد. حتی وارد UI شوید
    -❌ **Otherwise:** The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation +❌ **در غیر این صورت:** داده‌های محاسباتی خالص آزمون شما ممکن است در 10 میلی‌ثانیه آماده باشد، اما پس از آن کل آزمون به دلیل برخی انیمیشن‌های فانتزی و نامربوط، 500 میلی‌ثانیه (100 تست = 1 دقیقه) طول خواهد کشید.
    -
    Code Examples +
    نمونه کد
    -### :clap: Doing It Right Example: Separating out the UI details +### :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 +test("هنگامی که لیست کاربران برای نشان دادن فقط VIP پرچم گذاری می شود، باید فقط اعضای VIP نمایش داده شود", () => { + // مقدار دهی کردن 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 + // مفایسه کردن - ابتدا داده ها را از UI استخراج کنید 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 + expect(allRenderedUsers).toEqual(allRealVIPUsers); //داده ها را با داده ها مقایسه کنید، اینجا رابط کاربری وجود ندارد }); ```
    -### :thumbsdown: Anti-Pattern Example: Assertion mix UI details and data +### :thumbsdown: مثال ضد الگو: جزئیات و داده های رابط کاربری ترکیبی ادعا ```javascript -test("When flagging to show only VIP, should display only VIP members", () => { - // Arrange +test("هنگام پرچم گذاری برای نمایش فقط VIP، باید فقط اعضای VIP نمایش داده شود", () => { + // مقداری دهی کردن 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 + // مقابسه کردن - ترکیب ui و data در این قسمت expect(getAllByTestId("user")).toEqual('[
  • John Doe
  • ]'); }); ``` From e96d72014330d391097f7bb05329ee53fdec5aaa Mon Sep 17 00:00:00 2001 From: alex_p Date: Tue, 4 Oct 2022 15:27:31 +0300 Subject: [PATCH 466/502] docs: update README.md --- readme-ru.md | 2190 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2190 insertions(+) create mode 100644 readme-ru.md diff --git a/readme-ru.md b/readme-ru.md new file mode 100644 index 00000000..95ce4435 --- /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 Categorize tests under at least 2 levels + +: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

    🌍 💻
    + + + + + From e3565227faeee7e6ab16d7b91840403e82628d9f Mon Sep 17 00:00:00 2001 From: alex_p Date: Tue, 4 Oct 2022 15:30:25 +0300 Subject: [PATCH 467/502] docs: update README.md --- readme-ru.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme-ru.md b/readme-ru.md index 95ce4435..239e63bc 100644 --- a/readme-ru.md +++ b/readme-ru.md @@ -698,7 +698,7 @@ describe("Order service", function () {

    -## ⚪ ️ 1.12 Categorize tests under at least 2 levels +## ⚪ ️ 1.12 Структурируйте тесты, как минимум, на 2 уровня :white_check_mark: **Сделать:** Придайте набору тестов некую структуру, чтобы любой, кто посмотрит на них, мог легко понять, что происходит (тесты - лучшая документация). Общим методом для этого является размещение как минимум двух блоков "describe" над тестами: первый - для названия тестируемого блока, а второй - для дополнительного разделения тестов, например, сценария или пользовательских категорий (см. примеры кода и скриншот ниже). Данное разделение также улучшить финальные отчеты по тестированию. Читатель сможет легко разобраться с категориями тестов, найти нужный раздел и понять, где могла возникнуть ошибка. Более того, разработчику будет легче ориентироваться в случае большого количества тестов. Существует несколько способов придать тестам структуру, которое можно найти здесь [given-when-then](https://github.com/searls/jasmine-given) и здесь [RITE](https://github.com/ericelliott/riteway) From ee14cbef4253426f042571c9a96e4c2cd96e21bf Mon Sep 17 00:00:00 2001 From: Ali Azmoodeh Date: Thu, 13 Oct 2022 14:21:45 +0330 Subject: [PATCH 468/502] finished persian translate --- readme-pr-fr.md | 370 ++++++++++++++++++++++++------------------------ 1 file changed, 185 insertions(+), 185 deletions(-) diff --git a/readme-pr-fr.md b/readme-pr-fr.md index 4ee19591..20831337 100644 --- a/readme-pr-fr.md +++ b/readme-pr-fr.md @@ -1160,41 +1160,41 @@ test("هنگام پرچم گذاری برای نمایش فقط VIP، باید

    -## ⚪ ️ 3.2 Query HTML elements based on attributes that are unlikely to change +## ⚪ ️ 3.2 پرس و جو از عناصر HTML بر اساس ویژگی هایی که بعید است تغییر کنند -:white_check_mark: **Do:** Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed +:white_check_mark: **انجام دادن:** عناصر HTML را بر اساس ویژگی هایی که احتمالاً بر خلاف انتخابگرهای CSS و مانند برچسب های فرم، در تغییرات گرافیکی باقی می مانند، پرس و جو کنید. اگر عنصر تعیین‌شده چنین ویژگی‌هایی را ندارد، یک ویژگی تست اختصاصی مانند «test-id-submit-button» ایجاد کنید. پیمودن این مسیر نه تنها تضمین می‌کند که تست‌های عملکردی/منطقی شما هرگز به دلیل تغییرات ظاهری و احساسی شکسته نمی‌شوند، بلکه برای کل تیم مشخص می‌شود که این عنصر و ویژگی توسط تست ‌ها استفاده می‌شوند و نباید حذف شوند.
    -❌ **Otherwise:** You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border' +❌ **در غیر این صورت:** شما می‌خواهید عملکرد ورود به سیستم را که شامل بسیاری از مؤلفه‌ها، منطق و سرویس‌ها می‌شود، آزمایش کنید، همه چیز به‌خوبی تنظیم شده است - موارد خرد، جاسوس‌ها، تماس‌های Ajax جدا شده‌اند. همه عالی به نظر می رسند سپس تست شکست می خورد زیرا طراح کلاس div CSS را از "thick-border" به "thin-border" تغییر داده است.'
    -
    Code Examples +
    نمونه کد
    -### :clap: Doing It Right Example: Querying an element using a dedicated attribute for testing +### :clap: انجام درست مثال: پرس و جو از یک عنصر با استفاده از یک ویژگی اختصاصی برای تست ![](https://img.shields.io/badge/🔧%20Example%20using%20React-blue.svg "Examples with React") ```html -// the markup code (part of React component) +// کد نشانه گذاری (بخشی از مؤلفه React)

    {value} - +

    ``` ```javascript -// this example is using react-testing-library -test("Whenever no data is passed to metric, show 0 as default", () => { - // Arrange +// این مثال از react-testing-library استفاده می کند +test("هر زمان که هیچ داده ای به متریک منتقل نمی شود، 0 را به عنوان پیش فرض نشان دهید", () => { + // مقدار دهی کردن const metricValue = undefined; - // Act + // اجرا کردن const { getByTestId } = render(); expect(getByTestId("errorsLabel").text()).toBe("0"); @@ -1203,17 +1203,17 @@ test("Whenever no data is passed to metric, show 0 as default", () => {
    -### :thumbsdown: Anti-Pattern Example: Relying on CSS attributes +### :thumbsdown: مثال ضد الگو: تکیه بر ویژگی های CSS ```html - + {value} - + ``` ```javascript -// this exammple is using enzyme -test("Whenever no data is passed, error metric shows zero", () => { +// این مثال استفاده از آنزیم است +test("هر زمان که هیچ داده ای ارسال نشود، معیار خطا صفر را نشان می دهد", () => { // ... expect(wrapper.find("[className='d-flex-column']").text()).toBe("0"); @@ -1224,23 +1224,23 @@ test("Whenever no data is passed, error metric shows zero", () => {
    -## ⚪ ️ 3.3 Whenever possible, test with a realistic and fully rendered component +## ⚪ ️ 3.3 در صورت امکان، با یک جزء واقعی و کاملا رندر شده تست کنید -:white_check_mark: **Do:** Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet ['Favour blackbox testing'](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-14-stick-to-black-box-testing-test-only-public-methods)). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake +: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)). اگر یکی از اجزای کودک به طور قابل توجهی کند می شود (مثلاً انیمیشن) یا تنظیمات را پیچیده می کند - به طور واضح آن را با یک جعلی جایگزین کنید. -With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children +با تمام آنچه گفته شد، یک کلمه احتیاط لازم است: این تکنیک برای اجزای کوچک/متوسط ​​که اندازه معقولی از اجزای کودک را در خود جای می دهند، کار می کند. رندر کردن کامل یک مؤلفه با تعداد زیاد فرزندان، استدلال در مورد شکست تست (تحلیل علت ریشه) را دشوار می کند و ممکن است بسیار کند شود. در چنین مواردی، فقط چند آزمایش بر روی آن جزء والد چاق بنویسید و آزمایش های بیشتری را بر روی فرزندان آن بنویسید
    -❌ **Otherwise:** When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance? +❌ **در غیر این صورت:** هنگامی که با فراخوانی روش‌های خصوصی و بررسی وضعیت درونی یک کامپوننت به درونی آن وارد می‌شوید - باید همه آزمایش‌ها را هنگام بازسازی اجرای مؤلفه‌ها مجدداً تغییر دهید. آیا واقعاً برای این سطح از نگهداری ظرفیت دارید؟?
    -
    Code Examples +
    نمونه کد
    -### :clap: Doing It Right Example: Working realistically with a fully rendered component +### :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") @@ -1258,37 +1258,37 @@ class Calendar extends React.Component { } } -//Examples use React & Enzyme -test("Realistic approach: When clicked to show filters, filters are displayed", () => { - // Arrange +//برای مثال از React & Enzyme استفاده می شود +test("رویکرد واقع گرایانه: هنگامی که برای نمایش فیلترها کلیک کنید، فیلترها نمایش داده می شوند", () => { + // مقدار دهی کردن 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: Anti-Pattern Example: Mocking the reality with shallow rendering +### :thumbsdown: مثال ضد الگو: تمسخر واقعیت با رندر کم عمق ```javascript -test("Shallow/mocked approach: When clicked to show filters, filters are displayed", () => { - // Arrange +test("رویکرد کم عمق/ مسخره شده: وقتی برای نمایش فیلترها کلیک کنید، فیلترها نمایش داده می شوند", () => { + // مقدار دهی کردن 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? + // اگر نام پروپوزال را تغییر دهیم یا چیزی مربوطه را پاس نکنیم چه؟ }); ``` @@ -1296,55 +1296,55 @@ test("Shallow/mocked approach: When clicked to show filters, filters are display
    -## ⚪ ️ 3.4 Don't sleep, use frameworks built-in support for async events. Also try to speed things up +## ⚪ ️ 3.4 نخوابید، از framework‌های داخلی پشتیبانی برای رویدادهای همگام استفاده کنید. همچنین سعی کنید کارها را تسریع کنید -:white_check_mark: **Do:** In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. [Cypress cy.request('url')](https://docs.cypress.io/guides/references/best-practices.html#Unnecessary-Waiting)), other provide API for waiting like [@testing-library/dom method wait(expect(element))](https://testing-library.com/docs/guide-disappearance). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to [hurry-up the clock](https://jestjs.io/docs/en/timer-mocks). Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like [wait-for-expect](https://www.npmjs.com/package/wait-for-expect) can help with a semi-deterministic solution +: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) می تواند با یک راه حل نیمه قطعی کمک کند
    -❌ **Otherwise:** When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance +❌ **در غیر این صورت:** هنگام خواب طولانی مدت، آزمایش ها یک مرتبه کندتر خواهند بود. هنگامی که سعی می کنید برای تعداد کمی بخوابید، زمانی که واحد تحت آزمایش به موقع پاسخ ندهد، آزمایش با شکست مواجه می شود. بنابراین به یک مبادله بین پوسته پوسته شدن و عملکرد بد خلاصه می شود
    -
    Code Examples +
    نمونه کد
    -### :clap: Doing It Right Example: E2E API that resolves only when the async operations is done (Cypress) +### :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 -// using Cypress +// استفاده كردن از 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 +cy.wait("@products"); // صبر کنید تا مسیر ظاهر شود +// این خط تنها زمانی اجرا می شود که مسیر آماده باشد ``` -### :clap: Doing It Right Example: Testing library that waits for DOM elements +### :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: Anti-Pattern Example: custom sleep code +### :thumbsdown: مثال ضد الگو: کد خواب سفارشی ```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) { @@ -1353,7 +1353,7 @@ test("movie title appears", async () => { } }, 100); - // wait for appearance and return the element + // منتظر ظاهر شدن باشید و عنصر را برگردانید const movie = await waitForElement(() => getByText("the lion king")); }); ``` @@ -1362,21 +1362,21 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.5 Watch how the content is served over the network +## ⚪ ️ 3.5 مراقب نحوه ارائه محتوا از طریق شبکه باشید ![](https://img.shields.io/badge/🔧%20Example%20using%20Google%20LightHouse-blue.svg "Examples with Lighthouse") -✅ **Do:** Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like [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 +✅ **انجام دادن:** برخی از مانیتورهای فعال را اعمال کنید که تضمین می‌کند بارگذاری صفحه در شبکه واقعی بهینه شده است - این شامل هر گونه نگرانی 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
    -❌ **Otherwise:** It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration +❌ **در غیر این صورت:** باید ناامید کننده باشد که متوجه شوید پس از چنین دقت زیادی برای ایجاد یک UI، تست های عملکردی 100% گذرانده شده و بسته بندی پیچیده - UX به دلیل پیکربندی اشتباه CDN وحشتناک و کند است.
    -
    Code Examples +
    نمونه کد -### :clap: Doing It Right Example: Lighthouse page load inspection report +### :clap: انجام درست مثال: گزارش بازرسی بارگذاری صفحه فانوس دریایی ![](/assets/lighthouse2.png "Lighthouse page load inspection report") @@ -1384,26 +1384,26 @@ test("movie title appears", async () => {
    -## ⚪ ️ 3.6 Stub flaky and slow resources like backend APIs +## ⚪ ️ 3.6 منابع ضعیف و کندی مانند APIهای Backend را جمع آوری کنید -:white_check_mark: **Do:** When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests +:white_check_mark: **انجام دادن:** هنگام کدنویسی تست‌های اصلی خود (نه تست‌های E2E)، از درگیر کردن هر منبعی که خارج از مسئولیت و کنترل شماست مانند API پشتیبان خودداری کنید و به جای آن از خرده‌ها استفاده کنید (یعنی تست دوبار). عملا، به جای تماس های شبکه واقعی با API ها، از چند کتابخانه دوتایی (مانند [Sinon](https://sinonjs.org/), [Test doubles](https://www.npmjs.com/package/testdouble), etc) برای کلفت کردن پاسخ API. مزیت اصلی جلوگیری از پوسته پوسته شدن است - آزمایش یا مرحله‌بندی APIها طبق تعریف چندان پایدار نیستند و هر از چند گاهی در تست‌های شما شکست می‌خورند، اگرچه مؤلفه شما به خوبی رفتار می‌کند (انواع تولید برای آزمایش در نظر گرفته نشده است و معمولاً درخواست‌ها را کاهش می‌دهد). انجام این کار به شبیه‌سازی رفتارهای API مختلف اجازه می‌دهد که رفتار مؤلفه شما را مانند زمانی که هیچ داده‌ای پیدا نمی‌شود یا زمانی که API خطا می‌دهد، هدایت کند. آخرین اما نه کم‌اهمیت، تماس‌های شبکه سرعت آزمایش‌ها را بسیار کند می‌کند
    -❌ **Otherwise:** The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower +❌ **در غیر این صورت:** میانگین تست بیشتر از چند میلی ثانیه اجرا نمی شود، یک تماس API معمولی 100 میلی ثانیه طول می کشد>، این باعث می شود هر تست 20 برابر کندتر شود.
    -
    Code Examples +
    نمونه کد
    -### :clap: Doing It Right Example: Stubbing or intercepting API calls +### :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 -// unit under test +// واحد در حال آزمایش export default function ProductsList() { const [products, setProducts] = useState(false); @@ -1419,17 +1419,17 @@ export default function ProductsList() { 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(); }); ``` @@ -1438,38 +1438,38 @@ test("When no products exist, show the appropriate message", () => {
    -## ⚪ ️ 3.7 Have very few end-to-end tests that spans the whole system +## ⚪ ️ 3.7 تعداد بسیار کمی از تست های سرتاسری که کل سیستم را در بر می گیرد -:white_check_mark: **Do:** Although E2E (end-to-end) usually means UI-only testing with a real browser (See [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like [Cypress](https://www.cypress.io/) and [Puppeteer](https://github.com/GoogleChrome/puppeteer). The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment +: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 احتمالاً نوع مشکلاتی را که برای آنها هدف قرار گرفته‌اند - خطاهای استقرار و یکپارچه‌سازی را شناسایی می‌کنند. توصیه می‌شود آن‌ها را روی یک محیط صحنه‌سازی شبیه تولید اجرا کنید
    -❌ **Otherwise:** UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected +❌ **در غیر این صورت:** UI ممکن است برای آزمایش عملکرد خود سرمایه گذاری زیادی کند تا دیرتر متوجه شود که بار بازگشتی بازگشتی (شمایه داده ای که UI باید با آن کار کند) بسیار متفاوت از آنچه انتظار می رود است.
    -## ⚪ ️ 3.8 Speed-up E2E tests by reusing login credentials +## ⚪ ️ 3.8 با استفاده مجدد از اعتبارنامه ورود به سیستم، تست های E2E را افزایش دهید -:white_check_mark: **Do:** In E2E tests that involve a real backend and rely on a valid user token for API calls, it doesn't payoff to isolate the test to a level where a user is created and logged-in in every request. Instead, login only once before the tests execution start (i.e. before-all hook), save the token in some local storage and reuse it across requests. This seem to violate one of the core testing principle - keep the test autonomous without resources coupling. While this is a valid worry, in E2E tests performance is a key concern and creating 1-3 API requests before starting each individual tests might lead to horrible execution time. Reusing credentials doesn't mean the tests have to act on the same user records - if relying on user records (e.g. test user payments history) than make sure to generate those records as part of the test and avoid sharing their existence with other tests. Also remember that the backend can be faked - if your tests are focused on the frontend it might be better to isolate it and stub the backend API (see [bullet 3.6](https://github.com/goldbergyoni/javascript-testing-best-practices#-%EF%B8%8F-36-stub-flaky-and-slow-resources-like-backend-apis)). +: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)).
    -❌ **Otherwise:** Given 200 test cases and assuming login=100ms = 20 seconds only for logging-in again and again +❌ **در غیر این صورت:** با توجه به 200 مورد تست و با فرض ورود = 100ms = 20 ثانیه فقط برای ورود مجدد و دوباره
    -
    Code Examples +
    نمونه کد
    -### :clap: Doing It Right Example: Logging-in before-all and not before-each +### :clap: انجام درست آن مثال: قبل از همه وارد شوید و نه قبل از هر کدام ![](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'), @@ -1481,7 +1481,7 @@ before(() => { }) }) -// happens before EACH test +// قبل از هر آزمون اتفاق می افتد beforeEach(setUser => { cy.visit('/home', () => { onBeforeLoad (win => { @@ -1495,28 +1495,28 @@ beforeEach(setUser => {
    -## ⚪ ️ 3.9 Have one E2E smoke test that just travels across the site map +## ⚪ ️ 3.9 یک تست دود E2E داشته باشید که فقط در سراسر نقشه سایت حرکت می کند -:white_check_mark: **Do:** For production monitoring and development-time sanity check, run a single E2E test that visits all/most of the site pages and ensures no one breaks. This type of test brings a great return on investment as it's very easy to write and maintain, but it can detect any kind of failure including functional, network and deployment issues. Other styles of smoke and sanity checking are not as reliable and exhaustive - some ops teams just ping the home page (production) or developers who run many integration tests which don't discover packaging and browser issues. Goes without saying that the smoke test doesn't replace functional tests rather just aim to serve as a quick smoke detector +:white_check_mark: **انجام دادن:** برای نظارت بر تولید و بررسی سلامت زمان توسعه، یک تست E2E را اجرا کنید که از همه/بیشتر صفحات سایت بازدید می کند و مطمئن می شود که هیچ کس خراب نمی شود. این نوع تست بازگشت سرمایه زیادی را به ارمغان می آورد زیرا نوشتن و نگهداری آن بسیار آسان است، اما می تواند هر نوع خرابی از جمله مشکلات عملکردی، شبکه و استقرار را تشخیص دهد. سایر سبک‌های بررسی دود و سلامت عقل چندان قابل اعتماد و جامع نیستند - برخی از تیم‌های عملیاتی فقط صفحه اصلی (تولید) را پینگ می‌کنند یا توسعه‌دهندگانی که تست‌های ادغام زیادی را انجام می‌دهند که مشکلات بسته‌بندی و مرورگر را کشف نمی‌کنند. ناگفته نماند که تست دود جایگزین تست های عملکردی نمی شود، بلکه فقط به عنوان یک آشکارساز سریع دود عمل می کند.
    -❌ **Otherwise:** Everything might seem perfect, all tests pass, production health-check is also positive but the Payment component had some packaging issue and only the /Payment route is not rendering +❌ **در غیر این صورت:** ممکن است همه چیز عالی به نظر برسد، همه آزمایش‌ها با موفقیت انجام می‌شوند، بررسی سلامت تولید نیز مثبت است، اما مؤلفه پرداخت مشکلی در بسته‌بندی داشت و فقط مسیر پرداخت / رندر نمی‌شود.
    -
    Code Examples +
    نمونه کد
    -### :clap: Doing It Right Example: Smoke travelling across all pages +### :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", () => { - // exemplified using Cypress but can be implemented easily - // using any E2E suite +it("هنگام انجام تست smoke در تمام صفحات، باید همه آنها را با موفقیت بارگیری کنید", () => { + // نمونه ای با استفاده از Cypress است اما می توان آن را به راحتی اجرا کرد + // با استفاده از هر مجموعه E2E cy.visit("https://mysite.com/home"); cy.contains("Home"); cy.visit("https://mysite.com/Login"); @@ -1530,24 +1530,24 @@ it("When doing smoke testing over all page, should load them all successfully",
    -## ⚪ ️ 3.10 Expose the tests as a live collaborative document +## ⚪ ️ 3.10 آزمایش ها را به عنوان یک سند مشارکتی زنده در معرض دید قرار دهید -:white_check_mark: **Do:** Besides increasing app reliability, tests bring another attractive opportunity to the table - serve as live app documentation. Since tests inherently speak at a less-technical and product/UX language, using the right tools they can serve as a communication artifact that greatly aligns all the peers - developers and their customers. For example, some frameworks allow expressing the flow and expectations (i.e. tests plan) using a human-readable language so any stakeholder, including product managers, can read, approve and collaborate on the tests which just became the live requirements document. This technique is also being referred to as 'acceptance test' as it allows the customer to define his acceptance criteria in plain language. This is [BDD (behavior-driven testing)](https://en.wikipedia.org/wiki/Behavior-driven_development) at its purest form. One of the popular frameworks that enable this is [Cucumber which has a JavaScript flavor](https://github.com/cucumber/cucumber-js), see example below. Another similar yet different opportunity, [StoryBook](https://storybook.js.org/), allows exposing UI components as a graphic catalog where one can walk through the various states of each component (e.g. render a grid w/o filters, render that grid with multiple rows or with none, etc), see how it looks like, and how to trigger that state - this can appeal also to product folks but mostly serves as live doc for developers who consume those components. +: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/), اجازه می دهد تا مؤلفه های رابط کاربری را به عنوان یک کاتالوگ گرافیکی در معرض دید قرار دهید، جایی که می توانید در حالت های مختلف هر مؤلفه قدم بزنید (مثلاً یک شبکه بدون فیلتر ارائه دهید، آن شبکه را با چندین ردیف یا با هیچ کدام و غیره ارائه کنید)، ببینید چگونه به نظر می رسد و چگونه است. برای راه اندازی آن حالت - این می تواند برای افراد محصول نیز جذاب باشد، اما بیشتر به عنوان سند زنده برای توسعه دهندگانی که آن اجزا را مصرف می کنند، عمل می کند. -❌ **Otherwise:** After investing top resources on testing, it's just a pity not to leverage this investment and win great value +❌ **در غیر این صورت:** پس از سرمایه گذاری منابع برتر در آزمایش، فقط حیف است که از این سرمایه گذاری استفاده نکنید و ارزش زیادی کسب نکنید
    -
    Code Examples +
    نمونه کد
    -### :clap: Doing It Right Example: Describing tests in human-language using cucumber-js +### :clap: انجام درست آن مثال: توصیف تست ها به زبان انسان با استفاده از cucumber-js ![](https://img.shields.io/badge/🔨%20Example%20using%20Cucumber-blue.svg "Examples using Cucumber") ```text -This is how one can describe tests using cucumber: plain language that allows anyone to understand and collaborate +اینگونه می توان تست ها را با استفاده از cucumber توصیف کرد: زبانی ساده که به هر کسی اجازه می دهد درک کند و همکاری کند. Feature: Twitter new tweet @@ -1562,7 +1562,7 @@ Feature: Twitter new tweet Then I see message "Tweet saved" ``` -### :clap: Doing It Right Example: Visualizing our components, their various states and inputs using Storybook +### :clap: انجام درست مثال: تجسم اجزای ما، حالت‌های مختلف و ورودی‌های آنها با استفاده از Storybook ![](https://img.shields.io/badge/🔨%20Example%20using%20StoryBook-blue.svg "Using StoryBook") @@ -1572,27 +1572,27 @@ Feature: Twitter new tweet

    -## ⚪ ️ 3.11 Detect visual issues with automated tools +## ⚪ ️ 3.11 مشکلات بصری را با ابزارهای خودکار تشخیص دهید -:white_check_mark: **Do:** Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. [wraith](https://github.com/BBC-News/wraith), [PhantomCSS](<[https://github.com/HuddleEng/PhantomCSS](https://github.com/HuddleEng/PhantomCSS)>) but might charge significant setup time. The commercial line of tools (e.g. [Applitools](https://applitools.com/), [Percy.io](https://percy.io/)) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by eliminating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/CSS changes that led to the issue +: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 که منجر به این مشکل شده است، یک قدم بیشتر است.
    -❌ **Otherwise:** How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden? +❌ **در غیر این صورت:** صفحه محتوایی که محتوای عالی را نمایش می‌دهد چقدر خوب است (100٪ تست‌ها با موفقیت انجام شد)، فورا بارگیری می‌شود اما نیمی از منطقه محتوا پنهان است.?
    -
    Code Examples +
    نمونه کد
    -### :thumbsdown: Anti-Pattern Example: A typical visual regression - right content that is served badly +### :thumbsdown: مثال ضد الگو: یک رگرسیون بصری معمولی - محتوای درستی که بد ارائه می شود ![alt text](assets/amazon-visual-regression.jpeg "Amazon page breaks")
    -### :clap: Doing It Right Example: Configuring wraith to capture and compare UI snapshots +### :clap: انجام درست مثال: پیکربندی wraith برای گرفتن و مقایسه عکس های فوری UI ![](https://img.shields.io/badge/🔨%20Example%20using%20Wraith-blue.svg "Using Wraith") @@ -1621,7 +1621,7 @@ paths: path: /subscribe ``` -### :clap: Doing It Right Example: Using Applitools to get snapshot comparison and other advanced features +### :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") @@ -1648,33 +1648,33 @@ describe("visual validation", () => {

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

    -## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number +## ⚪ ️ 4.1 پوشش کافی برای داشتن اعتماد به نفس داشته باشید، به نظر می رسد 80٪ عدد خوش شانس باشد -:white_check_mark: **Do:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. +:white_check_mark: **انجام دادن:** هدف از تست گرفتن اعتماد به نفس کافی برای حرکت سریع است، بدیهی است که هر چه کد بیشتر تست شود، تیم می تواند اعتماد بیشتری داشته باشد. پوشش معیاری است از تعداد خطوط کد (و شاخه ها، دستورات و غیره) که توسط آزمایش ها به دست می آیند. پس چقدر کافی است؟ 10-30٪ واضح است که برای درک درستی ساخت بسیار کم است، از طرف دیگر 100٪ بسیار گران است و ممکن است تمرکز شما را از مسیرهای مهم به گوشه های عجیب و غریب کد تغییر دهد. پاسخ طولانی این است که به عوامل زیادی مانند نوع برنامه بستگی دارد - اگر شما در حال ساخت نسل بعدی ایرباس A380 هستید، 100٪ ضروری است، برای یک وب سایت تصاویر کارتونی 50٪ ممکن است خیلی زیاد باشد. اگرچه اکثر علاقه مندان به تست ادعا می کنند که آستانه پوشش مناسب متنی است، اکثر آنها عدد 80٪ را نیز به عنوان یک قانون ذکر می کنند. ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) که احتمالاً باید اکثر برنامه ها را برآورده کند. -Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets +نکات پیاده سازی: ممکن است بخواهید ادغام پیوسته (CI) خود را برای داشتن آستانه پوشش پیکربندی کنید. ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) و ساختنی را که مطابق با این استاندارد نیست متوقف کنید (همچنین می توان آستانه را برای هر جزء پیکربندی کرد، به مثال کد زیر مراجعه کنید). علاوه بر این، تشخیص کاهش پوشش ساخت را در نظر بگیرید (زمانی که یک کد جدید متعهد شده دارای پوشش کمتری است)— این باعث می‌شود توسعه‌دهندگان مقدار کد تست شده را افزایش دهند یا حداقل حفظ کنند. همه آنچه گفته شد، پوشش تنها یک معیار است، یک معیار مبتنی بر کمی، که برای نشان دادن استحکام آزمایش شما کافی نیست. و همچنین همانطور که در گلوله های بعدی نشان داده شده است می توان آن را فریب داد
    -❌ **Otherwise:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down +❌ **در غیر این صورت:** اعتماد به نفس و اعداد دست به دست هم می دهند، بدون اینکه واقعاً بدانید که بیشتر سیستم را آزمایش کرده اید - همچنین مقداری ترس وجود خواهد داشت و ترس سرعت شما را کاهش می دهد.
    -
    Code Examples +
    نمونه کد
    -### :clap: Example: A typical coverage report +### :clap: مثال: یک گزارش پوشش معمولی ![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report")
    -### :clap: Doing It Right Example: Setting up coverage per component (using Jest) +### :clap: انجام درست آن مثال: تنظیم پوشش برای هر جزء (با استفاده از Jest) ![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") @@ -1684,22 +1684,22 @@ Implementation tips: You may want to configure your continuous integration (CI)

    -## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities +## ⚪ ️ 4.2 گزارش های پوشش را برای شناسایی مناطق آزمایش نشده و سایر موارد عجیب بازرسی کنید -:white_check_mark: **Do:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +:white_check_mark: **انجام دادن:** برخی از مسائل دقیقاً زیر رادار پنهان می شوند و با استفاده از ابزارهای سنتی پیدا کردن آنها واقعاً سخت است. اینها واقعاً باگ نیستند، بلکه بیشتر رفتارهای شگفت انگیز برنامه هستند که ممکن است تأثیر شدیدی داشته باشند. به عنوان مثال، اغلب برخی از مناطق کد هرگز یا به ندرت فراخوانی نمی شوند - شما فکر می کنید که کلاس "PricingCalculator" همیشه قیمت محصول را تعیین می کند، اما به نظر می رسد که در واقع هرگز فراخوانی نمی شود، اگرچه ما 10000 محصول در DB داریم و تعداد زیادی فروش... پوشش کد گزارش‌ها به شما کمک می‌کنند متوجه شوید که آیا برنامه به‌گونه‌ای که شما فکر می‌کنید رفتار می‌کند یا خیر. به غیر از آن، همچنین می‌تواند مشخص کند که کدام نوع کد آزمایش نشده است - با اطلاع از اینکه 80 درصد کد آزمایش شده است، نمی‌گوید که آیا قسمت‌های حیاتی پوشش داده شده‌اند یا خیر. ایجاد گزارش آسان است - فقط برنامه خود را در مرحله تولید یا در حین آزمایش با ردیابی پوشش اجرا کنید و سپس گزارش‌های رنگارنگی را مشاهده کنید که نشان می‌دهد تعداد دفعات فراخوانی هر ناحیه کد مشخص می‌شود. اگر وقت خود را صرف نگاهی اجمالی به این داده ها کنید - ممکن است چند اشتباه پیدا کنید
    -❌ **Otherwise:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from +❌ **در غیر این صورت:** اگر نمی‌دانید کدام بخش از کدتان آزمایش نشده است، نمی‌دانید این مشکلات از کجا می‌آیند.
    -
    Code Examples +
    نمونه کد
    -### :thumbsdown: Anti-Pattern Example: What’s wrong with this coverage report? +### :thumbsdown: مثال ضد الگو: این گزارش پوشش چه اشکالی دارد؟ -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) +بر اساس یک سناریوی واقعی که در آن ما استفاده از برنامه خود را در QA ردیابی کردیم و الگوهای ورود جالبی را پیدا کردیم (نکته: میزان خرابی های ورود به سیستم نامتناسب است، چیزی به وضوح اشتباه است. در نهایت مشخص شد که برخی از باگ های frontend مدام به سیستم وارد می شوند. API ورود باطن) ![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report?") @@ -1707,28 +1707,28 @@ Based on a real-world scenario where we tracked our application usage in QA and

    -## ⚪ ️ 4.3 Measure logical coverage using mutation testing +## ⚪ ️ 4.3 اندازه گیری پوشش منطقی با استفاده از تست جهش -:white_check_mark: **Do:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +:white_check_mark: **انجام دادن:** معیار پوشش سنتی اغلب دروغ می گوید: ممکن است پوشش 100٪ کد را به شما نشان دهد، اما هیچ یک از توابع شما، حتی یک مورد، پاسخ مناسب را نشان نمی دهد. چطور؟ آن را به سادگی اندازه گیری می کند که از کدام خطوط کد تست بازدید کرده است، اما بررسی نمی کند که آیا آزمون ها واقعاً چیزی را آزمایش کرده اند - برای پاسخ درست اظهار شده است. مانند کسی که برای تجارت سفر می کند و مهر پاسپورت خود را نشان می دهد - این نشان دهنده انجام هیچ کاری نیست، فقط اینکه او از تعداد کمی از فرودگاه ها و هتل ها بازدید کرده است. -Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: +آزمایش مبتنی بر جهش با اندازه‌گیری مقدار کدی که واقعاً تست شده است و نه فقط بازدید شده، به شما کمک می‌کند.. [Stryker](https://stryker-mutator.io/) یک کتابخانه جاوا اسکریپت برای آزمایش جهش است و پیاده سازی آن واقعاً منظم است: -(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations +(1) به عمد کد را تغییر می دهد و "اشکالات گیاهی" را ایجاد می کند. برای مثال کد newOrder.price===0 به newOrder.price!=0 تبدیل می شود. این "اشکال" جهش نامیده می شود -(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. +(2) تست ها را اجرا می کند، اگر همه موفق شوند، مشکل داریم - آزمایش ها به هدف خود یعنی کشف باگ ها عمل نکردند، جهش ها به اصطلاح زنده می مانند. اگر آزمایش ها شکست خوردند، عالی است، جهش ها کشته شدند. -Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +دانستن اینکه همه یا بیشتر جهش‌ها کشته شده‌اند، اطمینان بسیار بیشتری نسبت به پوشش سنتی می‌دهد و زمان راه‌اندازی مشابه است.
    -❌ **Otherwise:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code +❌ **در غیر این صورت:** شما فریب خواهید خورد که فکر کنید پوشش 85٪ به این معنی است که تست شما اشکالات را در 85٪ از کد شما تشخیص می دهد.
    -
    Code Examples +
    نمونه کد
    -### :thumbsdown: Anti-Pattern Example: 100% coverage, 0% testing +### :thumbsdown: مثال ضد الگو: 100% پوشش، 0% تست ![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") @@ -1741,14 +1741,14 @@ function addNewOrder(newOrder) { return { approved: true }; } -it("Test addNewOrder, don't use such test names", () => { +it("addNewOrder را تست کنید، از چنین نام های آزمایشی استفاده نکنید", () => { addNewOrder({ assignee: "John@mailer.com", price: 120 }); -}); //Triggers 100% code coverage, but it doesn't check anything +}); //پوشش 100٪ کد را فعال می کند، اما چیزی را بررسی نمی کند ```
    -### :clap: Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :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)") @@ -1756,32 +1756,32 @@ it("Test addNewOrder, don't use such test names", () => {

    -## ⚪ ️4.4 Preventing test code issues with Test linters +## ⚪ ️4.4 جلوگیری از مشکلات کد تست با لینترهای تست -:white_check_mark: **Do:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +: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) برای مثال می تواند هشدار دهد زمانی که یک آزمون اصلاً ادعایی ندارد (چک نکردن چیزی)
    -❌ **Otherwise:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation +❌ **در غیر این صورت:** مشاهده 90% پوشش کد و 100% تست‌های سبز باعث می‌شود چهره شما لبخند بزرگی بزند فقط تا زمانی که متوجه شوید که بسیاری از تست‌ها برای هیچ چیزی ادعا نمی‌کنند و بسیاری از مجموعه‌های آزمایشی صرفاً نادیده گرفته شده‌اند. امیدوارم بر اساس این مشاهده نادرست چیزی را مستقر نکرده باشید
    -
    Code Examples +
    نمونه کد
    -### :thumbsdown: Anti-Pattern Example: A test case full of errors, luckily all are caught by Linters +### :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 +describe("توضیحات خیلی کوتاه", () => { + const userToken = userService.getDefaultToken() // *error:no-setup-in-describe، به جای آن از هوک ها (به مقدار کم) استفاده کنید + it("کمی توضیحات", () => {});//* error: valid-test-description. باید شامل کلمه "Should" + حداقل 5 کلمه باشد }); -it.skip("Test name", () => {// *error:no-skipped-tests, error:error:no-global-tests. Put tests only under describe or suite +it.skip("نام تست", () => {// *error:no-skipped-tests, error:error:no-global-tests. تست ها را فقط در قسمت توصیف یا مجموعه قرار دهید expect("somevalue"); // error:no-assert }); -it("Test name", () => {// *error:no-identical-title. Assign unique titles to tests +it("نام تست", () => {// *error:no-identical-title. عناوین منحصر به فرد را به آزمون ها اختصاص دهید }); ``` @@ -1789,24 +1789,24 @@ it("Test name", () => {// *error:no-identical-title. Assign unique titles to tes

    -# Section 5️⃣: CI and Other Quality Measures +# بخش 5️⃣: CI و سایر معیارهای کیفیت

    ## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues -:white_check_mark: **Do:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +: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(…)
    -❌ **Otherwise:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5 min linter setup could detect this TYPO and save your day +❌ **در غیر این صورت:**یک روز بارانی را در نظر بگیرید که در آن تولید شما مدام خراب می‌شود اما گزارش‌ها ردیابی پشته خطا را نشان نمی‌دهند. چی شد؟ کد شما به اشتباه یک شی بدون خطا پرتاب کرد و رد پشته از بین رفت، دلیل خوبی برای ضربه زدن سر خود به دیوار آجری است. یک راه‌اندازی 5 دقیقه‌ای لینتر می‌تواند این اشتباه تایپی را شناسایی کرده و در روز شما صرفه‌جویی کند
    -
    Code Examples +
    نمونه کد
    -### :thumbsdown: Anti-Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug +### :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") @@ -1814,22 +1814,22 @@ it("Test name", () => {// *error:no-identical-title. Assign unique titles to tes

    -## ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI +## ⚪ ️ 5.2 حلقه بازخورد را با توسعه دهنده-CI محلی کوتاه کنید -:white_check_mark: **Do:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +:white_check_mark: **انجام دادن:** آیا از یک CI با بازرسی های کیفیت براق مانند آزمایش، پرده زدن، بررسی آسیب پذیری ها و غیره استفاده می کنید؟ به توسعه دهندگان کمک کنید تا این خط لوله را نیز به صورت محلی اجرا کنند تا بازخورد فوری دریافت کنند و زمان را کوتاه کنند [حلقه بازخورد](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). چرا؟ یک فرآیند تست کارآمد حلقه های تکراری و متعددی را تشکیل می دهد: (1) آزمایشات -> (2) بازخورد -> (3) بازساز. هرچه بازخورد سریعتر باشد، توسعه دهنده می تواند تکرارهای بهبود بیشتری را در هر ماژول انجام دهد و نتایج را کامل کند. در تلنگر، زمانی که بازخورد دیر می رسد، تکرارهای بهبود کمتری می تواند در یک روز بسته بندی شود، تیم ممکن است در حال حاضر به سمت موضوع/وظیفه/ماژول دیگری حرکت کند و ممکن است برای اصلاح آن ماژول آماده نباشد. -Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +در عمل، برخی از فروشندگان 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))
    -❌ **Otherwise:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact +❌ **در غیر این صورت:** هنگامی که نتایج کیفی یک روز پس از کد به دست می‌آیند، آزمایش به بخشی روان از توسعه تبدیل نمی‌شود، بلکه به یک مصنوع رسمی پس از واقعیت تبدیل می‌شود.
    -
    Code Examples +
    نمونه های کد
    -### :clap: Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: درست انجام دادن مثال: اسکریپت‌های npm که بازرسی کیفیت کد را انجام می‌دهند، همه به‌صورت موازی اجرا می‌شوند یا زمانی که یک توسعه‌دهنده تلاش می‌کند کد جدیدی را وارد کند. ```json { @@ -1854,22 +1854,22 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com

    -## ⚪ ️5.3 Perform e2e testing over a true production-mirror +## ⚪ ️5.3 آزمایش e2e را روی یک آینه تولید واقعی انجام دهید -:white_check_mark: **Do:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of FaaS code. +: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 را امکان پذیر می کند. -The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +اکوسیستم عظیم 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 از راه دور انجام شود.
    -❌ **Otherwise:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated +❌ **در غیر این صورت:**استفاده از فناوری های مختلف برای تولید و آزمایش مستلزم حفظ دو مدل استقرار است و توسعه دهندگان و تیم عملیات را از هم جدا نگه می دارد.
    -
    Code Examples +
    نمونه کد
    -### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([Credit: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-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
    @@ -1877,19 +1877,19 @@ The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for

    -## ⚪ ️5.4 Parallelize test execution +## ⚪ ️5.4 موازی کردن اجرای آزمایش -:white_check_mark: **Do:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes +: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 ابری با استفاده از چندین ماشین - تقاضای موازی کردن تست‌ها را مستقل نگه می‌دارد زیرا هر کدام ممکن است در فرآیندهای مختلف اجرا شوند. -❌ **Otherwise:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant +❌ **در غیر این صورت:** دریافت نتایج آزمون 1 ساعت پس از فشار دادن کد جدید، همانطور که قبلاً ویژگی‌های بعدی را کدنویسی کرده‌اید، یک دستور العمل عالی برای کاهش مرتبط کردن تست است.
    -
    Code Examples +
    نمونه کد
    -### :clap: Doing It Right Example: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :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)") @@ -1897,25 +1897,25 @@ The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for

    -## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check +## ⚪ ️5.5 با استفاده از چک مجوز و سرقت ادبی از مسائل قانونی خودداری کنید -:white_check_mark: **Do:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stack Overflow and apparently violates some copyrights +:white_check_mark: **انجام دادن:** مسائل مربوط به مجوز و سرقت ادبی احتمالاً دغدغه اصلی شما در حال حاضر نیست، اما چرا این کادر را در 10 دقیقه تیک ندهید؟ یک دسته از بسته های npm مانند [بررسی مجوز](https://www.npmjs.com/package/license-checker) و [چک سرقت ادبی](https://www.npmjs.com/package/plagiarism-checker) (تجاری با طرح رایگان) را می توان به راحتی در خط لوله CI خود قرار داد و غم هایی مانند وابستگی ها را با مجوزهای محدود یا کدی که از Stack Overflow کپی شده است و ظاهراً برخی از حق چاپ را نقض می کند بررسی کرد. -❌ **Otherwise:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues +❌ **در غیر این صورت:** به طور ناخواسته، توسعه‌دهندگان ممکن است از بسته‌هایی با مجوزهای نامناسب استفاده کنند یا کد تجاری را کپی پیست کنند و با مشکلات قانونی مواجه شوند.
    -
    Code Examples +
    نمونه کد
    -### :clap: Doing It Right Example: +### :clap: انجام درست آن مثال: ```shell -# install license-checker in your CI environment or also locally +# لایسنس چک را در محیط CI خود یا به صورت محلی نصب کنید 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 +# از آن بخواهید که تمام مجوزها را اسکن کند و اگر مجوز غیرمجاز پیدا کرد با کد خروجی غیر از 0 شکست بخورد. سیستم CI باید این شکست را بگیرد و ساخت را متوقف کند license-checker --summary --failOn BSD ``` @@ -1927,19 +1927,19 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.6 Constantly inspect for vulnerable dependencies +## ⚪ ️5.6 به طور مداوم وابستگی های آسیب پذیر را بررسی کنید -:white_check_mark: **Do:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +:white_check_mark: **انجام دادن:** حتی معتبرترین وابستگی ها مانند Express آسیب پذیری های شناخته شده ای دارند. این می تواند به راحتی با استفاده از ابزارهای جامعه مانند [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), یا ابزارهای تجاری مانند [snyk](https://snyk.io/) (نسخه انجمن رایگان را نیز ارائه دهید). هر دو را می توان از CI شما در هر ساختنی فراخوانی کرد -❌ **Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious +❌ **در غیر این صورت:** پاک نگه داشتن کد خود از آسیب‌پذیری‌ها بدون ابزار اختصاصی، مستلزم این است که دائماً انتشارات آنلاین در مورد تهدیدات جدید را دنبال کنید. کاملا خسته کننده
    -
    Code Examples +
    نمونه کد
    -### :clap: Example: NPM Audit result +### :clap: مقال: NPM Audit نتیجه ![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") @@ -1947,26 +1947,26 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.7 Automate dependency updates +## ⚪ ️5.7 به‌روزرسانی‌های وابستگی را خودکار کنید -:white_check_mark: **Do:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: +: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 can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. +(1) CI می‌تواند در ساخت‌هایی که وابستگی‌های منسوخ دارند با استفاده از ابزارهایی مانند [‘npm outdated’](https://docs.npmjs.com/cli/outdated) یا ‘npm-check-updates (ncu)’ . انجام این کار توسعه دهندگان را وادار می کند تا وابستگی ها را به روز کنند. -(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). +(2) از ابزارهای تجاری استفاده کنید که کد را اسکن می کنند و به طور خودکار درخواست های کشش را با وابستگی های به روز ارسال می کنند. یک سوال جالب باقی مانده این است که سیاست به‌روزرسانی وابستگی چگونه باید باشد— به‌روزرسانی در هر وصله سربار زیادی ایجاد می‌کند، به‌روزرسانی درست زمانی که یک اصلی منتشر می‌شود ممکن است به یک نسخه ناپایدار اشاره کند (بسیاری از بسته‌ها در همان روزهای اول پس از انتشار آسیب‌پذیر هستند., [را ببینید](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope حادثه). -An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) +یک خط‌مشی به‌روزرسانی کارآمد ممکن است اجازه دهد تا مقداری «دوره واگذاری» وجود داشته باشد - اجازه دهید کد برای مدتی از آخرین @ و نسخه‌ها عقب بماند، قبل از اینکه نسخه محلی را منسوخ در نظر بگیرید (به عنوان مثال نسخه محلی 1.3.1 و نسخه مخزن 1.3.8 است).
    -❌ **Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky +❌ **در غیر این صورت:** تولید شما بسته هایی را اجرا می کند که به صراحت توسط نویسنده آنها به عنوان خطرناک برچسب گذاری شده است
    -
    Code Examples +
    نمونه کد
    -### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions +### :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") @@ -1974,33 +1974,33 @@ An efficient update policy may allow some ‘vesting period’ — let the c

    -## ⚪ ️ 5.8 Other, non-Node related, CI tips +## ⚪ ️ 5.8 نکات دیگر، غیر مرتبط با گره، CI -:white_check_mark: **Do:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +:white_check_mark: **انجام دادن:** این پست بر روی توصیه‌های آزمایشی متمرکز شده است، یا حداقل می‌توان آن را با 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
    +
    1. از یک نحو اعلانی استفاده کنید. این تنها گزینه برای اکثر فروشندگان است اما نسخه های قدیمی Jenkins امکان استفاده از کد یا UI را می دهد
    2. فروشنده ای را انتخاب کنید که از پشتیبانی Docker بومی برخوردار باشد
    3. زود شکست بخورید، ابتدا سریع ترین تست های خود را اجرا کنید. یک مرحله / نقطه عطف "تست دود" ایجاد کنید که چندین بازرسی سریع را گروه بندی می کند (مانند پرده زدن، تست های واحد) و بازخورد سریع را به committer کد ارائه می دهد.
    4. بررسی تمام مصنوعات ساختنی از جمله گزارش‌های آزمایش، گزارش‌های پوشش، گزارش‌های جهش، گزارش‌ها و غیره را آسان کنید.
    5. چندین خط لوله/شغل برای هر رویداد ایجاد کنید، از مراحل بین آنها دوباره استفاده کنید. به عنوان مثال، یک کار را برای commit های شاخه ویژگی و یک کار دیگر را برای PR اصلی پیکربندی کنید. به هر منطق اجازه استفاده مجدد را با استفاده از مراحل مشترک بدهید (اکثر فروشندگان مکانیزمی برای استفاده مجدد از کد ارائه می دهند)
    6. هرگز اسرار را در یک اعلامیه شغلی قرار ندهید، آنها را از یک فروشگاه مخفی یا از پیکربندی شغل بگیرید.
    7. به صراحت نسخه را در یک نسخه منتشر کنید یا حداقل اطمینان حاصل کنید که توسعه دهنده این کار را انجام داده است
    8. فقط یک بار بسازید و تمام بازرسی‌ها را روی یک مصنوع ساخت (مثلاً تصویر داکر) انجام دهید.
    9. در یک محیط زودگذر که حالت رانش بین ساخت‌ها را ندارد، تست کنید. ذخیره node_modules ممکن است تنها استثنا باشد

    -❌ **Otherwise:** You‘ll miss years of wisdom +❌ **در غیر این صورت:** شما سالهای خرد را از دست خواهید داد

    -## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions +## ⚪ ️ 5.9 ساخت ماتریس: همان مراحل CI را با استفاده از چندین نسخه Node اجرا کنید -:white_check_mark: **Do:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +:white_check_mark: **انجام دادن:** بررسی کیفیت در مورد سرندیپیتی است، هرچه زمینه بیشتری را پوشش دهید، در تشخیص زودهنگام مشکلات شانس بیشتری خواهید داشت. هنگام توسعه بسته‌های قابل استفاده مجدد یا اجرای یک تولید چند مشتری با پیکربندی‌های مختلف و نسخه‌های Node، CI باید خط لوله آزمایش‌ها را روی همه جایگشت‌های پیکربندی‌ها اجرا کند. به عنوان مثال، با فرض اینکه ما از MySQL برای برخی از مشتریان و از Postgres برای برخی دیگر استفاده می‌کنیم - برخی از فروشندگان CI از ویژگی به نام «Matrix» پشتیبانی می‌کنند که امکان اجرای مجموعه آزمایشی را در برابر همه جایگشت‌های MySQL، Postgres و چندین نسخه Node مانند 8، 9 و 10 می‌دهد. این کار فقط با استفاده از پیکربندی بدون هیچ تلاش اضافی انجام می شود (با فرض اینکه تست یا هر گونه بررسی کیفیت دیگری داشته باشید). سایر CI که از Matrix پشتیبانی نمی کنند ممکن است افزونه ها یا ترفندهایی برای اجازه دادن به آن داشته باشند
    -❌ **Otherwise:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? +❌ **در غیر این صورت:** بنابراین، پس از انجام این همه کار سخت در تست نوشتن، تنها به دلیل مشکلات پیکربندی، اجازه می‌دهیم باگ‌ها پنهان شوند.?
    -
    Code Examples +
    نمونه کد
    -### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +### :clap: مثال: استفاده از تعریف ساخت تراویس (فروشنده CI) برای اجرای همان آزمایش روی چندین نسخه Node -
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    +
    زبان: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test


    From 690df32d37da764757853799045f40545f7cc243 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Mon, 28 Nov 2022 05:29:48 -0500 Subject: [PATCH 469/502] Update readme.md --- readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 57a818c0..2a8134c3 100644 --- a/readme.md +++ b/readme.md @@ -26,9 +26,8 @@ Start by understanding the ubiquitous testing practices that are the foundation ### Written By 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 [7 hours of video](https://www.testjavascript.com), 14 test types and more than 40 best practices +- 📗 [Testing Node.js & JavaScript From A To Z](https://www.testjavascript.com) - My comprehensive online course with more than [7 hours of video](https://www.testjavascript.com) - [Follow me on Twitter](https://twitter.com/goldbergyoni/) -- [Next workshop: Verona, Italy 🇮🇹, April 20th](https://2022.jsday.it/workshop/nodejs_testing.html)
    From d51ab0cac7b7cdb63e18b75f4e279112f3336331 Mon Sep 17 00:00:00 2001 From: Serhii Shramko Date: Wed, 7 Dec 2022 22:42:23 +0100 Subject: [PATCH 470/502] feat: implement all chapters --- readme-ua.md | 178 +++++++++++++++++++++++++-------------------------- 1 file changed, 88 insertions(+), 90 deletions(-) diff --git a/readme-ua.md b/readme-ua.md index f73b5783..1f76d0e4 100644 --- a/readme-ua.md +++ b/readme-ua.md @@ -1663,19 +1663,19 @@ describe("visual validation", () => {

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

    -## ⚪ ️ 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number +## ⚪ ️ 4.1 Отримайте достатнє покриття, щоб бути впевненим, ~80%, здається, це щасливе число -:white_check_mark: **Роби:** The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10–30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of application — if you’re building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule ([Fowler: “in the upper 80s or 90s”](https://martinfowler.com/bliki/TestCoverage.html)) that presumably should satisfy most of the applications. +:white_check_mark: **Роби:** Мета тестування полягає в тому, щоб отримати достатню впевненість для швидкого просування; очевидно, чим більше коду тестується, тим впевненішою може бути команда. Покриття — це показник того, скільки рядків коду (і розгалужень, операторів тощо) охоплено тестами. То скільки вистачить? 10–30% — це, очевидно, занадто мало, щоб отримати будь-яке уявлення про правильність збірки, з іншого боку, 100% — це дуже дорого й може перемістити вашу увагу з критичних шляхів на екзотичні куточки коду. Довга відповідь полягає в тому, що це залежить від багатьох факторів, таких як тип застосування —«якщо ви створюєте наступне покоління Airbus A380, 100% є обов’язковим, для веб-сайту з мультфільмами 50% може бути забагато. Хоча більшість ентузіастів тестування стверджують, що правильний поріг охоплення є контекстним, більшість із них також згадує число 80% як правило ([Fowler: "in the upper 80s or 90s"](https://martinfowler. com/bliki/TestCoverage.html)), який, імовірно, повинен задовольнити більшість програм. -Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) and stop a build that doesn’t stand to this standard (it’s also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage) — this will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets +Поради щодо впровадження: Можливо, ви захочете налаштувати безперервну інтеграцію (CI), щоб мати поріг покриття ([Jest link](https://jestjs.io/docs/en/configuration.html#collectcoverage-boolean)) і зупинити збірку що не відповідає цьому стандарту (також можна налаштувати порогове значення для кожного компонента, див. приклад коду нижче). На додаток до цього, подумайте про виявлення зменшення покриття збірки (коли нещодавно зафіксований код має менше покриття) — це підштовхне розробників збільшити або принаймні зберегти кількість перевіреного коду. Загалом, охоплення — це лише один показник, кількісний, якого недостатньо, щоб визначити надійність вашого тестування. І його також можна обдурити, як показано в наступних пунктах
    -❌ **Інакше:** Confidence and numbers go hand in hand, without really knowing that you tested most of the system — there will also be some fear and fear will slow you down +❌ **Інакше:** Впевненість і цифри йдуть пліч-о-пліч, навіть не знаючи, що ви перевірили більшу частину системи -також буде певний страх, і страх сповільнить вас
    @@ -1683,28 +1683,28 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :clap: Example: A typical coverage report +### :clap: Приклад: Типовий звіт про покриття -![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "A typical coverage report") +![alt text](assets/bp-18-yoni-goldberg-code-coverage.png "Типовий звіт про покриття")
    -### :clap: Приклад правильного виконання: Setting up coverage per component (using Jest) +### :clap: Приклад правильного виконання: Налаштування покриття для кожного компонента (за допомогою Jest) -![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Using Jest") +![](https://img.shields.io/badge/🔨%20Example%20using%20Jest-blue.svg "Jest") -![alt text](assets/bp-18-code-coverage2.jpeg "Setting up coverage per component (using Jest)") +![alt text](assets/bp-18-code-coverage2.jpeg "Налаштування покриття для кожного компонента (за допомогою Jest)")


    -## ⚪ ️ 4.2 Inspect coverage reports to detect untested areas and other oddities +## ⚪ ️ 4.2 Перевірте звіти про покриття, щоб виявити неперевірені області та інші дивацтва -:white_check_mark: **Роби:** Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invoked — you thought that the ‘PricingCalculator’ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales… Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not tested — being informed that 80% of the code is tested doesn’t tell whether the critical parts are covered. Generating reports is easy — just run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this data — you might find some gotchas +:white_check_mark: **Роби:** Деякі проблеми непомічені, і їх дуже важко знайти за допомогою традиційних інструментів. Насправді це не помилки, а скоріше дивовижна поведінка програми, яка може мати серйозні наслідки. Наприклад, часто деякі області коду ніколи або рідко викликаються — ви думали, що клас «PricingCalculator» завжди встановлює ціну продукту, але виявилося, що він насправді ніколи не викликається, хоча ми маємо 10000 продуктів у БД і багато продажів… Покриття коду звіти допомагають зрозуміти, чи працює програма так, як ви думаєте. Окрім цього, він також може підкреслити, які типи коду не перевірено —«інформація про те, що 80% коду перевірено, не говорить про те, чи охоплено критичні частини. Створювати звіти легко — просто запустіть свою програму в робочому стані або під час тестування з відстеженням покриття, а потім перегляньте кольорові звіти, у яких показано, як часто викликається кожна область коду. Якщо ви не поспішаєте зазирнути в ці дані — ви можете знайти деякі проблеми
    -❌ **Інакше:** If you don’t know which parts of your code are left un-tested, you don’t know where the issues might come from +❌ **Інакше:** Якщо ви не знаєте, які частини вашого коду залишилися необробленими, ви не знаєте, звідки можуть виникнути проблеми
    @@ -1712,30 +1712,30 @@ Implementation tips: You may want to configure your continuous integration (CI)
    -### :thumbsdown: Приклад антишаблону: What’s wrong with this coverage report? +### :thumbsdown: Приклад антишаблону: Що не так із цим звітом про покриття? -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) +На основі реального сценарію, коли ми відстежували використання нашої програми в QA та знаходили цікаві шаблони входу (Підказка: кількість помилок входу непропорційна, щось явно не так. Нарешті виявилося, що якась помилка інтерфейсу постійно вражає серверний API входу) -![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "What’s wrong with this coverage report?") +![alt text](assets/bp-19-coverage-yoni-goldberg-nodejs-consultant.png "Що не так із цим звітом про покриття?")


    -## ⚪ ️ 4.3 Measure logical coverage using mutation testing +## ⚪ ️ 4.3 Виміряйте логічне покриття за допомогою тестування на мутації -:white_check_mark: **Роби:** The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesn’t check if the tests actually tested anything — asserted for the right response. Like someone who’s traveling for business and showing his passport stamps — this doesn’t prove any work done, only that he visited few airports and hotels. +:white_check_mark: **Роби:** Традиційний показник покриття часто брехня: він може показати вам 100% покриття коду, але жодна з ваших функцій, навіть жодна, не повертає правильну відповідь. Як так? він просто вимірює, які рядки коду відвідав тест, але не перевіряє, чи тести справді щось перевіряли — заявлено для правильної відповіді. Як хтось, хто подорожує у справах і показує штампи в паспорті —«це не доводить жодної роботи, лише те, що він відвідав кілька аеропортів і готелів. -Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. [Stryker](https://stryker-mutator.io/) is a JavaScript library for mutation testing and the implementation is really neat: +Тестування на основі мутацій тут, щоб допомогти, вимірюючи кількість коду, який був фактично ПРОТЕСТОВАНИЙ, а не просто ВІДВІДАНИЙ. [Stryker](https://stryker-mutator.io/) — це бібліотека JavaScript для тестування на мутації, реалізація якої дуже гарна: -(1) it intentionally changes the code and “plants bugs”. For example the code newOrder.price===0 becomes newOrder.price!=0. This “bugs” are called mutations +(1) він навмисно змінює код і «підсаджує помилки». Наприклад, код newOrder.price===0 стає newOrder.price!=0. Ці «помилки» називаються мутаціями -(2) it runs the tests, if all succeed then we have a problem — the tests didn’t serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed. +(2) він запускає тести, якщо всі вдаються, тоді у нас є проблема —«тести не виконали своєї мети виявлення помилок, мутації так звані вижили. Якщо тести провалилися, то чудово, мутації були вбиті. -Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar +Знання того, що всі або більшість мутацій були знищені, дає набагато більшу впевненість, ніж традиційне покриття, а час налаштування подібний
    -❌ **Інакше:** You’ll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code +❌ **Інакше:** Ви будете обдурені, якщо повірите, що 85% покриття означає, що ваш тест виявить помилки в 85% вашого коду
    @@ -1743,9 +1743,9 @@ Knowing that all or most of the mutations were killed gives much higher confiden
    -### :thumbsdown: Приклад антишаблону: 100% coverage, 0% testing +### :thumbsdown: Приклад антишаблону: 100% покриття, 0% тестування -![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Using Stryker") +![](https://img.shields.io/badge/🔨%20Example%20using%20Stryker-blue.svg "Використання Stryker") ```javascript function addNewOrder(newOrder) { @@ -1758,33 +1758,33 @@ function addNewOrder(newOrder) { 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 +}); // Запускає 100% покриття коду, але нічого не перевіряє ```
    -### :clap: Приклад правильного виконання: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations) +### :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/bp-20-yoni-goldberg-mutation-testing.jpeg "Звіти Stryker, інструмент для перевірки мутацій, виявляють і підраховують кількість коду, який не перевіряється (Мутації)")


    -## ⚪ ️4.4 Preventing test code issues with Test linters +## ⚪ ️4.4 Запобігання проблемам тестового коду за допомогою тестових лінтерів -:white_check_mark: **Роби:** A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) will warn when a test is written at the global level (not a son of a describe() statement) or when tests are [skipped](https://mochajs.org/#inclusive-tests) which might lead to a false belief that all tests are passing. Similarly, [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) can, for example, warn when a test has no assertions at all (not checking anything) +: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) може, наприклад, попередити, коли тест взагалі не має тверджень (нічого не перевіряє)
    -❌ **Інакше:** Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests aren’t asserting for anything and many test suites were just skipped. Hopefully, you didn’t deploy anything based on this false observation +❌ **Інакше:** Побачивши 90% охоплення коду та 100% зелених тестів, ваше обличчя буде широко посміхатися, доки ви не зрозумієте, що багато тестів нічого не стверджують, а багато наборів тестів просто пропущено. Сподіваюся, ви нічого не розгорнули на основі цього помилкового спостереження
    Приклади коду
    -### :thumbsdown: Приклад антишаблону: A test case full of errors, luckily all are caught by Linters +### :thumbsdown: Приклад антишаблону: Тестовий приклад, повний помилок, на щастя, усі вони виявлені за допомогою лінтерів (Linters) ```javascript describe("Too short description", () => { @@ -1804,16 +1804,16 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test

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

    -## ⚪ ️ 5.1 Enrich your linters and abort builds that have linting issues +## ⚪ ️ 5.1 Збагачуйте ваші лінтери та припиняйте збірки, які мають проблеми з лінінгами -:white_check_mark: **Роби:** Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like [ESLint standard](https://www.npmjs.com/package/eslint-plugin-standard) or [Airbnb style](https://www.npmjs.com/package/eslint-config-airbnb)), consider including some specializing Linters like [eslint-plugin-chai-expect](https://www.npmjs.com/package/eslint-plugin-chai-expect) that can discover tests without assertions, [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise?activeTab=readme) can discover promises with no resolve (your code will never continue), [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security?activeTab=readme) which can discover eager regex expressions that might get used for DOS attacks, and [eslint-plugin-you-dont-need-lodash-underscore](https://www.npmjs.com/package/eslint-plugin-you-dont-need-lodash-underscore) is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash.\_map(…) +: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(…)
    -❌ **Інакше:** Consider a rainy day where your production keeps crashing but the logs don’t display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5 min linter setup could detect this TYPO and save your day +❌ **Інакше:** Уявіть собі дощовий день, коли ваша продуктивність продовжує давати збої, але журнали не відображають трасування стека помилок. Що трапилось? Ваш код помилково викинув об’єкт без помилки, і трасування стека було втрачено, що є вагомою причиною для того, щоб битися головою об цегляну стіну. 5-хвилинне налаштування Linter може виявити цю TYPO і врятувати ваш день
    @@ -1821,22 +1821,22 @@ it("Test name", () => {*//error:no-identical-title. Assign unique titles to test
    -### :thumbsdown: Приклад антишаблону: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug +### :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") +![alt text](assets/bp-21-yoni-goldberg-eslint.jpeg "Помилково створено неправильний об’єкт Error, трасування стека для цієї помилки не з’явиться. На щастя, ESLint виявляє наступну виробничу помилку")


    -## ⚪ ️ 5.2 Shorten the feedback loop with local developer-CI +## ⚪ ️ 5.2 Скоротіть цикл зворотного зв’язку за допомогою локального Developer-CI -:white_check_mark: **Роби:** Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the [feedback loop](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2-feedback-loops/). Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module. +:white_check_mark: **Роби:** Використовуєте CI з блискучою перевіркою якості, як-от тестування, лінтування, перевірка вразливостей тощо? Допоможіть розробникам запустити цей конвеєр також локально, щоб отримати миттєвий відгук і скоротити [цикл зворотного зв’язку](https://www.gocd.org/2016/03/15/are-you-ready-for-continuous-delivery-part-2 -петлі зворотного зв'язку/). чому ефективний процес тестування складається з багатьох ітераційних циклів: (1) тестування -> (2) зворотній зв'язок -> (3) рефакторинг. Чим швидший зворотній зв’язок, тим більше ітерацій покращення може виконати розробник для кожного модуля та покращити результати. З іншого боку, коли зворотній зв’язок надходить із запізненням, менше ітерацій покращення може бути упаковано в один день, команда може вже перейти до іншої теми/завдання/модуля та не готова вдосконалювати цей модуль. -Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com/docs/2.0/local-cli/)) allow running the pipeline locally. Some commercial tools like [wallaby provide highly-valuable & testing insights](https://wallabyjs.com/) as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities) — use tools like [concurrently](https://www.npmjs.com/package/concurrently) for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one command — e.g. ‘npm run quality’ — to get instant feedback. Consider also aborting a commit if the quality check failed using a githook ([husky can help](https://github.com/typicode/husky)) +На практиці деякі постачальники 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))
    -❌ **Інакше:** When the quality results arrive the day after the code, testing doesn’t become a fluent part of development rather an after the fact formal artifact +❌ **Інакше:** Коли якісні результати надходять наступного дня після коду, тестування не стає плавною частиною розробки, а формальним артефактом.
    @@ -1844,7 +1844,7 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com
    -### :clap: Приклад правильного виконання: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code +### :clap: Приклад правильного виконання: Сценарії npm, які виконують перевірку якості коду, усі виконуються паралельно на вимогу або коли розробник намагається надіслати новий код ```javascript "scripts": { @@ -1869,14 +1869,14 @@ Practically, some CI vendors (Example: [CircleCI local CLI](https://circleci.com

    -## ⚪ ️5.3 Perform e2e testing over a true production-mirror +## ⚪ ️5.3 Виконайте тестування e2e на справжньому робочому дзеркалі -:white_check_mark: **Роби:** End to end (e2e) testing are the main challenge of every CI pipeline — creating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: [Docker-compose](https://serverless.com/) allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with [‘AWS Local’](https://github.com/localstack/localstack) to work with a stub of the real AWS services. If you went [serverless](https://serverless.com/) multiple frameworks like serverless and [AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/serverless_app.html) allows the local invocation of FaaS code. +: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. -The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a ‘minimized-Kubernetes’ using tools like [Minikube](https://kubernetes.io/docs/setup/minikube/) and [MicroK8s](https://microk8s.io/) which resemble the real thing only come with less overhead. Another approach is testing over a remote ‘real-Kubernetes’, some CI providers (e.g. [Codefresh](https://codefresh.io/)) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes. +Величезна екосистема Kubernetes ще має формалізувати стандартний зручний інструмент для локального та CI-дзеркалювання, хоча часто запускається багато нових інструментів. Одним із підходів є запуск «мінімізованого Kubernetes» за допомогою таких інструментів, як [Minikube](https://kubernetes.io/docs/setup/minikube/) і [MicroK8s](https://microk8s.io/), які нагадують справжній річ лише з меншими накладними витратами. Інший підхід полягає в тестуванні на віддаленому «реальному Kubernetes». Деякі постачальники CI (наприклад, [Codefresh](https://codefresh.io/)) мають власну інтеграцію з середовищем Kubernetes і дозволяють легко запускати конвеєр CI через реальний інші дозволяють створювати спеціальні сценарії для віддаленого Kubernetes.
    -❌ **Інакше:** Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated +❌ **Інакше:** Використання різних технологій для виробництва та тестування вимагає підтримки двох моделей розгортання та роз’єднує розробників і команду операцій
    @@ -1884,7 +1884,7 @@ The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for
    -### :clap: Example: a CI pipeline that generates Kubernetes cluster on the fly ([Credit: Dynamic-environments Kubernetes](https://container-solutions.com/dynamic-environments-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
    @@ -1892,11 +1892,11 @@ The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for

    -## ⚪ ️5.4 Parallelize test execution +## ⚪ ️5.4 Паралелізуйте виконання тесту -:white_check_mark: **Роби:** When done right, testing is your 24/7 friend providing almost instant feedback. In practice, executing 500 CPU-bounded unit test on a single thread can take too long. Luckily, modern test runners and CI platforms (like [Jest](https://github.com/facebook/jest), [AVA](https://github.com/avajs/ava) and [Mocha extensions](https://github.com/yandex/mocha-parallel-tests)) can parallelize the test into multiple processes and achieve significant improvement in feedback time. Some CI vendors do also parallelize tests across containers (!) which shortens the feedback loop even further. Whether locally over multiple processes, or over some cloud CLI using multiple machines — parallelizing demand keeping the tests autonomous as each might run on different processes +: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 з використанням кількох машин — розпаралелювання попиту, зберігаючи автономність тестів, оскільки кожен може виконуватися на різних процесах -❌ **Інакше:** Getting test results 1 hour long after pushing new code, as you already code the next features, is a great recipe for making testing less relevant +❌ **Інакше:** Отримання результатів тестування через 1 годину після введення нового коду, оскільки ви вже кодуєте наступні функції, є чудовим рецептом для того, щоб зробити тестування менш актуальним
    @@ -1904,19 +1904,19 @@ The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for
    -### :clap: Приклад правильного виконання: Mocha parallel & Jest easily outrun the traditional Mocha thanks to testing parallelization ([Credit: JavaScript Test-Runners Benchmark](https://medium.com/dailyjs/javascript-test-runners-benchmark-3a78d4117b4)) +### :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)") +![alt text](assets/bp-24-yonigoldberg-jest-parallel.png "Mocha parallel & Jest легко випереджають традиційну Mocha завдяки розпаралелюванню тестування")


    -## ⚪ ️5.5 Stay away from legal issues using license and plagiarism check +## ⚪ ️5.5 Тримайтеся подалі від юридичних проблем, використовуючи ліцензію та перевірку на плагіат -:white_check_mark: **Роби:** Licensing and plagiarism issues are probably not your main concern right now, but why not tick this box as well in 10 minutes? A bunch of npm packages like [license check](https://www.npmjs.com/package/license-checker) and [plagiarism check](https://www.npmjs.com/package/plagiarism-checker) (commercial with free plan) can be easily baked into your CI pipeline and inspect for sorrows like dependencies with restrictive licenses or code that was copy-pasted from Stack Overflow and apparently violates some copyrights +:white_check_mark: **Роби:** Проблеми ліцензування та плагіату, ймовірно, не є вашою головною проблемою зараз, але чому б також не поставити галочку в цьому полі через 10 хвилин? Купа пакетів npm, таких як [перевірка ліцензії](https://www.npmjs.com/package/license-checker) і [перевірка на плагіат](https://www.npmjs.com/package/plagiarism-checker) ( рекламний ролик із безкоштовним планом) можна легко вставити у ваш конвеєр CI та перевірити на наявність таких проблем, як залежності з обмежувальними ліцензіями або код, який було скопійовано з Stack Overflow і, очевидно, порушує деякі авторські права -❌ **Інакше:** Unintentionally, developers might use packages with inappropriate licenses or copy paste commercial code and run into legal issues +❌ **Інакше:** Ненавмисно розробники можуть використати пакети з невідповідними ліцензіями або скопіювати комерційний код і зіткнутися з юридичними проблемами
    @@ -1943,11 +1943,11 @@ license-checker --summary --failOn BSD

    -## ⚪ ️5.6 Constantly inspect for vulnerable dependencies +## ⚪ ️5.6 Постійно перевіряйте наявність вразливих залежностей -:white_check_mark: **Роби:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community tools such as [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), or commercial tools like [snyk](https://snyk.io/) (offer also a free community version). Both can be invoked from your CI on every build +:white_check_mark: **Роби:** Навіть найавторитетніші залежності, такі як Express, мають відомі вразливості. Це можна легко приборкати за допомогою інструментів спільноти, таких як [npm audit](https://docs.npmjs.com/getting-started/running-a-security-audit), або комерційних інструментів, таких як [snyk](https:// snyk.io/) (пропонуємо також безкоштовну версію спільноти). Обидва можна викликати з вашого КІ під час кожної збірки -❌ **Інакше:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious +❌ **Інакше:** Щоб захистити ваш код від уразливостей без спеціальних інструментів, потрібно постійно стежити за публікаціями в Інтернеті про нові загрози. Досить нудно
    @@ -1955,26 +1955,25 @@ license-checker --summary --failOn BSD
    -### :clap: Example: NPM Audit result +### :clap: Приклад: Результат аудиту NPM -![alt text](assets/bp-26-npm-audit-snyk.png "NPM Audit result") +![alt text](assets/bp-26-npm-audit-snyk.png "Результат аудиту NPM")


    -## ⚪ ️5.7 Automate dependency updates +## ⚪ ️5.7 Автоматизуйте оновлення залежностей -:white_check_mark: **Роби:** Yarn and npm latest introduction of package-lock.json introduced a serious challenge (the road to hell is paved with good intentions) — by default now, packages are no longer getting updates. Even a team running many fresh deployments with ‘npm install’ & ‘npm update’ won’t get any new updates. This leads to subpar dependent packages versions at best or to vulnerable code at worst. Teams now rely on developers goodwill and memory to manually update the package.json or use tools [like ncu](https://www.npmjs.com/package/npm-check-updates) manually. A more reliable way could be to automate the process of getting the most reliable dependency versions, though there are no silver bullet solutions yet there are two possible automation roads: +:white_check_mark: **Роби:** Yarn і npm останнє представлення package-lock.json поставило серйозну проблему (дорога в пекло вимощена благими намірами) — тепер за замовчуванням пакунки більше не отримують оновлення. Навіть команда, яка виконує багато свіжих розгортань за допомогою «встановлення npm» і «оновлення npm», не отримає нових оновлень. У кращому випадку це призводить до неповноцінних версій пакунків або до вразливого коду в гіршому. Команди тепер покладаються на добру волю та пам’ять розробників, щоб вручну оновити package.json або використовувати інструменти [наприклад, ncu](https://www.npmjs.com/package/npm-check-updates) вручну. Надійнішим способом може бути автоматизація процесу отримання найнадійніших версій залежностей, хоча немає універсальних рішень, але є два можливі шляхи автоматизації: -(1) CI can fail builds that have obsolete dependencies — using tools like [‘npm outdated’](https://docs.npmjs.com/cli/outdated) or ‘npm-check-updates (ncu)’ . Doing so will enforce developers to update dependencies. +(1) CI може давати збій збіркам із застарілими залежностями — використанням таких інструментів, як [‘npm outdated’](https://docs.npmjs.com/cli/outdated) або ‘npm-check-updates (ncu)’ . Це змусить розробників оновити залежності. -(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policy — updating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, [see the](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) eslint-scope incident). +(2) Використовуйте комерційні інструменти, які сканують код і автоматично надсилають запити на отримання з оновленими залежностями. Залишається одне цікаве питання: якою має бути політика оновлення залежностей —«оновлення кожного патча створює надто багато накладних витрат, оновлення безпосередньо під час випуску основного може вказувати на нестабільну версію (багато пакетів виявляються вразливими в перші дні після випуску, [перегляньте](https://nodesource.com/blog/a-high-level-post-mortem-of-the-eslint-scope-security-incident/) інцидент eslint-scope). -An efficient update policy may allow some ‘vesting period’ — let the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8) -
    +Ефективна політика оновлення може дозволити певний «період набуття прав» — нехай код відстає від @latest на деякий час і версії, перш ніж вважати локальну копію застарілою (наприклад, локальна версія — 1.3.1, а версія сховища — 1.3.8)
    -❌ **Інакше:** Your production will run packages that have been explicitly tagged by their author as risky +❌ **Інакше:** У вашому виробництві працюватимуть пакунки, які були явно позначені їх автором як ризиковані
    @@ -1982,31 +1981,31 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -### :clap: Example: [ncu](https://www.npmjs.com/package/npm-check-updates) can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions +### :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") +![alt text](assets/bp-27-yoni-goldberg-npm.png "можна використовувати вручну або в конвеєрі CI, щоб визначити, наскільки код відстає від останніх версій")


    -## ⚪ ️ 5.8 Other, non-Node related, CI tips +## ⚪ ️ 5.8 Інші поради CI, не пов’язані з Node -:white_check_mark: **Роби:** This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known +: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

    -❌ **Інакше:** You‘ll miss years of wisdom +❌ **Інакше:** Ви пропустите роки мудрості

    -## ⚪ ️ 5.9 Build matrix: Run the same CI steps using multiple Node versions +## ⚪ ️ 5.9 Матриця побудови: виконуйте ті самі кроки CI, використовуючи кілька версій Node -:white_check_mark: **Роби:** Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for others — some CI vendors support a feature called ‘Matrix’ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesn’t support Matrix might have extensions or tweaks to allow that +:white_check_mark: **Роби:** Перевірка якості пов’язана з інтуїцією, чим більше ви охопите, тим більше вам пощастить у виявленні проблем на ранній стадії. Під час розробки пакетів для багаторазового використання або запуску виробництва для кількох клієнтів із різними конфігураціями та версіями Node, CI має запустити конвеєр тестів для всіх перестановок конфігурацій. Наприклад, якщо припустити, що ми використовуємо MySQL для одних клієнтів, а Postgres для інших —«деякі постачальники CI підтримують функцію під назвою «Матриця», яка дозволяє виконувати тестування всіх перестановок MySQL, Postgres і кількох версій Node, таких як 8, 9 і 10. Це робиться лише за допомогою конфігурації без будь-яких додаткових зусиль (за умови, що у вас є тестування чи будь-які інші перевірки якості). Інші КІ, які не підтримують Matrix, можуть мати розширення або налаштування, щоб дозволити це
    -❌ **Інакше:** So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues? +❌ **Інакше:** Отже, після всієї цієї важкої роботи з написання тестування, ми дозволимо помилкам прокрадатися лише через проблеми з конфігурацією?
    @@ -2014,7 +2013,7 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -### :clap: Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions +### :clap: Приклад: Використання визначення збірки Travis (постачальник CI) для запуску одного тесту на кількох версіях Node
    language: node_js
    node_js:
    - "7"
    - "6"
    - "5"
    - "4"
    install:
    - npm install
    script:
    - npm run test
    @@ -2029,11 +2028,11 @@ An efficient update policy may allow some ‘vesting period’ — let the c
    -**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) +**Опис:** Я незалежний консультант, який працює з компаніями зі списку Fortune 500 і гаражними стартапами над вдосконаленням їхніх додатків JS і Node.js. Більше ніж будь-яка інша тема мене захоплює, і я прагну оволодіти мистецтвом тестування. Я також автор [найкращих практик Node.js](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) +**📗 Онлайн-курс:** Сподобався цей посібник і ви бажаєте вдосконалити свої навички тестування? Відвідайте мій комплексний курс [Тестування Node.js і JavaScript від А до Я](https://www.testjavascript.com)
    @@ -2049,30 +2048,29 @@ An efficient update policy may allow some ‘vesting period’ — let the c ## [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 +Подбав про те, щоб переглянути, вдосконалити, відшліфувати та відшліфувати всі тексти +**Опис:** full-stack веб-інженер, ентузіаст Node.js та GraphQL

    ## [Ido Richter](https://github.com/idori) -**Role:** Concept, design and great advice +**Роль:** Concept, design and great advice -**About:** A savvy frontend developer, CSS expert and emojis freak +**Опис:** Кмітливий розробник інтерфейсу, експерт із CSS і фанат емодзі ## [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. +**Опис:** Любить працювати над проектами Node.js і безпекою веб-додатків. -## Contributors ✨ +## Автори ✨ -Thanks goes to these wonderful people who have contributed to this repository! +Дякуємо цим чудовим людям, які зробили внесок у це сховище! From 96480d0a00f7702bf552702bb4713b8aea78a6e5 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:06:30 +0000 Subject: [PATCH 471/502] docs: update readme.md [skip ci] --- readme.md | 105 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/readme.md b/readme.md index 2a8134c3..70dcbeb5 100644 --- a/readme.md +++ b/readme.md @@ -2083,57 +2083,60 @@ 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

    🌍 💻
    Scott Davis
    Scott Davis

    🖋
    Adrien REDON
    Adrien REDON

    🖋
    Stefano Magni
    Stefano Magni

    🖋
    Yeoh Joer
    Yeoh Joer

    🖋
    Jhonny Moreira
    Jhonny Moreira

    🖋
    Ian Germann
    Ian Germann

    🖋
    Hafez
    Hafez

    🖋
    Ruxandra Fediuc
    Ruxandra Fediuc

    🖋
    Jack
    Jack

    🖋
    Peter Carrero
    Peter Carrero

    🖋
    Huhgawz
    Huhgawz

    🖋
    Haakon Borch
    Haakon Borch

    🖋
    Jaime Mendoza
    Jaime Mendoza

    🖋
    Cameron Dunford
    Cameron Dunford

    🖋
    John Gee
    John Gee

    🖋
    Aurelijus Rožėnas
    Aurelijus Rožėnas

    🖋
    Aaron
    Aaron

    🖋
    Tom Nagle
    Tom Nagle

    🖋
    Yves yao
    Yves yao

    🖋
    Userbit
    Userbit

    🖋
    Glaucia Lemos
    Glaucia Lemos

    🚧
    koooge
    koooge

    🖋
    Michal
    Michal

    🖋
    roywalker
    roywalker

    🖋
    dangen
    dangen

    🖋
    biesiadamich
    biesiadamich

    🖋
    Yanlin Jiang
    Yanlin Jiang

    🖋
    sanguino
    sanguino

    🖋
    Morgan
    Morgan

    🖋
    Lukas Bischof
    Lukas Bischof

    ⚠️ 🖋
    JuanMa Ruiz
    JuanMa Ruiz

    🖋
    Luís Ângelo Rodrigues Jr.
    Luís Ângelo Rodrigues Jr.

    🖋
    José Fernández
    José Fernández

    🖋
    Alejandro Gutierrez Barcenilla
    Alejandro Gutierrez Barcenilla

    🖋
    Jason
    Jason

    🖋
    Otavio Araujo
    Otavio Araujo

    ⚠️ 🖋
    Alex Ivanov
    Alex Ivanov

    🖋
    Yiqiao Xu
    Yiqiao Xu

    🖋
    YuBin, Hsu
    YuBin, Hsu

    🌍 💻
    Ali Azmoodeh
    Ali Azmoodeh

    🖋
    From 91a30571e03485a282fc10dde906597c2dfb5a18 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:06:31 +0000 Subject: [PATCH 472/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 52b52910..21426a42 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -360,10 +360,20 @@ "translation", "code" ] + }, + { + "login": "TREER00T", + "name": "Ali Azmoodeh", + "avatar_url": "https://avatars.githubusercontent.com/u/76606342?v=4", + "profile": "https://github.com/TREER00T", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", "projectOwner": "goldbergyoni", "repoType": "github", - "repoHost": "https://github.com" + "repoHost": "https://github.com", + "commitConvention": "angular" } From 71c0a1681fa9d95daaac1c3c6220270dad10f7a9 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:18:07 +0000 Subject: [PATCH 473/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 21426a42..e97af5c0 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -369,6 +369,15 @@ "contributions": [ "content" ] + }, + { + "login": "Saimon398", + "name": "Alex Popov", + "avatar_url": "https://avatars.githubusercontent.com/u/71539667?v=4", + "profile": "https://github.com/Saimon398", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 3677c165b664c3541799adaf435893fff589c4c9 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:18:15 +0000 Subject: [PATCH 474/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 0f7ab363..e79a549e 100644 --- a/readme.md +++ b/readme.md @@ -2133,6 +2133,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Yiqiao Xu
    Yiqiao Xu

    🖋 YuBin, Hsu
    YuBin, Hsu

    🌍 💻 Ali Azmoodeh
    Ali Azmoodeh

    🖋 + Alex Popov
    Alex Popov

    🖋 From c617680ebd9b8708155053f84718f65e79898ec3 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:21:34 +0000 Subject: [PATCH 475/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index e79a549e..901516f8 100644 --- a/readme.md +++ b/readme.md @@ -2134,6 +2134,7 @@ Thanks goes to these wonderful people who have contributed to this repository! YuBin, Hsu
    YuBin, Hsu

    🌍 💻 Ali Azmoodeh
    Ali Azmoodeh

    🖋 Alex Popov
    Alex Popov

    🖋 + Serhii Shramko
    Serhii Shramko

    🖋 From 9461f420c3a34683ded778b39a11643a4c396ddc Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:21:35 +0000 Subject: [PATCH 476/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index e97af5c0..4c1850d2 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -378,6 +378,15 @@ "contributions": [ "content" ] + }, + { + "login": "Shramkoweb", + "name": "Serhii Shramko", + "avatar_url": "https://avatars.githubusercontent.com/u/42001531?v=4", + "profile": "http://shramko.dev", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From b19800c16a956fb547277d129fd11d98d5865665 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Wed, 14 Dec 2022 08:22:26 -0500 Subject: [PATCH 477/502] Update readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 901516f8..7ee8cc00 100644 --- a/readme.md +++ b/readme.md @@ -40,6 +40,7 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇧🇷[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) - 🇯🇵[Japanese (draft)](https://github.com/yuichkun/javascript-testing-best-practices/blob/master/readme-jp.md) - Courtesy of [Yuichi Yogo](https://github.com/yuichkun) and [ryo](https://github.com/kawamataryo) +- 🇺🇦[Ukrainian](readme-ua.md) - Courtesy of [Serhii Shramko](https://github.com/Shramkoweb) - 🇹🇼[Traditional Chinese](readme-zh-TW.md) - Courtesy of [Yubin Hsu](https://github.com/yubinTW) - 🇮🇷[Persian](readme-pr-fr.md) - Courtesy of [Ali Azmoodeh](https://github.com/TREER00T) - Want to translate to your own language? please open an issue 💜 From e65651ffb162a8cf21502d1c27a71a03e020aef4 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:26:08 +0000 Subject: [PATCH 478/502] docs: update readme.md [skip ci] --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index 7ee8cc00..221e0906 100644 --- a/readme.md +++ b/readme.md @@ -2137,6 +2137,9 @@ Thanks goes to these wonderful people who have contributed to this repository! Alex Popov
    Alex Popov

    🖋 Serhii Shramko
    Serhii Shramko

    🖋 + + Yugo Sakamoto
    Yugo Sakamoto

    🖋 + From 680af5af1bcae76052f0659e13936d535d1cceda Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:26:09 +0000 Subject: [PATCH 479/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 4c1850d2..14bf6871 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -387,6 +387,15 @@ "contributions": [ "content" ] + }, + { + "login": "yugoccp", + "name": "Yugo Sakamoto", + "avatar_url": "https://avatars.githubusercontent.com/u/1724114?v=4", + "profile": "https://github.com/yugoccp", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 40994a22cc7813c11a3102f575d8678b2f2ea5dc Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:31:12 +0000 Subject: [PATCH 480/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 221e0906..5e871081 100644 --- a/readme.md +++ b/readme.md @@ -2139,6 +2139,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Yugo Sakamoto
    Yugo Sakamoto

    🖋 + Frazer Smith
    Frazer Smith

    🖋 From 21d598cb48e66b447ff26c0ef4727a74e2206e1c Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:31:13 +0000 Subject: [PATCH 481/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 14bf6871..7e4ffe2e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -396,6 +396,15 @@ "contributions": [ "content" ] + }, + { + "login": "Fdawgs", + "name": "Frazer Smith", + "avatar_url": "https://avatars.githubusercontent.com/u/43814140?v=4", + "profile": "https://yeovilhospital.co.uk/", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 6f9c0778bc130a9dfd305f8133050d99bb8e02ac Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:31:58 +0000 Subject: [PATCH 482/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 2dd2f82e..d52123b3 100644 --- a/readme.md +++ b/readme.md @@ -2140,6 +2140,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Yugo Sakamoto
    Yugo Sakamoto

    🖋 Frazer Smith
    Frazer Smith

    🖋 + Wralith
    Wralith

    🖋 From a4cb7f27a969530fc3bc0cafe1b0328b1ef758a6 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:31:59 +0000 Subject: [PATCH 483/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 7e4ffe2e..f24fbb49 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -405,6 +405,15 @@ "contributions": [ "content" ] + }, + { + "login": "wralith", + "name": "Wralith", + "avatar_url": "https://avatars.githubusercontent.com/u/75392169?v=4", + "profile": "https://github.com/wralith", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From d4b80e5a6f7989bbf12f6e36bf8ff200723c1abf Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:33:38 +0000 Subject: [PATCH 484/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index f0423d74..48dfcb5f 100644 --- a/readme.md +++ b/readme.md @@ -2141,6 +2141,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Yugo Sakamoto
    Yugo Sakamoto

    🖋 Frazer Smith
    Frazer Smith

    🖋 Wralith
    Wralith

    🖋 + Harang
    Harang

    🖋 From fc4495b69ae14996b697216f539bc96c891c1e23 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:33:39 +0000 Subject: [PATCH 485/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index f24fbb49..233ce6a3 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -414,6 +414,15 @@ "contributions": [ "content" ] + }, + { + "login": "saseungmin", + "name": "Harang", + "avatar_url": "https://avatars.githubusercontent.com/u/60910665?v=4", + "profile": "https://haranglog.tistory.com", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 26dbb53b750d2a02f096b879026e62feafe1503a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:44:10 +0000 Subject: [PATCH 486/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 9495eea1..10b3088a 100644 --- a/readme.md +++ b/readme.md @@ -2142,6 +2142,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Frazer Smith
    Frazer Smith

    🖋 Wralith
    Wralith

    🖋 Harang
    Harang

    🖋 + rcanelav
    rcanelav

    🖋 From 92e3e5aad824116d670258ae2592de9cc7172d03 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:44:11 +0000 Subject: [PATCH 487/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 233ce6a3..69c2b513 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -423,6 +423,15 @@ "contributions": [ "content" ] + }, + { + "login": "rcanelav", + "name": "rcanelav", + "avatar_url": "https://avatars.githubusercontent.com/u/64812826?v=4", + "profile": "https://github.com/rcanelav", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From c08c0e6caa12a8f0fdd7a921e69d822ac90c9052 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:46:37 +0000 Subject: [PATCH 488/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 10b3088a..9b0612d8 100644 --- a/readme.md +++ b/readme.md @@ -2143,6 +2143,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Wralith
    Wralith

    🖋 Harang
    Harang

    🖋 rcanelav
    rcanelav

    🖋 + Drew Wilson
    Drew Wilson

    🖋 From 15a37310fb0fa4140d1f348353d0b2c1407ec10f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:46:38 +0000 Subject: [PATCH 489/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 69c2b513..7c21ad13 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -432,6 +432,15 @@ "contributions": [ "content" ] + }, + { + "login": "drewrwilson", + "name": "Drew Wilson", + "avatar_url": "https://avatars.githubusercontent.com/u/4324656?v=4", + "profile": "https://github.com/drewrwilson", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 457de5cd1722af640a16673d2f738fd83f0bbe75 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:48:15 +0000 Subject: [PATCH 490/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 9b0612d8..bc60e3ce 100644 --- a/readme.md +++ b/readme.md @@ -2144,6 +2144,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Harang
    Harang

    🖋 rcanelav
    rcanelav

    🖋 Drew Wilson
    Drew Wilson

    🖋 + XtLee
    XtLee

    🖋 From 0ad6a9a34c1e6b38e67f466c6f6624f0070ad437 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:48:16 +0000 Subject: [PATCH 491/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 7c21ad13..9a1ce9ee 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -441,6 +441,15 @@ "contributions": [ "content" ] + }, + { + "login": "XtLee", + "name": "XtLee", + "avatar_url": "https://avatars.githubusercontent.com/u/30145777?v=4", + "profile": "https://github.com/XtLee", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From f263237cc95db5632164a797f99e7391c8ca2704 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:49:55 +0000 Subject: [PATCH 492/502] docs: update readme.md [skip ci] --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index 393d8fa5..7d6cdfe2 100644 --- a/readme.md +++ b/readme.md @@ -2147,6 +2147,9 @@ Thanks goes to these wonderful people who have contributed to this repository! Drew Wilson
    Drew Wilson

    🖋 XtLee
    XtLee

    🖋 + + Simon Ingeson
    Simon Ingeson

    🖋 + From 1967b1dbeb0859e68e9827741cd079e6faddd774 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:49:56 +0000 Subject: [PATCH 493/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 9a1ce9ee..672bc4d2 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -450,6 +450,15 @@ "contributions": [ "content" ] + }, + { + "login": "smonn", + "name": "Simon Ingeson", + "avatar_url": "https://avatars.githubusercontent.com/u/44818?v=4", + "profile": "https://www.smonn.se", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From f8d45eb8024938376f85365892763a8a3fe6eab2 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:50:51 +0000 Subject: [PATCH 494/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index c1a3c456..d57c7dee 100644 --- a/readme.md +++ b/readme.md @@ -2149,6 +2149,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Simon Ingeson
    Simon Ingeson

    🖋 + elfacu0
    elfacu0

    🖋 From 6759ddb5b9390f08f93006999897613d85fcb05f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:50:52 +0000 Subject: [PATCH 495/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 672bc4d2..a653d878 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -459,6 +459,15 @@ "contributions": [ "content" ] + }, + { + "login": "elfacu0", + "name": "elfacu0", + "avatar_url": "https://avatars.githubusercontent.com/u/30785449?v=4", + "profile": "https://github.com/elfacu0", + "contributions": [ + "content" + ] } ], "projectName": "javascript-testing-best-practices", From 0cd0e2e453239ad5843faeb3ad8579654cde231c Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:51:47 +0000 Subject: [PATCH 496/502] docs: update readme.md [skip ci] --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index d57c7dee..9752d187 100644 --- a/readme.md +++ b/readme.md @@ -2150,6 +2150,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Simon Ingeson
    Simon Ingeson

    🖋 elfacu0
    elfacu0

    🖋 + jorbelca
    jorbelca

    🖋 From eb154f51f963ce384ebb5ce6b0f0193c9305a3bc Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:51:48 +0000 Subject: [PATCH 497/502] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index a653d878..db0972e7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -468,6 +468,15 @@ "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", From cff5db64fa4fcb7e206c7cb65fe3e8c32fa14048 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Wed, 14 Dec 2022 08:54:21 -0500 Subject: [PATCH 498/502] Update readme.md --- readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 9752d187..fac5f7ed 100644 --- a/readme.md +++ b/readme.md @@ -40,9 +40,11 @@ Start by understanding the ubiquitous testing practices that are the foundation - 🇧🇷[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) - 🇯🇵[Japanese (draft)](https://github.com/yuichkun/javascript-testing-best-practices/blob/master/readme-jp.md) - Courtesy of [Yuichi Yogo](https://github.com/yuichkun) and [ryo](https://github.com/kawamataryo) -- 🇺🇦[Ukrainian](readme-ua.md) - Courtesy of [Serhii Shramko](https://github.com/Shramkoweb) - 🇹🇼[Traditional Chinese](readme-zh-TW.md) - Courtesy of [Yubin Hsu](https://github.com/yubinTW) +- 🇺🇦[Ukrainian](readme-ua.md) - Courtesy of [Serhii Shramko](https://github.com/Shramkoweb) - 🇮🇷[Persian](readme-pr-fr.md) - Courtesy of [Ali Azmoodeh](https://github.com/TREER00T) +- 🇷🇺[Russian](readme-ru.md) - Courtesy of [Alex Popov](https://github.com/Saimon398) + - Want to translate to your own language? please open an issue 💜

    From cdea2444a25622d664a58f4612915961832dd933 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 23 Jul 2023 20:02:10 +0300 Subject: [PATCH 499/502] Update readme.md --- readme.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index fac5f7ed..d3bb8299 100644 --- a/readme.md +++ b/readme.md @@ -21,13 +21,9 @@ Start by understanding the ubiquitous testing practices that are the foundation
    -## 🚀 We have an [official Node.js starter - Practica.js](https://github.com/practicajs/practica). Use it to generate a new solution skeleton with testing baked in, Or just it to learn by testing code examples +### Written By Yoni Goldberg - A JavaScript & Node.js consultant -### Written By 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 [7 hours of video](https://www.testjavascript.com) -- [Follow me on Twitter](https://twitter.com/goldbergyoni/) +## 🥳 Exciting news: After two years of recording and editing, I've just released my super-comprehensive testing course [It's now on a 🎁 limited-time sale during July](https://testjavascript.com/)
    From 5542c60d106c2e1566279155eabb37f339646a67 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 23 Jul 2023 20:03:10 +0300 Subject: [PATCH 500/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index d3bb8299..ac0397fa 100644 --- a/readme.md +++ b/readme.md @@ -23,7 +23,7 @@ Start by understanding the ubiquitous testing practices that are the foundation ### Written By Yoni Goldberg - A JavaScript & Node.js consultant -## 🥳 Exciting news: After two years of recording and editing, I've just released my super-comprehensive testing course [It's now on a 🎁 limited-time sale during July](https://testjavascript.com/) +### 👨‍🏫 Exciting news: After two years of recording and editing, I've just released my super-comprehensive testing course [It's now on a 🎁 limited-time sale during July](https://testjavascript.com/)
    From 3dc36fd7b60ccfda604a3164023726baceec48e5 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Sun, 23 Jul 2023 20:03:44 +0300 Subject: [PATCH 501/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index ac0397fa..a9b67d07 100644 --- a/readme.md +++ b/readme.md @@ -23,7 +23,7 @@ Start by understanding the ubiquitous testing practices that are the foundation ### Written By Yoni Goldberg - A JavaScript & Node.js consultant -### 👨‍🏫 Exciting news: After two years of recording and editing, I've just released my super-comprehensive testing course [It's now on a 🎁 limited-time sale during July](https://testjavascript.com/) +### 👨‍🏫 Exciting news: I've just released my super-comprehensive testing course after two years of recording and editing. [It's now on a 🎁 limited-time sale during July](https://testjavascript.com/)
    From 63a2bb07bb718d0a34a9c1249ef0df9b1266dad8 Mon Sep 17 00:00:00 2001 From: Yoni Goldberg Date: Tue, 8 Aug 2023 16:58:43 +0300 Subject: [PATCH 502/502] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index a9b67d07..211643f3 100644 --- a/readme.md +++ b/readme.md @@ -23,7 +23,7 @@ Start by understanding the ubiquitous testing practices that are the foundation ### Written By Yoni Goldberg - A JavaScript & Node.js consultant -### 👨‍🏫 Exciting news: I've just released my super-comprehensive testing course after two years of recording and editing. [It's now on a 🎁 limited-time sale during July](https://testjavascript.com/) +### 👨‍🏫 Exciting news: I've just released my super-comprehensive testing course after two years of recording and editing. [Less than 48 hours left for the 🎁 special launch deal](https://testjavascript.com/)