做者:Valentino Gagliardi翻译:疯狂的技术宅javascript
原文:https://www.valentinog.com/bl...html
未经容许严禁转载前端
在技术术语中测试意味着检查咱们的代码是否符合某些预期。例如:给定一些输入,一个名为“transformer”的函数应返回预期的输出。java
有许多类型的测试,很快你就会被术语所淹没,让咱们长话短书。测试分为三大类:react
在这个 Jest 教程中,咱们将仅涵盖单元测试,但在文章的最后,你将找到更多用于其余类型测试的资源。git
Jest 是一个 JavaScript 测试运行器,即用于建立、运行和结构化测试的 JavaScript 库。 Jest 做为 NPM 包发布,你能够将其安装在任何 JavaScript 项目中。 Jest 是目前最受欢迎的测试运行器之一,也是 Create React App 的默认选择。程序员
当谈到测试时,即便是简单的代码块也会使初学者瘫痪。最多见的问题是“我怎么知道要测试些什么?”。若是你正在编写 Web 应用,那么一个好的起点就是测试应用的每一个页面和每一个用户交互。但 Web 应用也由单元代码组成,如函数和模块,也须要进行测试。不少时候有两种状况:github
该怎么办?对于这两种状况,你能够经过考虑代码来检查,以检查给定函数是否产生预期结果**。如下是典型测试流程的样子:面试
应该怎么办? 对于这两种状况,你能够经过将测试看做检查给定函数是否产生预期结果的代码来帮助本身。 如下是典型测试流程的样子:正则表达式
就是这样。若是你按照这些术语思考,测试再也不可怕:输入 - 预期输出 - 断言结果。接下来咱们还会看到一个方便的工具,用于检查几乎确切的测试内容。如今就动手学习 Jest!
与每一个 JavaScript 项目同样,你须要一个 NPM 环境(确保在你的系统上安装了 Node)。建立一个新文件夹并用如下命令初始化项目:
mkdir getting-started-with-jest && cd $_ npm init -y
接下来安装Jest:
npm i jest --save-dev
咱们还须要用配置一个 NPM 脚本,用于从命令行运行咱们的测试。打开 package.json 并配置名为“test”的脚本以运行Jest:
"scripts": { "test": "jest" },
做为开发者,咱们都喜欢创意自由。可是当谈到严肃的事情时,大部分时间你都没有那么多的特权。一般咱们必须遵循规范,即创建的书面或口头描述。
在本教程中,咱们从项目经理那里获得了一个至关简单的规范。一个超级重要的客户端须要一个函数来过滤一个对象数组。
对于每一个对象,咱们必须检查名为“url”的属性,若是属性的值与给定的术语匹配,那么咱们应该在结果数组中包含匹配的对象。做为一个精通测试的 JavaScript 开发人员,你想要遵循测试驱动开发,这是一个强制在开始编码以前编写失败测试的学科。
默认状况下,Jest 但愿在项目下名为 tests 的文件夹中找到测试文件。建立新文件夹:
cd getting-started-with-jest mkdir __tests__
接下来在 tests 中建立一个名为 filterByTerm.spec.js 的新文件。你可能想知道为何扩展名是“.spec。”。这是一个借用 Ruby 的约定,用于将文件标记为给定功能的规范。
如今来测试吧!
如今建立你的第一次Jest测试。打开 filterByTerm.spec.js 并建立一个测试块:
describe("Filter function", () => { // test stuff });
咱们的第一个朋友是 describe,一个用于包含一个或多个相关测试的 Jest 方法。每次开始为功能编写一套新测试时,都会将其包含在 describe 块中。正如你所看到的,它须要两个参数:一个用于描述测试套件的字符串,还有一个用于包装实际测试的回调函数。
接下来咱们将遇到另外一个名为 test 的函数,它是实际的测试块:
describe("Filter function", () => { test("it should filter by a search term (link)", () => { // actual test }); });
这时咱们已准备好编写测试了。请记住,测试是关于输入、功能和预期输出的问题。首先定义一个简单的输入,一个对象数组:
describe("Filter function", () => { test("it should filter by a search term (link)", () => { const input = [ { id: 1, url: "https://www.url1.dev" }, { id: 2, url: "https://www.url2.dev" }, { id: 3, url: "https://www.link3.dev" } ]; }); });
接下来定义预期结果。根据规范,测试中的函数应该省略其 url 属性与给定搜索项不匹配的对象。咱们能够期待例如具备单个对象的数组,给定 “link” 做为搜索项:
describe("Filter function", () => { test("it should filter by a search term (link)", () => { const input = [ { id: 1, url: "https://www.url1.dev" }, { id: 2, url: "https://www.url2.dev" }, { id: 3, url: "https://www.link3.dev" } ]; const output = [{ id: 3, url: "https://www.link3.dev" }]; }); });
如今准备编写实际的测试。咱们将使用 expect 和一个 Jest matcher 来检查这个函数在调用时返回的预期结果。这是测试:
expect(filterByTerm(input, "link")).toEqual(output);
为了进一步细分,你能够在代码中调用函数:
filterByTerm(inputArr, "link");
在 Jest 测试中,你应该将函数调用包含在 expect 中,它与匹配器(用于检查输出的Jest函数)一块儿进行实际测试。这是完整的测试:
describe("Filter function", () => { test("it should filter by a search term (link)", () => { const input = [ { id: 1, url: "https://www.url1.dev" }, { id: 2, url: "https://www.url2.dev" }, { id: 3, url: "https://www.link3.dev" } ]; const output = [{ id: 3, url: "https://www.link3.dev" }]; expect(filterByTerm(input, "link")).toEqual(output); }); });
(有关 Jest 匹配器的更多信息,请查看文档(https://jestjs.io/docs/en/get...))。
你能够这样执行测试:
npm test
你会看到测试失败了:
FAIL __tests__/filterByTerm.spec.js Filter function ✕ it should filter by a search term (2ms) ● Filter function › it should filter by a search term (link) ReferenceError: filterByTerm is not defined 9 | const output = [{ id: 3, url: "https://www.link3.dev" }]; 10 | > 11 | expect(filterByTerm(input, "link")).toEqual(output); | ^ 12 | }); 13 | }); 14 |
“ReferenceError: filterByTerm is not defined”. 实际上这是一件好事。咱们会在下一节修复它!
真正缺乏的是 filterByTerm 的实现。为方便起见,咱们将在测试所在的同一文件中建立该函数。在一个实际项目中,你须要在另外一个文件中定义该函数并从测试文件中导入它。
为了进行测试,咱们将使用一个名为 filter 的原生 JavaScript 函数,它能够过滤掉数组中的元素。这是 filterByTerm 的最小实现:
function filterByTerm(inputArr, searchTerm) { return inputArr.filter(function(arrayElement) { return arrayElement.url.match(searchTerm); }); }
如下是它的工做原理:对于输入数组的每一个元素,咱们检查“url”属性,使用 match 方法将其与正则表达式进行匹配。这是完整的代码:
function filterByTerm(inputArr, searchTerm) { return inputArr.filter(function(arrayElement) { return arrayElement.url.match(searchTerm); }); } describe("Filter function", () => { test("it should filter by a search term (link)", () => { const input = [ { id: 1, url: "https://www.url1.dev" }, { id: 2, url: "https://www.url2.dev" }, { id: 3, url: "https://www.link3.dev" } ]; const output = [{ id: 3, url: "https://www.link3.dev" }]; expect(filterByTerm(input, "link")).toEqual(output); }); });
再次运行测试:
npm test
看到它经过了!
PASS __tests__/filterByTerm.spec.js Filter function ✓ it should filter by a search term (link) (4ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 0.836s, estimated 1s
很好。但咱们完成了测试吗?尚未。 使咱们的函数失败须要什么条件?让咱们用大写搜索词强调函数:
function filterByTerm(inputArr, searchTerm) { return inputArr.filter(function(arrayElement) { return arrayElement.url.match(searchTerm); }); } describe("Filter function", () => { test("it should filter by a search term (link)", () => { const input = [ { id: 1, url: "https://www.url1.dev" }, { id: 2, url: "https://www.url2.dev" }, { id: 3, url: "https://www.link3.dev" } ]; const output = [{ id: 3, url: "https://www.link3.dev" }]; expect(filterByTerm(input, "link")).toEqual(output); expect(filterByTerm(input, "LINK")).toEqual(output); // New test }); });
运行测试......它将失败。该再次修复它了!
filterByTerm 也应该考虑大写的搜索术语。换句话说,即便搜索项是大写字符串,它也应该返回匹配的对象:
filterByTerm(inputArr, "link"); filterByTerm(inputArr, "LINK");
为了测试这种状况,咱们引入了一个新测试:
expect(filterByTerm(input, "LINK")).toEqual(output); // New test
为了使它经过,咱们能够调整提供给 match 的正则表达式:
// return arrayElement.url.match(searchTerm); //
咱们能够构建一个不区分大小写的正则表达式,而不是直接传递 searchTerm,也就是说,不管怎样的字符串都匹配的表达式。这是修复:
function filterByTerm(inputArr, searchTerm) { const regex = new RegExp(searchTerm, "i"); return inputArr.filter(function(arrayElement) { return arrayElement.url.match(regex); }); }
这是完整的测试:
describe("Filter function", () => { test("it should filter by a search term (link)", () => { const input = [ { id: 1, url: "https://www.url1.dev" }, { id: 2, url: "https://www.url2.dev" }, { id: 3, url: "https://www.link3.dev" } ]; const output = [{ id: 3, url: "https://www.link3.dev" }]; expect(filterByTerm(input, "link")).toEqual(output); expect(filterByTerm(input, "LINK")).toEqual(output); }); }); function filterByTerm(inputArr, searchTerm) { const regex = new RegExp(searchTerm, "i"); return inputArr.filter(function(arrayElement) { return arrayElement.url.match(regex); }); }
再次运行并看到它经过。作得好!做为练习,你要写两个新的测试并检查如下条件:
你将如何构建这些新测试?
在下一节中,咱们将看到测试的另外一个重要主题:代码覆盖率。
什么是代码覆盖率?在谈论它以前,先让咱们快速调整一下代码。在项目根目录中建立一个名为 src 的新文件夹,并建立一个名为 filterByTerm.js 的文件,放置并导出咱们的函数:
mkdir src && cd _$ touch filterByTerm.js
这是文件 filterByTerm.js:
function filterByTerm(inputArr, searchTerm) { if (!searchTerm) throw Error("searchTerm cannot be empty"); const regex = new RegExp(searchTerm, "i"); return inputArr.filter(function(arrayElement) { return arrayElement.url.match(regex); }); } module.exports = filterByTerm;
如今伪装我是你的新同事。我对测试一无所知,我应该直接在该函数内部添加一个新的 if语句,而不是要求更多的上下文:
function filterByTerm(inputArr, searchTerm) { if (!searchTerm) throw Error("searchTerm cannot be empty"); if (!inputArr.length) throw Error("inputArr cannot be empty"); // new line const regex = new RegExp(searchTerm, "i"); return inputArr.filter(function(arrayElement) { return arrayElement.url.match(regex); }); } module.exports = filterByTerm;
filterByTerm 中有一行新的代码,彷佛不会被测试。除非我告诉你“有一个新的测试声明”你不会在咱们的函数中确切地知道测试。几乎不可能想象咱们的代码能够采用的全部路径,所以须要一种有助于揭示这些盲点的工具。
该工具被称为代码覆盖,它是工具箱中的强大工具。 Jest 具备内置代码覆盖率,你能够经过两种方式激活:
在使用 coverage 运行测试以前,请确保在 tests/filterByTerm.spec.js 中导入 filterByTerm:
const filterByTerm = require("../src/filterByTerm"); // ...
保存文件并用 coverage 运行测试:
npm test -- --coverage
这是你获得的结果:
PASS __tests__/filterByTerm.spec.js Filter function ✓ it should filter by a search term (link) (3ms) ✓ it should filter by a search term (uRl) (1ms) ✓ it should throw when searchTerm is empty string (2ms) -----------------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | -----------------|----------|----------|----------|----------|-------------------| All files | 87.5 | 75 | 100 | 100 | | filterByTerm.js | 87.5 | 75 | 100 | 100 | 3 | -----------------|----------|----------|----------|----------|-------------------| Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total
这是对咱们的功能测试范围的一个很好的总结。如你所见第3行被uncovered。你能够尝试经过测试我添加的新语句来达到100%的代码覆盖率。
若是你想保持代码覆盖率始终处于活动状态,请在 package.json 中配置 Jest,以下所示:
"scripts": { "test": "jest" }, "jest": { "collectCoverage": true },
你还能够将标志传递给测试脚本:
"scripts": { "test": "jest --coverage" },
还有一种能够得到代码覆盖率的HTML报告的方法,它就像配置Jest同样:
"scripts": { "test": "jest" }, "jest": { "collectCoverage": true, "coverageReporters": ["html"] },
如今,每次运行 npm test 时,你均可以在项目文件夹中访问名为 coverage 的新文件夹:getting-started-with-jest/coverage/。在该文件夹中,你将找到一堆文件,其中 /coverage/index.html 是代码覆盖范围的完整HTML摘要。
若是单击函数名称,你还会看到确切的未经测试的代码行:
很整洁不是吗?使用代码覆盖,你能够在有疑问时发现要测试的内容。
React 是一个很是流行的 JavaScript 库,用于建立动态用户界面。 Jest 能够顺利地测试 React 应用(Jest 和 React 均来自 Facebook 的工程师)。 Jest 也是 Create React App 中的默认测试器。
若是你想学习如何测试React组件,请查看测试React组件:最明确的指南。该指南涵盖了单元测试组件、类组件、带hook的功能组件和新的 Act API。
测试是一个很大并且引人入胜的话题。有许多类型的测试和用于测试的库。在这个 Jest 教程中,你学习了如何为覆盖率报告配置 Jest,如何组织和编写简单的单元测试,以及如何测试 JavaScript 代码。
要了解有关 UI测试的更多信息,我强烈建议你查看用 Cypress 进行 JavaScript 端到端测试。
即便它与 JavaScript 无关,我也建议阅读 Harry Percival 的使用 Python 进行测试驱动开发。它包含了全部测试内容的提示和技巧,并深刻介绍了全部不一样类型的测试。
若是你已准备好再迈出一步,要了解自动化测试和持续集成那么JavaScript中的自动化测试和持续集成是为你准备的。
你能够在Github上找到本教程的代码:getting-started-with-jest以及练习的解决方案。