当前位置: 首页 > news >正文

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

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.mrgr.cn/news/67270.html

相关文章:

  • 实习冲刺Day15
  • 乐尚代驾十订单支付seata、rabbitmq异步消息、redisson延迟队列
  • 零基础快速入门MATLAB
  • 【数据结构】算法的时间复杂度和空间复杂度
  • Ollama AI 框架缺陷可能导致 DoS、模型盗窃和中毒
  • 如何对LabVIEW软件进行性能评估?
  • 详解:字符串常量池
  • Linux入门之vim
  • Git超详细笔记包含IDEA整合操作
  • 狐假虎威,数据流图其实很简单
  • 题目练习之二叉树那些事儿
  • Centos7修改默认yum源(ARM架构)(2024年6月30号后)
  • 防火墙|WAF|漏洞|网络安全
  • 信息学奥赛一本通 1395:烦人的幻灯片(slides)
  • Flutter鸿蒙next 中的 Drawer 导航栏
  • 【360】基于springboot的志愿服务管理系统
  • 粒子群优化双向深度学习!PSO-BiTCN-BiGRU-Attention多输入单输出回归预测
  • 【云岚到家】-day09-2-秒杀抢购
  • 为什么我的软件内存占用这么高?从内存占用过高到C++内存管理方法
  • 【数据结构】插入排序——直接插入排序 和 希尔排序
  • 操作系统——作业、进程调度算法
  • 初识多线程
  • Linux 系统目录结构
  • 分布式中常见的问题及其解决办法
  • Go + Wasm
  • C#-类:静态成员的介绍