Jest 初学者教程:开始使用 Jest 进行 JavaScript 测试
dev.to 的粉丝刚刚突破 5000 了!谢谢大家!真是个很棒的社区!还有谁也在 Twitter 上?快来联系我 =>我在这里。
测试是什么意思?如何使用 Jest 测试 JavaScript 代码?本 Jest 初学者教程带你学习 JavaScript 测试的基础知识!
测试是什么意思?
用技术术语来说,测试意味着检查我们的代码是否符合某些预期。例如:一个名为“transformer”的函数应该在给定某些输入的情况下返回预期的输出。
测试类型有很多,很快你就会被各种术语弄得不知所措,但长话短说,测试主要分为三类:
- 单元测试
- 集成测试
- UI 测试
在本 Jest 教程中,我们将仅介绍单元测试,但在文章末尾您将找到其他类型测试的资源。
Jest 教程:什么是 Jest?
Jest是一个 JavaScript 测试运行器,即用于创建、运行和构建测试的 JavaScript 库。Jest 以 NPM 包的形式分发,您可以将其安装在任何 JavaScript 项目中。Jest 是目前最流行的测试运行器之一,也是 Create React App 的默认选择。
首先要做的事情是:我怎么知道要测试什么?
说到测试,即使是一段简单的代码也可能让初学者不知所措。最常见的问题是“我怎么知道要测试什么?” 。如果你正在编写一个 Web 应用程序,一个好的起点应该是测试应用程序的每个页面和每个用户交互。但是,Web 应用程序也由需要测试的代码单元(例如函数和模块)组成。大多数情况下有两种情况:
- 您继承了未经测试的遗留代码
- 你必须凭空实现一个新功能
该怎么办?对于这两种情况,你可以把测试想象成一小段代码,用来检查给定函数是否产生了预期的结果。典型的测试流程如下:
- 导入要测试的函数
- 给函数输入
- 定义预期输出
- 检查函数是否产生预期的输出
真的,就是这样。如果你能按照以下思路思考,测试就不再可怕了:输入 - 预期输出 - 断言结果。一会儿我们还会看到一个方便的工具,可以几乎准确地检查需要测试的内容。现在就动手体验 Jest 吧!
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"
},
一切就绪!
Jest 教程:规范和测试驱动开发
作为开发者,我们都喜欢自由发挥创意。但当涉及到严肃的事情时,大多数时候你并没有那么多的自由。我们通常必须遵循规范,也就是关于构建内容的书面或口头描述。
在本教程中,我们从项目经理那里得到了一个相当简单的需求。一个非常重要的客户需要一个JavaScript 函数来过滤一个对象数组。
对于每个对象,我们必须检查一个名为“url”的属性,如果该属性的值与给定的术语匹配,则应将匹配的对象包含在结果数组中。作为一名精通测试的 JavaScript 开发人员,您需要遵循测试驱动开发,这是一门要求在开始编写代码之前编写失败测试的学科。
默认情况下,Jest 会在项目文件夹中名为tests 的文件夹中查找测试文件。创建新文件夹,然后:
cd getting-started-with-jest
mkdir __tests__
接下来在tests中创建一个名为filterByTerm.spec.js的新文件。你可能想知道为什么扩展名包含“.spec”。这是从 Ruby 中借用的一种约定,用于将文件标记为给定功能的规范。
现在让我们开始测试吧!
Jest 教程:测试结构和第一次失败的测试
是时候创建你的第一个 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匹配器来检查我们虚构的(暂时的)函数在调用时是否返回了预期的结果。测试代码如下:
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 匹配器的更多信息,请查看文档)。
此时你可以尝试一下:
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 未定义”。这其实是件好事。我们下节再修复它吧!
Jest 教程:修复测试(并再次破坏它)
真正缺少的是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
});
});
运行测试……它会失败。是时候再次修复它了!
Jest 教程:修复大写字母测试
filterByTerm也应该考虑大写搜索词。换句话说,即使搜索词是大写字符串,它也应该返回匹配的对象:
filterByTerm(inputArr, "link");
filterByTerm(inputArr, "LINK");
为了测试这种情况,我们引入了一个新测试:
expect(filterByTerm(input, "LINK")).toEqual(output); // New test
为了使其通过,我们可以调整提供的正则表达式来匹配:
//
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);
});
}
再次运行,看看是否通过了。干得好!作为练习,你写两个新的测试,并检查以下条件:
- 测试搜索词“uRl”
- 测试空的搜索词。函数应该如何处理它?
您将如何构建这些新测试?
在下一节中,我们将看到测试中的另一个重要主题:代码覆盖率。
Jest 教程:代码覆盖率
什么是代码覆盖率?在讨论它之前,让我们快速调整一下代码。在项目根目录下创建一个名为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”
- 通过在 package.json 中配置 Jest
在运行覆盖测试之前,请确保在测试/filterByTerm.spec.js中导入 filterByTerm:
const filterByTerm = require("../src/filterByTerm");
// ...
保存文件并运行覆盖测试:
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 行未被覆盖。尝试通过测试我添加的新语句来达到 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 摘要:
如果您单击函数名称,您还会看到未经测试的代码行:
是不是很棒?有了代码覆盖率,当你有疑问时就能发现需要测试的地方。
Jest 教程:如何测试 React?
React是一个用于创建动态用户界面的超级流行的 JavaScript 库。Jest 可以流畅地测试 React 应用(Jest 和 React 均出自 Facebook 工程师之手)。Jest 也是 Create React App 中的默认测试运行器。
如果你想学习如何测试 React 组件,可以查看《测试 React 组件:最权威的指南》。该指南涵盖了单元测试组件、类组件、带钩子的功能组件以及新的 Act API。
结论(下一步该怎么做)
测试是一个庞大而迷人的话题。市面上有很多类型的测试和库。在本 Jest 教程中,你学习了如何配置 Jest 以生成覆盖率报告、如何组织和编写简单的单元测试以及如何测试 JavaScript 代码。
要了解有关UI 测试的更多信息,我强烈建议您查看使用 Cypress 进行 JavaScript 端到端测试。
即使与 JavaScript 无关,我也建议阅读Harry Percival 的《Python 测试驱动开发》。这本书充满了各种测试技巧和窍门,并深入涵盖了各种不同类型的测试。
如果您已准备好迈出这一步并了解自动化测试和持续集成,那么JavaScript 中的自动化测试和持续集成非常适合您。
您可以在 Github 上找到本教程的代码:getting-started-with-jest以及练习的解决方案。
感谢您的阅读,敬请期待!
文章来源:https://dev.to/valentinogagliardi/jest-tutorial-for-beginners-getting-started-with-jest-for-javascript-testing-e6c