Jest项目实战(2): 项目开发与测试

embedded/2024/11/14 15:12:06/

1. 项目初始化

首先,我们需要为开源库取一个名字,并确保该名字在 npm 上没有被占用。假设我们选择的名字是 jstoolpack,并且已经确认该名字在 npm 上不存在。

mkdir jstoolpack
cd jstoolpack
npm init -y

2. 安装依赖

接下来,我们需要安装一些开发和测试依赖。我们将使用 TypeScript 进行开发,并使用 Jest 进行单元测试

"devDependencies": {"@types/jest": "^29.5.1","jest": "^29.5.0","jest-environment-jsdom": "^29.5.0","ts-jest": "^29.1.0","ts-node": "^10.9.1","typescript": "^5.0.4"
}
npm i @types/jest jest jest-environment-jsdom ts-jest ts-node typescript -D

3. 项目结构

在项目根目录下创建 src(源码目录)和 tests(测试目录)。项目本身不难,该项目是一个类似于 lodash 的工具库项目,会对常见的 array、function、string、object 等提供一些工具方法。

mkdir src tests

4. 配置 TypeScript

在项目根目录下创建 tsconfig.json 文件,配置 TypeScript 编译选项。

{"compilerOptions": {"target": "es6","module": "commonjs","strict": true,"esModuleInterop": true,"skipLibCheck": true,"forceConsistentCasingInFileNames": true,"outDir": "./dist","rootDir": "./src","baseUrl": ".","paths": {"*": ["node_modules/*"]}},"include": ["src/**/*.ts"],"exclude": ["node_modules", "dist"]
}

5. 配置 Jest

在项目根目录下创建 jest.config.js 文件,配置 Jest 测试框架。

module.exports = {preset: 'ts-jest',testEnvironment: 'jsdom',roots: ['<rootDir>/tests'],transform: {'^.+\\.tsx?$': 'ts-jest',},moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};

6. 开发工具方法

6.1 range 方法

这里我们打算扩展一个名为 range 的方法,该方法可以生成指定范围的数组:

range(1, 6) ---> [1, 2, 3, 4, 5] 左闭右开
range(1, 6, 2) ---> [1, 3, 5]
range(1, 6, -2) ---> [1, 3, 5]range(6, 1) ---> [6, 5, 4, 3, 2]
range(6, 1, -2) ---> [6, 4, 2]
range(6, 1, 2) ---> [6, 4, 2]

对应的源码如下:

// 理论上来讲,start,stop,step 都应该是 number 类型
// 但是我们的代码最终是打包为 js 给开发者使用
// 开发者可能会存在各种非常的调用 range() range('a','b','c')
// 因此我们这里打算从方法内部进行参数防御,从而提升我们代码的健壮性
export function range(start?: any, stop?: any, step?: any) {// 参数防御start = start ? (isNaN(+start) ? 0 : +start) : 0;stop = stop ? (isNaN(+stop) ? 0 : +stop) : 0;step = step ? (isNaN(+step) ? 0 : +step) : 1;// 保证 step 的正确if ((start < stop && step < 0) || (start > stop && step > 0)) {step = -step;}const arr: number[] = [];for (let i = start; start > stop ? i > stop : i < stop; i += step) {arr.push(i);}return arr;
}

对应的测试代码如下:

import { range } from "../src/array";test("正常的情况", () => {expect(range(1, 6)).toEqual([1, 2, 3, 4, 5]);expect(range(1, 6, 2)).toEqual([1, 3, 5]);expect(range(6, 1)).toEqual([6, 5, 4, 3, 2]);expect(range(6, 1, -2)).toEqual([6, 4, 2]);
});test("错误的情况", () => {expect(range()).toEqual([]);expect(range("a", "b", "c")).toEqual([]);
});test("测试只传入start", () => {// 相当于结束值默认为 0expect(range(2)).toEqual([2, 1]);expect(range(-2)).toEqual([-2, -1]);
});test("测试step", () => {expect(range(1, 6, -2)).toEqual([1, 3, 5]);expect(range(6, 1, 2)).toEqual([6, 4, 2]);
});

6.2 truncate 方法

这里我们打算提供了一个 truncate 的方法,有些时候字符串过长,那么我们需要进行一些截取

truncate("1231323423424", 5) ----> 12...
truncate("12345", 5) ----> 12345
truncate("1231323423424", 5, '-') ----> 1231-

对应的源码如下:

export function truncate(str?: any, len?: any, omission = "...") {// 内部来做参数防御str = String(str);omission = String(omission);len = len ? Math.round(len) : NaN;if (isNaN(len)) {return "";}if (str.length > len) {// 说明要开始截断str = str.slice(0, len - omission.length) + omission;}return str;
}

对应的测试代码如下:

import { truncate } from "../src/string";test("应该将字符串截取到指定长度", () => {expect(truncate("Hello World", 5)).toBe("He...");expect(truncate("Hello World", 10)).toBe("Hello W...");expect(truncate("Hello World", 11)).toBe("Hello World");expect(truncate("Hello World", 15)).toBe("Hello World");expect(truncate("1231323423424", 5)).toBe("12...");expect(truncate("12345", 5)).toBe("12345");expect(truncate("1231323423424", 5, "-")).toBe("1231-");
});test("如果长度参数不是一个数字,那么返回一个空字符串", () => {expect(truncate("Hello World", NaN)).toBe("");expect(truncate("Hello World", "abc" as any)).toBe("");
});test("应该正确处理空字符串和未定义的输入", () => {expect(truncate("", 5)).toBe("");expect(truncate(undefined, 5)).toBe("un...");
});test("应该正确处理省略号参数", () => {expect(truncate("Hello World", 5, "...")).toBe("He...");expect(truncate("Hello World", 10, "---")).toBe("Hello W---");
});test("始终应该返回一个字符串", () => {expect(typeof truncate("Hello World", 5)).toBe("string");expect(typeof truncate("Hello World", NaN)).toBe("string");expect(typeof truncate(undefined, 5)).toBe("string");
});

6.3 debounce 方法

函数防抖是一个很常见的需求,我们扩展一个 debounce 方法,可以对传入的函数做防抖处理

对应的代码如下:

type FuncType = (...args: any[]) => any;
export function debounce<T extends FuncType>(func: T,wait: number
): (...args: Parameters<T>) => void {let timerId: ReturnType<typeof setTimeout> | null = null;return function (...args: Parameters<T>): void {if (timerId) {clearTimeout(timerId);}timerId = setTimeout(() => {func(...args);}, wait);};
}

对应的测试代码如下:

import { debounce } from "../src/function";beforeEach(() => {jest.useFakeTimers();
});afterEach(() => {jest.clearAllTimers();jest.useRealTimers();
});test("应该在等待时间之后调用函数",()=>{const func = jest.fn();const debouncedFunc = debounce(func, 1000);debouncedFunc();jest.advanceTimersByTime(500);expect(func).toHaveBeenCalledTimes(0);jest.advanceTimersByTime(500);expect(func).toHaveBeenCalledTimes(1);
})test("当防抖函数执行的时候,始终只执行最后一次的调用",()=>{const func = jest.fn();const debouncedFunc = debounce(func, 1000);debouncedFunc('a');debouncedFunc('b');debouncedFunc('c');jest.advanceTimersByTime(1000);expect(func).toHaveBeenCalledWith('c');
})test("在等待时间内又调用了函数,重置计时器",()=>{const func = jest.fn();const debouncedFunc = debounce(func, 1000);debouncedFunc();jest.advanceTimersByTime(500);debouncedFunc();jest.advanceTimersByTime(500);expect(func).toHaveBeenCalledTimes(0);jest.advanceTimersByTime(1000);expect(func).toHaveBeenCalledTimes(1);
})

7. 运行测试

package.json 中添加一个测试脚本。

{"scripts": {"test": "jest"}
}

运行测试命令:

npm test

8. 构建和发布

package.json 中添加构建脚本。

{"scripts": {"build": "tsc","test": "jest"}
}

构建项目:

npm run build

发布到 npm:

npm login
npm publish

总结

通过以上步骤,我们成功地搭建了一个简单的 JavaScript 工具库项目 jstoolpack,并实现了 rangetruncatedebounce 三个常用工具方法。我们使用了 TypeScript 进行类型检查,并使用 Jest 进行单元测试,确保代码的健壮性和可靠性。最后,我们通过 npm 发布了这个工具库,使其可以被其他开发者使用。


http://www.ppmy.cn/embedded/136641.html

相关文章

深度学习:1-of-N 编码详解

1-of-N 编码详解 1-of-N 编码&#xff0c;也称为独热编码&#xff08;One-Hot Encoding&#xff09;&#xff0c;是一种常用于处理分类变量的编码技术。在此编码方案中&#xff0c;每个类别被表示为一个长度等于类别数目的二进制向量&#xff0c;其中一个元素设为1&#xff0c…

CI/CD 实践总结

本文旨在介绍ZBJ DevOps团队倾力打造的DevOps平台中关于CI/CD流水线部分的实践。历经三次大版本迭代更新的流水线&#xff0c;完美切合ZBJ各种业务发展需求&#xff0c;在满足高频率交付的同时&#xff0c;提高了研发效率&#xff0c;降低了研发成本&#xff0c;保证了交付质量…

[Redis] Redis主从复制模式

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

[NewStar 2024] week5完结

每次都需要用手机验证码登录&#xff0c;题作的差不多就没再进过。今天把week5解出的部分记录下。好像时间过去很久了。 Crypto 没e也能完 这题给了e,p,q,dp,dq。真不清楚还缺啥 long_to_bytes(pow(c,dp,p)) 格格你好棒 给了a,b和提示((p2*r) * 3*a q) % b < 70 其中r…

蓝桥杯-网络安全比赛题目-遗漏的压缩包

小蓝同学给你发来了他自己开发的网站链接&#xff0c; 他说他故意留下了一个压缩包文件&#xff0c;里面有网站的源代码&#xff0c; 他想考验一下你的网络安全技能。 &#xff08;点击“下发赛题”后&#xff0c;你将得到一个http链接。如果该链接自动跳转到https&#xff0c;…

C# 高精度计时器Stopwatch

C# 高精度计时器Stopwatch 引言经典举例(1)启动和停止方法实例(2)复位和重启方法实例 小结 引言 偶然发现C# 的计时器类Stopwatch&#xff0c;他特别适合测量运行时间&#xff0c;使用简单、计时精确。它源于命名空间System.Diagnostics&#xff0c;使用时必须using引用。 经…

Python实现PSO粒子群优化算法优化CNN-Transformer回归模型(优化神经元数量和迭代次数)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后关注获取。 1.项目背景 随着深度学习技术的发展&#xff0c;卷积神经网络&#xff08;CNN&#xff09;和变换器&#xff0…

python manage.py命令集

python manage.py 是 Django 框架中用于管理 Django 项目的命令行工具。它提供了一系列命令&#xff0c;用于创建应用、运行服务器、创建数据库迁移、管理静态文件等。 startproject python manage.py startproject myproject 创建一个新的 Django 项目。myproject 是项目的…