测试驱动开发,或称TDD,是工作场所中一种相当普遍的编程风格,而且在许多情况下,是某些项目的强制要求。幸运的是,当你了解这个过程背后的基本原理时,TDD是相当简单的。
也有许多库和框架用于JavaScript的后端测试。其中最流行的两个是Jest和Mocha,还有Jasmine、Ava、Tape和QUnit等替代品。
Node.js现在有自己的内置测试运行器,自v18以来一直很稳定。然而,它仍然处于实验模式,所以它很可能会随着时间的推移而改变。
在本文中,我们将讨论如何使用新的Node.js测试运行器进行一些基本测试,以及使用Jest测试不同的端点。
在后端进行测试
首先,让我们跳到使用Node.js在后端进行测试。
使用Node.js测试运行器来测试后端代码
如果你想使用内置的Node.js测试运行器,你需要导入下面的库,并按照Node.js文档来运行测试。
// JavaScript ESM Modules syntax
import test from 'node:test';
// JavaScript CommonJS syntax
const test = require('node:test');
当然,你也需要确保你使用的是v18或更高版本。你可以在命令行中使用node -v ,检查你目前运行的是哪个版本。
首先,让我们看看使用内置的Node.js测试运行器进行测试是什么样的。
项目设置
打开你的BASH应用程序,cd 到你选择的目录。运行下面的命令来构建你的项目。
mkdir node-backend-testing
cd node-backend-testing
npm init -y
touch index.js index.test.js
接下来,在你的代码编辑器中打开新创建的项目,将代码添加到它们相关的文件中。
下面是文件index.js 的代码。
const calcAge = (dob) => {
const digits = {
year: 'numeric',
};
const year = new Date().toLocaleDateString('en-US', digits);
console.log(year);
return year - dob;
};
const createBox = (x, y) => {
return x * y;
};
const canDrive = () => {
const age = 18;
if (age >= 18) {
return 'Full Driving Licence';
} else {
return 'Provisional License';
}
};
const powerLevel = () => {
const power = 9001;
if (power > 9000) {
return true;
} else {
return false;
}
};
const workSchedule = (employeeOne, employeeTwo) => {
return employeeOne + employeeTwo;
};
module.exports = {
calcAge,
createBox,
canDrive,
powerLevel,
workSchedule,
};
接下来,我们有index.test.js 的代码。
const test = require('node:test');
const assert = require('assert/strict');
const {
calcAge,
createBox,
canDrive,
powerLevel,
workSchedule,
} = require('./index');
// Calculates how old someone is and depending on the year this test could pass or fail
test('calculates age', () => {
return assert.equal(calcAge(2000), 22);
});
// Creates a box with an equal height and width
test('creates a box', async (t) => {
await t.test('creates a small box', () => {
assert.equal(createBox(10, 10), 100);
});
await t.test('creates a large box', () => {
assert.equal(createBox(50, 50), 2500);
});
});
// Checks to see whether or not the person has a full driving licence
test('checks license', () => {
return assert.match(`${canDrive()}`, /Full Driving Licence/);
});
// Confirms that the person has a power level that is over 9000!
test('confirms power level', () => {
return assert.ok(powerLevel());
});
// Checks to see if the employees have the same amount of shift work days in a week
test('employees have an equal number of work days', () => {
const employeeOne = ['Monday', 'Tuesday', 'Wednesday,', 'Thursday'];
const employeeTwo = ['Friday', 'Saturday', 'Sunday,', 'Monday'];
return assert.equal(workSchedule(employeeOne.length, employeeTwo.length), 8);
});
最后,为文件package.json ,添加这个运行脚本。
"scripts": {
"test": "node index.test.js"
},
该测试脚本运行Node测试运行器,然后将运行index.test.js 中的测试。
运行Node测试
你只需要在根文件夹内运行一个命令。再次强调,你需要使用Node v18或更高版本,这样才能工作。
运行下面的命令,你应该在控制台看到五个测试通过。
npm run test
你可以在index.js 和index.test.js 文件中玩玩代码,看看测试的通过和失败。如果你看一下控制台中的测试错误,你就会知道什么是失败的原因。
下面让我给你举几个例子。
计算年龄测试
要计算一个用户的年龄,使用当前年份减去他们的出生年份。见下面index.test.js 文件中的例子。
test('calculates age', () => {
return assert.equal(calcAge(2000), 21);
});
要看到测试失败,请输入一个不正确的年龄,如21 。在这个例子中,该函数期望的返回值是22 ,所以数字21 使测试失败。
创建一个方框测试
这个测试希望方程10 x 10 和50 x 50 的答案分别为100 和2500 。如果输入的值与正确的输出值相差甚远,则测试失败。
test('creates a box', async (t) => {
await t.test('creates a small box', () => {
assert.equal(createBox(10, 30), 100);
});
await t.test('creates a large box', () => {
assert.equal(createBox(50, 20), 2500);
});
});
检查许可证测试
这个测试检查一个人是否有有效的驾驶执照。将index.js 文件的canDrive 函数中的年龄改为小于18 。 测试将失败。
const canDrive = () => {
const age = 17;
if (age >= 18) {
return 'Full Driving Licence';
} else {
return 'Provisional License';
}
};
确认功率水平测试
这个测试检查一个人的力量水平是否超过9000(你明白龙珠Z的含义吗?
)。
将index.js 文件的powerLevel 函数内的功率级别改为小于9000 。测试将失败。
const powerLevel = () => {
const power = 5000;
if (power > 9000) {
return true;
} else {
return false;
}
};
相同工作天数测试
这个测试检查员工的工作天数是否相同。
目前有两个雇员各工作四天,我们总共有八天的时间。要看到测试失败,只需从数组中输入或删除一些天数,使它们不再是相同的长度。
test('employees have an equal number of work days', () => {
const employeeOne = ['Monday', 'Tuesday', 'Wednesday,', 'Thursday'];
const employeeTwo = ['Friday', 'Saturday'];
return assert.equal(workSchedule(employeeOne.length, employeeTwo.length), 8);
});
使用Jest来测试后端代码
现在,让我们使用Jest测试库做一些后端测试。
项目设置
打开你的BASH应用程序,cd 到你喜欢的目录。运行下面的命令来构建你的项目。
mkdir jest-backend-testing
cd jest-backend-testing
npm init -y
npm i express http-errors jest nodemon supertest
mkdir routes
touch routes/products.js
touch app.js app.test.js server.js
现在,在你的代码编辑器中打开项目,将下面的代码添加到它们相应的文件中。
下面是文件routes/product.js 的代码。
const express = require('express');
const router = express.Router();
const createError = require('http-errors');
// Products Array
const products = [{ id: '1', name: 'Playstation 5', inStock: false }];
// GET / => array of items
router.get('/', (req, res) => {
res.json(products);
});
// GET / => items by ID
router.get('/:id', (req, res, next) => {
const product = products.find(
(product) => product.id === String(req.params.id)
);
// GET /id => 404 if item not found
if (!product) {
return next(createError(404, 'Not Found'));
}
res.json(product);
});
router.post('/', (req, res, next) => {
const { body } = req;
if (typeof body.name !== 'string') {
return next(createError(400, 'Validation Error'));
}
const newProduct = {
id: '1',
name: body.name,
inStock: false,
};
products.push(newProduct);
res.status(201).json(newProduct);
});
module.exports = router;
接下来,是文件app.js 的代码。
const express = require('express');
const productsRoute = require('./routes/products');
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use('/', productsRoute);
module.exports = app;
下面是文件app.test.js 的代码。
const request = require('supertest');
const app = require('./app');
describe('GET /', () => {
it('GET / => array of items', () => {
return request(app)
.get('/')
.expect('Content-Type', /json/)
.expect(200)
.then((response) => {
expect(response.body).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
name: expect.any(String),
inStock: expect.any(Boolean),
}),
])
);
});
});
it('GET / => items by ID', () => {
return request(app)
.get('/1')
.expect('Content-Type', /json/)
.expect(200)
.then((response) => {
expect(response.body).toEqual(
expect.objectContaining({
id: expect.any(String),
name: expect.any(String),
inStock: expect.any(Boolean),
})
);
});
});
it('GET /id => 404 if item not found', () => {
return request(app).get('/10000000000').expect(404);
});
it('POST / => create NEW item', () => {
return (
request(app)
.post('/')
// Item send code
.send({
name: 'Xbox Series X',
})
.expect('Content-Type', /json/)
.expect(201)
.then((response) => {
expect(response.body).toEqual(
expect.objectContaining({
name: 'Xbox Series X',
inStock: false,
})
);
})
);
});
it('POST / => item name correct data type check', () => {
return request(app).post('/').send({ name: 123456789 }).expect(400);
});
});
差不多了!下面是server.js 文件的代码。
const app = require('./app');
const port = process.env.PORT || 3000;
app.listen(port, () =>
console.log(`Server running on port ${port}, http://localhost:${port}`)
);
最后,将这些运行脚本添加到你的package.json 文件中。
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest --watchAll"
},
start 脚本使用node运行服务器文件。
dev 脚本使用nodemon 运行服务器文件,实现自动重载。
test 脚本运行测试运行器Jest,自动观察文件变化。
运行Jest测试
现在是启动应用程序和测试运行器的时候了。在不同的标签或窗口中运行下面的命令,这样你就有一个脚本运行开发服务器,另一个运行Jest测试运行器。
服务器运行在http://localhost:3000/。
npm run dev
npm run test
你现在应该有dev 服务器在运行。Jest测试运行器也应该在运行,有五个通过的测试。让我们浏览一下每个测试,你可以看到它们的通过或失败。
这些测试的名称与标题相同,所以它们应该很容易在文件中找到。
数组项目测试
这个测试检查是否有一个对象数组被返回。要看到它失败,打开products.js 文件,用下面的代码替换顶部的路线。
router.get('/', (req, res) => {
res.send('products');
});
你应该得到错误expected Content-Type" matching /json/, got "text/html; charset=utf-8 ,测试应该失败。
现在试着把它改成下面的代码。
router.get('/', (req, res) => {
res.json('products');
});
你应该得到这个错误Expected: ArrayContaining [ObjectContaining {"id": Any<String>, "inStock": Any<Boolean>, "name": Any<String>}] 。
项目由id 测试
这个测试检查一个项目是否以正确的方式返回id 。将products.js 文件顶部的products 数组改为id ,它是一个数字(而不是一个字符串),可以看到它失败。
const products = [{ id: 1, name: 'Playstation 5', inStock: false }];
你应该得到这个错误:expected "Content-Type" matching /json/, got "text/html; charset=utf-8" 。
未找到项目测试
这个测试检查是否找不到一个项目。要看到它失败,请在products.js 文件中把404 error 的代码改为其他内容,如下面的例子。
if (!product) {
return next(createError(400, 'Not Found'));
}
你应该得到这样的错误:expected 404 "Not Found", got 400 "Bad Request" 。
正确的数据测试
这个测试检查对象是否有正确的数据和数据类型。要看到它失败,打开app.test.js 文件,用下面的代码替换发送代码。
.send({
name: 'Nintendo Switch',
})
你应该看到这个错误。
- Expected - 2
+ Received + 3
- ObjectContaining {
+ Object {
+ "id": "1",
"inStock": false,
- "name": "Xbox Series X",
+ "name": "Nintendo Switch",
正确的数据类型测试
这个测试检查名称变量是否是正确的数据类型。只有在检测到字符串的情况下才会失败。要看到测试失败,打开app.test.js 文件,滚动到底部,像这样把数字改为字符串。
return request(app).post('/').send({ name: '123456789' }).expect(400);
你应该得到这个错误:expected 400 "Bad Request", got 201 "Created" 。
Jest 28:新功能
Jest 28于2022年4月发布,带来了很多新功能。其中一个被要求最多的功能叫做分片。本质上,分片让你把你的测试套件分成不同的分片,以运行套件测试的一部分。
例如,要运行三分之一的测试,你可以使用下面的命令。
jest --shard=1/3
jest --shard=2/3
jest --shard=3/3
结语
这就是全部!我们刚刚介绍了在后端使用Node和Jest进行测试。我们所涉及的只是测试的基础知识--还有很多东西需要学习!