Shardeum 如何根据 unit-tests-todo.md 计划为缺失覆盖的模块补写 Jest 单元测试?
【免费下载链接】shardeumShardeum is an EVM based autoscaling blockchain项目地址: https://gitcode.com/GitHub_Trending/sh/shardeum
如果你接手 Shardeum(一个 EVM 兼容、基于动态状态分片的区块链项目)的代码维护,需要为还没有测试的模块补写 Jest 单元测试,仓库根目录的 unit-tests-todo.md 就是现成的任务清单:它列出了 90 个当前缺少单元测试的 TypeScript 文件,每个文件都有唯一的任务编号(#1–#90),并按从简单到复杂排序。本文以该清单为起点,说明如何从中挑选任务、在test/unit/下按仓库既有模式编写测试文件,并用npm test单跑或全量验证结果。
如何读懂 unit-tests-todo.md 并定位要测的文件
unit-tests-todo.md 的核心信息有三部分:
- 总量与分层:共 90 个未覆盖文件,Simple 30 个、Medium 34 个、Complex 26 个;
- 快速索引:
Tasks #1-30是简单文件(常量、类型、导出),Tasks #31-64是中等复杂度(单一职责模块),Tasks #65-90是复杂文件(核心系统组件,如 EVM 解释器、状态管理、块构建逻辑); - 策略建议:文档给出 6 条测试策略,其中与动手写测试直接相关的是两条——"Use Existing Test Patterns: Follow patterns from existing test files in the codebase for consistency"(沿用代码库中现有测试文件的模式),以及 "Mock Dependencies: For complex files, create appropriate mocks for external dependencies"(为复杂文件的外部依赖创建 mock)。文档还给出覆盖目标:工具类文件至少 80% 覆盖,关键业务逻辑 90% 以上。
清单中每个任务行同时给出文件路径和简要描述,例如:
- [ ] **Task #1**: /home/marc/work/shardeum/src/utils/constants.ts - Simple constants file (10 lines) - [ ] **Task #2**: /home/marc/work/shardeum/src/utils/versions.ts - Version reading utility (28 lines)注意路径前缀:清单里的/home/marc/work/shardeum/是计划作者本机目录,在本仓库中对应的就是src/下的相对路径,即 Task #2 对应 src/utils/versions.ts。
文档 "Notes" 部分还给了优先级提示:debug/目录下的文件可放低优先级(它们是开发工具);migration 类文件只需基础验证测试;纯导出的index.ts文件可以不写专门测试。选任务时可以先按自己的熟悉程度从 Simple 段挑,也可以按文档建议直接指定编号或整个章节来认领任务(如 "Task #31, #35, #40")。
环境准备
- Node 版本要求:package.json 中
engines指定"node": "20.19.3"; - 安装依赖必须用
npm ci而不是npm install,这一点在 CLAUDE.md 中明确强调("Install dependencies (use npm ci, not npm install)"):
npm ciCLAUDE.md 还要求提交前跑 lint 与格式检查(npm run lint、npm run format-check),这两个命令在写完测试后同样适用,见验证环节。
了解 Jest 的运行配置
补写测试前先了解 jest.config.js 的关键设置,它们决定了测试文件写在哪里、如何被收集:
module.exports = { preset: 'ts-jest', testEnvironment: 'node', testTimeout: 5000000, // the more node involve in testing, the higher the timeout requires verbose: true, roots: ['<rootDir>/test/unit','<rootDir>/test/testCases'], setupFiles: ['<rootDir>/test/unit/setup.ts'], testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], transform: { '^.+\\.(ts|tsx)$': 'ts-jest', }, moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, resetMocks: true, clearMocks: true, }与写新测试直接相关的几点:
- 单元测试入口是
test/unit/目录(roots配置),测试文件名需匹配*.test.ts等模式,CLAUDE.md 的 Testing 一节也说明:"Test files should follow*.test.tspattern",且 "Unit tests in/test/unit/"; - test/unit/setup.ts 只有一行有效代码:
jest.mock('../../src/index.ts', () => ({})),即把主入口 mock 成空对象。这是防止测试里 import 到src/index.ts时拉起整个应用的关键设置——仓库中现有测试文件顶部普遍有一条注释 "dont import index files, import the specific files you need",写新测试时同样要直接 import 目标模块的具体文件,而不是src的聚合入口; resetMocks: true和clearMocks: true意味着每个测试间 mock 状态会被重置。这是 test/unit/src/utils/versions.test.ts 这类测试采用"先jest.resetModules(),再在beforeEach里用jest.doMock注册依赖 mock,最后用require载入被测模块"写法的原因。给带外部依赖的模块写测试时,可以沿用这个模式。
测试文件的命名与目录约定
仓库现有单元测试目录结构与源码一一对应:test/unit/src/下按src/的目录树镜像组织。例如:
- src/utils/constants.ts 的测试是 test/unit/src/utils/constants.test.ts;
- [src/evm_v2/memory.ts] 的测试是 test/unit/src/evm_v2/memory.test.ts;
- 需要 mock 实现时放在对应目录的
__mocks__/下(如 src/shardeum/mocks/debugRestoreAccounts.ts 被 src/shardeum/debugRestoreAccounts.ts 相关测试使用)。
因此,给清单中某个任务补测试时,测试文件放到test/unit/src/<源文件相对路径>.test.ts。
实战示例:为 Task #2(src/utils/versions.ts)补写测试
以 Simple 段中的 Task #2 为例,说明从"读源码"到"测试通过"的完整流程。
第 1 步:读被测模块,确定导出内容与依赖。src/utils/versions.ts 导出readOperatorVersions(),它通过fs.readFileSync读取 CLI/GUI 两个 package.json 文件(路径常量来自FilePaths.CLI_PACKAGE/FilePaths.GUI_PACKAGE),并用@shardeum-foundation/lib-types的Utils.safeJsonParse解析版本。依赖涉及文件系统,属于 todo 清单策略中"需要 mock 外部依赖"的情况。
第 2 步:参考同一目录下已有的测试模式。test/unit/src/utils/versions.test.ts 就是该模块的测试文件,其结构是仓库内"带依赖 mock 的单测"标准写法:
// Mock fs module jest.mock('fs') // Mock lib-types jest.mock('@shardeum-foundation/lib-types') describe('versions', () => { let mockReadFileSync: jest.Mock let mockSafeJsonParse: jest.Mock beforeEach(() => { jest.clearAllMocks() // Clear the module cache to ensure fresh imports jest.resetModules() // Set up mocks mockReadFileSync = jest.fn() mockSafeJsonParse = jest.fn((data) => JSON.parse(data)) // Apply mocks jest.doMock('fs', () => ({ readFileSync: mockReadFileSync })) jest.doMock('@shardeum-foundation/lib-types', () => ({ Utils: { safeJsonParse: mockSafeJsonParse } })) }) describe('readOperatorVersions', () => { it('should read both CLI and GUI versions successfully', () => { const mockCLIPackage = JSON.stringify({ version: '1.2.3' }) const mockGUIPackage = JSON.stringify({ version: '4.5.6' }) mockReadFileSync.mockImplementation((path) => { if (path === FilePaths.CLI_PACKAGE) return Buffer.from(mockCLIPackage) if (path === FilePaths.GUI_PACKAGE) return Buffer.from(mockGUIPackage) throw new Error(`Unexpected path: ${path}`) }) // Import the function after mocks are set up const { readOperatorVersions } = require('../../../../src/utils/versions') const result = readOperatorVersions() expect(result).toEqual({ operatorCLIVersion: '1.2.3', operatorGUIVersion: '4.5.6' }) // ... }) }) })注意其中的要点:mock 在beforeEach中用jest.doMock注册(配合全局resetMocks/clearMocks配置),被测模块用require在 mock 生效之后才加载;测试覆盖了"两个文件都读取成功""单个文件读取失败返回空字符串""JSON 解析失败""package.json 缺少 version 字段"等分支,这些分支都来自被测函数自身的处理逻辑。
对更简单的模块(如 Task #1 的常量文件 src/utils/constants.ts),则不需要任何 mock,直接断言导出值即可,参考 test/unit/src/utils/constants.test.ts:
import { zeroAddressStr, emptyCodeHash, zeroAddressAccount } from '../../../../src/utils/constants' describe('constants', () => { it('should be a valid ethereum zero address', () => { expect(zeroAddressStr).toBe('0x0000000000000000000000000000000000000000') }) })第 3 步:按同样模式补写你认领的任务。无论哪个任务,流程都是:读源文件确认导出和依赖 → 在test/unit/src/对应位置新建*.test.ts→ 有外部依赖就按versions.test.ts的模式 mock,没有就直接断言。
验证测试
CLAUDE.md 的 Essential Commands 一节给出两条命令:
# 只跑某一个测试文件(path/to/test.ts 换成实际路径) npm test -- test/unit/src/utils/versions.test.ts # 跑全部测试 npm test两点说明:
package.json中配置了"pretest": "npm run compile",所以每次执行npm test(包括单文件)都会先执行tsc -p .编译 TypeScript。新测试文件如果 import 了不存在的导出,会在这一步或 ts-jest 转换时报错;- 提交前再跑 CLAUDE.md 要求的代码质量检查:
npm run lint npm run format-check判断测试是否完成:单文件命令的 Jest 输出显示该文件所有用例通过;随后全量npm test不因为你新增的文件而引入失败。unit-tests-todo.md 中建议的工具类 80%、关键逻辑 90%+ 覆盖目标是文档给出的方向性建议,仓库的普通npm test脚本未带--coverage参数,如需量化覆盖情况需要自行在 Jest 命令上加 coverage 参数(仓库中只有test:smoke等集成脚本默认带--coverage,且它们运行的是多节点集成测试,不适合作为补单元测试的验证手段)。
限制与注意事项
- 清单中的路径带有
/home/marc/work/shardeum/前缀,落到本仓库时统一去掉,只保留src/...部分; - 测试中不要 import
src/index.ts等聚合入口——test/unit/setup.ts 已将其 mock 为空对象,直接 import 具体源文件即可; - 不要动
test/testCases/下的内容当作单元测试:roots虽然同时包含该目录,但那里是集成/冒烟测试(如 test/testCases/transactionPoC.test.ts),且 test/README.md 说明其运行需要json-rpc-server在后台运行等前置条件,与补单元测试是两个任务; - 若你在 Simple 段认领了纯导出
index.ts文件(Task #9–#18 一类),可按 todo 清单 Notes 的说法跳过或只做最基础的导出存在性验证;debug/目录任务可放低优先级。
按 todo 清单的建议顺序推进即可:先 Simple(类型定义、常量、工具函数),再 Medium(单一职责模块,如 precompiles、storage),最后 Complex(EVM 解释器、状态管理等核心实现)。每完成一个任务,回到 unit-tests-todo.md 勾选对应的- [ ],保持计划与实际进度一致。
【免费下载链接】shardeumShardeum is an EVM based autoscaling blockchain项目地址: https://gitcode.com/GitHub_Trending/sh/shardeum
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考