JavaScript 测试:Unit、Integration、E2E — 从入门到精通
现代 Web 应用程序(尤其是基于 Node.js 的)开发离不开高质量的测试。测试不仅能在代码进入生产环境前捕获错误,还能作为活文档,并让你在重构代码时无需担心破坏功能。本文将解析测试金字塔的三个主要层级:unit(单元测试)、integration(集成测试)和 e2e(端到端测试)。你将了解它们的区别、使用哪些工具,并看到基于 Node.js 的完整示例。
1. 单元测试:测试隔离的模块
单元测试是测试金字塔的基础。它们检查单个函数、类或组件在完全隔离外部依赖(数据库、API、文件系统)的情况下的工作状态。主要目标是确保模块逻辑在任何输入数据下都能正确运行。
对于 Node.js,最流行的框架是 Jest(来自 Facebook)。它无需复杂配置,内置断言、模拟和代码覆盖率功能。
示例:测试数组排序函数
// utils.jsfunction sortNumbers(arr) { if (!Array.isArray(arr)) { throw new Error('Input must be an array'); } return [...arr].sort((a, b) => a - b);}
module.exports = { sortNumbers };// utils.test.jsconst { sortNumbers } = require('./utils');
describe('sortNumbers', () => { test('should sort an array of numbers in ascending order', () => { expect(sortNumbers([3, 1, 2])).toEqual([1, 2, 3]); });
test('should handle an empty array', () => { expect(sortNumbers([])).toEqual([]); });
test('should throw error for non-array input', () => { expect(() => sortNumbers('string')).toThrow('Input must be an array'); });});单元测试最佳实践:
- 一个测试只做一项检查(一个断言)。
- 使用
describe按函数对测试进行分组。 - 模拟外部依赖(使用
jest.mock())。 - 在编写代码之前编写测试(TDD — 测试驱动开发)。
2. 集成测试:检查组件间的交互
集成测试检查系统中不同模块如何协同工作。例如,你的控制器如何与数据库或外部 API 交互。与单元测试不同,这里我们不会模拟所有内容,而是使用真实(或测试)环境。
对于测试基于 Express 的 HTTP 服务器,Supertest 库与 Jest 配合使用效果极佳。
示例:测试 REST 端点
// app.jsconst express = require('express');const app = express();
app.get('/api/hello', (req, res) => { res.json({ message: 'Hello, World!' });});
app.post('/api/data', (req, res) => { const { name } = req.body; if (!name) { return res.status(400).json({ error: 'Name is required' }); } res.status(201).json({ received: name });});
module.exports = app;// app.test.jsconst request = require('supertest');const app = require('./app');
describe('Integration tests for API', () => { test('GET /api/hello should return a greeting', async () => { const response = await request(app) .get('/api/hello') .expect(200);
expect(response.body).toEqual({ message: 'Hello, World!' }); });
test('POST /api/data with valid body should return 201', async () => { const response = await request(app) .post('/api/data') .send({ name: 'Alice' }) .expect(201);
expect(response.body).toEqual({ received: 'Alice' }); });
test('POST /api/data without name should return 400', async () => {