概述
单元测试和性能测试,对Nodejs的应用很重要,可以保证好系统的质量,避免线上问题,出现问题也可以很快找到,其次就是可能会有某个库,某个方法,方便我们做独立的测试。
从大厂绩效考核的角度上来看,单元测试的质量分,也占了很大的一个比例。
单元测试-AAA模式
AAA代表Arrange、Act、Assert,这三个步骤分别对应于准备测试环境、执行被测功能以及验证结果。以下是关于AAA模式更详细的介绍:
- Arrange(准备) :这个阶段主要是设置测试所需的状态或条件。包括但不限于初始化对象、准备数据和配置环境等。这是为了确保测试能够在预定的条件下运行。
- Act(行动) :在这一阶段,调用被测试的方法或者函数,传入必要的参数,并获取输出结果。这是测试的核心部分,专注于被测功能的实际执行。
- Assert(断言) :最后一步是验证执行的结果是否符合预期。通过检查实际结果与期望结果是否一致来确定测试是否通过。这通常涉及到使用断言语句来进行比较。
可以使用node内置模块 - assert,去做单元测试。
const assert = require('assert');
//测试逻辑。。。
这样其实会有个问题,无法评估错误率,通过率这种,没有报告的形成
这里推荐一个工具Mocha
Mocha
编写测试用例的原则(如何去编写高质量的测试用例):
Fast-测试速度快,跑一次很慢的话,就很难用了。
Independent - 测试不要相互依赖,用例能并行测试。
Repeatable - 可重复,在生产环境,测试环境,本地,都可以这使用,可以验证。
Thorough - 完整的,应该覆盖好全部的测试条件和边界条件,保证可用性。
这里做一个最简单的测试。
新增了一个库
yarn add mocha
例如我需要一个方法把一个数组拆分成数组对象的模式,实现了一个方法arrayToObject
建了一个package.json
{
"name": "mocha-test",
"version": "1.0.0",
"description": "test mocha",
"main": "index.js",
"author": "hufeng",
"license": "MIT",
"devDependencies": {
"mocha": "^11.3.0"
},
"scripts": {
"test": "mocha"
}
}
App目录下,index.js:
module.exports = function arrayToObject(arr) {
const obj = {};
arr.forEach(([key, value]) => {
obj[key] = value;
});
return obj;
};
Test目录下:
// test/testArrayToObject.js
const assert = require('assert');
const arrayToObject = require('../app/index');
const { request } = require('http');
describe('arrayToObject', function() {
it('should convert a 2D array to an object', function() {
const input = [['name', 'Alice'], ['age', 25]];
const expectedOutput = { name: 'Alice', age: 25 };
const output = arrayToObject(input);
console.log(output)
assert.deepStrictEqual(output, expectedOutput);
});
it('should handle empty arrays', function() {
const input = [];
const expectedOutput = {};
const output = arrayToObject(input);
assert.deepStrictEqual(output, expectedOutput);
});
});
使用yarn test执行,获取单元测试的结果:
新增一条单元测试,当输入值为null的时候。
it('should handle null arrays', function() {
const input = {};
const expectedOutput = {};
const output = arrayToObject(input);
assert.deepStrictEqual(output, expectedOutput);
});
这个时候就会报错了,走单测脚本覆盖全所有的情况可以增强程序的健壮性:
也可以对自己编写的接口做单元测试。
// test/testApp.js
const request = require('supertest');
const app = require('../app');
describe('GET /', function() {
it('respond with Hello World!', function(done) {
request(app)
.get('/')
.expect('Content-Type', /text/)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
assert.strictEqual(res.text, 'Hello World!');
done();
});
});
});
describe('GET /user', function() {
it('respond with json', function(done) {
request(app)
.get('/user')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
assert.deepStrictEqual(res.body, { name: 'John Doe', age: 25 });
done();
});
});
});
其中,done是异步的,执行一个异步的结果。
性能测试
以下使用Artillery对Nodejs Api做性能测试。
可以通过脚本模拟真实环境的高并发或者网络情况,做极端情况下的测试。
为了观察这种情况一般要配合APM,APM如何搭建,翻我之前的文章去搭配使用就行了。
还可以做稳定性的测试,比如我这个系统跑久一点,会不会有内存泄漏。
极端的情况下,服务是否能够活着,出问题了能不能快速恢复等。
什么场景下需要做性能测试?
(1)新的应用需要上线的时候,预估服务能扛多少QPS,用于后面加机器。
(2)有一定量的访问,比如SSR服务这种,需要做性能测试。
(3)短时间短流量服务,比如抢票,团购这些。
(4)容易被人攻击的服务,携带URL的详情信息这种。
性能测试需要关注的指标?
QPS:每一秒请求数量。
峰值时间QPS/单台机器的QPS = 需要的机器
吞吐量/事务相关(TPS):每秒处理多少事务。
最大响应时间:最慢情况下的响应时间
错误率:错误的请求数占总请求数的占比
磁盘IO/内存:特别是内存泄漏,Nodejs应用需要非常重视。前面有文章发布过,如何查看是否内存泄漏和内存泄漏的原因。
可以参考下面性能测试的方法:
性能测试列表
| 测试策略 | 环境 | 记录数 | 场景 | 用户数 | 性能指标 | 目的 |
|---|---|---|---|---|---|---|
| 负载测试 | 一定软件、硬件、网络 | 相同数量级记录 | 运行一种或多种业务 | 不同虚拟用户 | 是否在用户要求范围 | 1. 系统能承载的最大用户数 2. 最大有效用户 3. 不同用户下系统响应时间 4. 服务器资源利用率 |
| 压力测试 | 一定软件、硬件、网络 | 相同数量级记录 | 运行一种或多种业务 | 模拟大量用户 | 稳定持续工作 | 让服务器处于极限下长时间连续运行 |
| 配置测试 | 不同软硬件配置 | 相同数量级记录 | 运行一种或多种业务 | 一定虚拟用户 | 不同的性能指标值 | 获取不同的性能指标,用于选择最佳的设备及参数配置 |
| 容量测试 | 一定软件、硬件、网络 | 构造不同数量级别记录 | 运行一种或多种业务 | 一定虚拟用户 | 不同的性能指标值 | 确认数据库容量 |
| 基准测试 | 一定软件、硬件、网络 | 1. 相同数量级记录 2. 不同数量级别记录 | 运行一种或多种业务 | 根据基准类型(负载、压力、配置、容量、并发)来确认 | 取得不同组合下的性能指标值 | 用于未来:系统调优和系统评测的结果比较,确认调优是否达到效果 |
| 并发测试 | 一定软件、硬件、网络 | 相同数量级记录 | 访问同一应用、存储过程、数据记录 | 不同虚拟用户 | 是否在用户要求范围 | 在满足性能要求的情况下,是否有死锁、数据故障、连接泄 |
使用Artillery做性能测试:
安装脚本:
npm install artillery -g
这里我拉了koa的代码:
git clone https://github.com/koajs/examples.git
在根目录下:
mkdir test
放测试脚本,这里使用了yml文件。
config:
target: "http://localhost:3000" # 替换为你的 Koa 服务地址
phases:
- duration: 60 # 测试持续时间(秒)
arrivalRate: 10 # 每秒发送的请求数(并发量)
name: "High Load Test" # 测试阶段名称
http:
pipelining: false # 是否启用 HTTP 管道(通常设为 false)
scenarios:
- name: "Test Koa API"
flow:
- get:
url: "/" # 替换为你的 Koa 接口路径
headers:
Authorization: "Bearer your_token" # 如果有认证,添加请求头
- think: 1 # 模拟用户思考时间(秒)
# - post:
# url: "/api/submit"
# json:
# key1: "value1" # POST 请求体数据
# key2: "value2"
直接执行artillery run xxx.yml
可以拿到一些数据,知道我们的压测结果。
这里我加大压力arrivalRate: 1000 # 每秒发送的请求数(并发量)
执行结果:
这里大概可以知道,我本地的部署,大概每秒能有800多个请求,大多数业务场景下,已经很够打了,有必要的话就加pod去扛就行了。
一般出现CPU这种问题,优先加机器,再去优化代码。
当出现OOM,内存打满那种,应该是代码存在问题,优先优化代码。
其次,arrivalRate支持一些强大的js动态注入参数的能力。
比如这样:
Authorization: "Bearer {{ auth_token }}" # 使用捕获的 token
如果是分布式的场景:
Artillery也是支持的,可以去看看Artillery的云服务。
或者本地多实例的形式去启动做压测
总之,笔主认为Node应用的性能绝对是很能打的了!在中大厂里,不是别人眼里的“玩具”应用,而是真正能落地,可靠的东西。
具体还是要实践得出结果。