同意
This commit is contained in:
254
docs/guide/project-caffeine-code-testing-specification-guide.md
Normal file
254
docs/guide/project-caffeine-code-testing-specification-guide.md
Normal file
@@ -0,0 +1,254 @@
|
||||
<!--
|
||||
---
|
||||
title: Project Caffeine 项目代码测试规范指南
|
||||
description: 为保证 Project Caffeine 提示词策略 MCP Server 的高可用性与健壮性制定的代码测试规范,涵盖单元测试、集成测试及相关最佳实践。
|
||||
type: Testing Guide
|
||||
version: v1.0.0
|
||||
file: project-caffeine-code-testing-specification-guide.md
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-07
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- Testing
|
||||
- Jest
|
||||
- MCP Server
|
||||
- Quality Assurance
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# Project Caffeine 项目代码测试规范指南
|
||||
|
||||
为保证 Project Caffeine 提示词策略 MCP Server 的高可用性、健壮性及代码质量,特制定本代码测试规范。本指南主要适用于开发阶段的自动化测试(单元测试与集成测试)及相关最佳实践。
|
||||
## 1. 测试工具栈建议
|
||||
|
||||
本项目推荐使用 **Jest** 作为核心测试框架,搭配 `ts-jest` 无缝支持 TypeScript 原生测试。
|
||||
|
||||
- **测试框架**: `jest`, `@types/jest`
|
||||
|
||||
- **TypeScript 支持**: `ts-jest`
|
||||
|
||||
- **Mock 工具**: Jest 内置 Mock 功能 (`jest.mock()`)
|
||||
|
||||
- **手动/交互式测试**: MCP Inspector
|
||||
|
||||
|
||||
_(如尚未安装,可通过 `npm install --save-dev jest ts-jest @types/jest` 安装,并使用 `npx ts-jest config:init` 初始化配置。)_
|
||||
|
||||
```bash
|
||||
npm install --save-dev jest ts-jest @types/jest
|
||||
npx ts-jest config:init
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 测试分层策略
|
||||
|
||||
基于项目的经典三层架构(接入层 `app.ts` -> 控制层 `controllers` -> 服务层 `services`),测试策略分为以下三个层级:
|
||||
|
||||
### 2.1 服务层单元测试 (Service Unit Tests)
|
||||
|
||||
- **目标**: 验证纯业务逻辑的正确性,这是测试的重中之重。
|
||||
|
||||
- **重点对象 (以 v0.1.1 为例)**: `intentService.ts` (意图拆解算法), `resourceService.ts` (文件操作), `promptService.ts` (JSON 解析与组装)。
|
||||
|
||||
- **策略**: 对于纯函数(例如 v0.1.1 中的 `generateSearchQueries`),提供不同的输入(边界值、空值、正常值)验证输出;对于涉及文件系统的操作(例如 `readObsidianNote`),**必须使用 Mock** 拦截原生 `fs` 调用,严禁在单元测试中真实读写物理磁盘。
|
||||
|
||||
### 2.2 控制层单元测试 (Controller Unit Tests)
|
||||
|
||||
- **目标**: 验证参数的 Zod 校验逻辑以及请求路由分发的正确性。
|
||||
|
||||
- **重点对象 (以 v0.1.1 为例)**: `promptsController.ts`, `toolsController.ts`。
|
||||
|
||||
- **策略**: Mock 掉底层的 Service 函数。重点测试:当传入非法参数时(如不带 `.md` 后缀的文件名),Zod Schema 是否能正确拦截并返回 `isError: true` 和标准的 MCP 错误响应格式。
|
||||
|
||||
|
||||
### 2.3 集成/E2E测试 (Integration Tests)
|
||||
|
||||
- **目标**: 验证 MCP Server 与客户端之间的 STDIO 协议通信及功能全链路。
|
||||
|
||||
- **策略**: 自动化层面投入较少,主要依赖 **MCP Inspector** 进行人工或半自动化点检。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 3. 测试文件命名与目录结构
|
||||
|
||||
- **目录位置**: 测试文件应与被测试的源码文件同级,统一放在同级的 `__tests__` 文件夹中,或直接与源码文件同级。
|
||||
|
||||
- **命名规范**: 以 `.test.ts` 或 `.spec.ts` 结尾。
|
||||
|
||||
- 例如(以 v0.1.1 为例):被测文件 `src/services/intentService.ts`,测试文件应为 `src/services/__tests__/intentService.test.ts`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 单元测试编写规范 (编写范例)
|
||||
|
||||
### 4.1 纯粹逻辑的测试 (无副作用)
|
||||
|
||||
针对 `intentService.ts` 中的 `generateSearchQueries` 函数,采用 **Given-When-Then** (假设-当-那么) 模式或清晰的用例描述:
|
||||
|
||||
```
|
||||
// src/services/__tests__/intentService.test.ts
|
||||
import { generateSearchQueries } from '../intentService';
|
||||
|
||||
describe('intentService -> generateSearchQueries', () => {
|
||||
it('当输入常规查询时,应该正确分词并返回3-5个检索词', () => {
|
||||
const result = generateSearchQueries('新能源汽车电池回收技术');
|
||||
expect(result.length).toBeGreaterThanOrEqual(3);
|
||||
expect(result.length).toBeLessThanOrEqual(5);
|
||||
expect(result).toContain('新能源汽车电池回收技术 相关研究'); // 验证补全逻辑
|
||||
});
|
||||
|
||||
it('当输入为空字符串时,应该返回默认的后备检索词', () => {
|
||||
const result = generateSearchQueries(' ');
|
||||
expect(result).toEqual(['通用研究主题']);
|
||||
});
|
||||
|
||||
it('当输入带有大量标点符号时,应该正确清洗', () => {
|
||||
const result = generateSearchQueries('AI芯片,市场趋势?2026;');
|
||||
// 验证标点符号是否被正确视为空格分割
|
||||
expect(result).toContain('AI芯片');
|
||||
expect(result).toContain('市场趋势');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 4.2 依赖外部系统(文件系统 IO)的 Mock 测试
|
||||
|
||||
针对 `resourceService.ts`,绝不允许污染真实的 `OBSIDIAN_VAULT_PATH`。
|
||||
|
||||
```
|
||||
// src/services/__tests__/resourceService.test.ts
|
||||
import { readObsidianNote, saveNote } from '../resourceService';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
// 全局 Mock fs 模块
|
||||
jest.mock('fs/promises');
|
||||
|
||||
describe('resourceService', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks(); // 每个用例前清除 mock 状态
|
||||
});
|
||||
|
||||
describe('readObsidianNote', () => {
|
||||
it('当发生路径遍历攻击时 (../),应该抛出安全警告', async () => {
|
||||
await expect(readObsidianNote('../../etc/passwd')).rejects.toThrow('安全警告:越权访问拦截!');
|
||||
});
|
||||
|
||||
it('当读取合法路径时,应该返回文件内容', async () => {
|
||||
// 模拟 fs.readFile 返回成功
|
||||
(fs.readFile as jest.Mock).mockResolvedValueOnce('# Mock Note Content');
|
||||
|
||||
const content = await readObsidianNote('test.md');
|
||||
expect(content).toBe('# Mock Note Content');
|
||||
expect(fs.readFile).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 4.3 控制层的数据校验 (Zod Schema) 测试
|
||||
|
||||
针对 `toolsController.ts`,验证 Zod 的拦截机制与 MCP 响应格式是否统一:
|
||||
|
||||
```
|
||||
// src/controllers/__tests__/toolsController.test.ts
|
||||
import { handleToolCall } from '../toolsController';
|
||||
import * as resourceService from '../../services/resourceService';
|
||||
|
||||
jest.mock('../../services/resourceService');
|
||||
|
||||
describe('toolsController -> handleToolCall', () => {
|
||||
it('当调用 save_note 且文件名缺失 .md 后缀时,应该返回 Zod 拦截的错误响应', async () => {
|
||||
const params = { filename: 'invalidName', content: 'test' };
|
||||
const result = await handleToolCall('save_note', params);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].type).toBe('text');
|
||||
expect(result.content[0].text).toContain('文件名必须以 .md 结尾'); // 验证 Zod 自定义错误消息
|
||||
});
|
||||
|
||||
it('当调用未知的工具名时,应该返回未知工具的错误响应', async () => {
|
||||
const result = await handleToolCall('unknown_tool', {});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('未知工具: unknown_tool');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 测试覆盖率标准
|
||||
|
||||
为了保障核心功能的稳定性,项目 CI/CD 流程中应设置覆盖率门槛:
|
||||
|
||||
- **Service 层**: 语句覆盖率 (Statements) 不低于 **85%**。
|
||||
|
||||
- **Controller 层**: 分支覆盖率 (Branches) 不低于 **80%**。
|
||||
|
||||
- 配置命令:`jest --coverage`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试编写的红线规定
|
||||
|
||||
1. **禁止真实 I/O**: 单元测试中严禁发起真实的磁盘读写或网络请求。必须使用 Mock。
|
||||
|
||||
2. **独立性**: 每个 `it` 用例必须相互独立。禁止用例 A 的运行结果作为用例 B 的依赖。必须善用 `beforeEach` 和 `afterEach` 清理状态(例如 `jest.clearAllMocks()`)。
|
||||
|
||||
3. **断言明确**: 不要只断言 `expect(result).toBeDefined()`。必须断言具体的数据结构或内容,例如 MCP 要求的 `content: [{ type: "text", text: "..." }]` 结构。
|
||||
|
||||
4. **涵盖异常流**: 测试不仅要覆盖“Happy Path”(快乐路径,即正常执行的流程),**必须**编写针对 `throw Error` 和 Zod `isError: true` 的异常分支测试(Unhappy Path)。
|
||||
|
||||
|
||||
**附注**: 编写完测试后,建议将 `npm run test` 和 `npm run test:coverage` 配置入 `package.json` 的 `scripts` 中,以便日常开发与构建流集成。
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试执行与查看指引
|
||||
|
||||
项目的 `package.json` 中已集成了标准化的测试脚本指令,开发者可以直接在终端使用以下命令执行测试:
|
||||
|
||||
### 7.1 运行所有测试
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
- **功能说明**:Jest 会自动全局扫描您的项目,找到所有符合命名规范的测试文件(如 `*.test.ts` 或 `*.spec.ts`),并串行/并行执行其中的所有用例。
|
||||
|
||||
- **输出查看**:终端会实时输出每个用例的测试结果,绿色的 `PASS` 表示通过,红色的 `FAIL` 表示失败及具体的报错堆栈。
|
||||
|
||||
|
||||
### 7.2 运行测试并生成代码覆盖率报告
|
||||
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
- **功能说明**:在跑完所有测试用例的同时,额外收集代码被测试用例“触碰”的情况,借此衡量测试的完备度。
|
||||
|
||||
- **输出查看**:
|
||||
|
||||
- **终端报表**:在控制台底部会输出一张表格,直观展示当前项目的“语句 (Stmts)”、“分支 (Branch)”、“函数 (Funcs)”和“行 (Lines)”覆盖率比例。
|
||||
|
||||
- **网页视图**:命令执行完毕后,项目根目录会自动生成一个 `coverage/` 文件夹。您可以通过浏览器打开 `coverage/lcov-report/index.html`,以直观的界面逐行查看哪些代码片段处于“漏测”状态。
|
||||
|
||||
|
||||
### 7.3 运行指定文件的测试
|
||||
|
||||
如果您正在专注开发某个模块(如意图拆解服务),不需要每次都全量执行测试,可在命令后追加关键词或文件名进行过滤:
|
||||
|
||||
```bash
|
||||
npm run test -- intentService
|
||||
```
|
||||
|
||||
- **功能说明**:Jest 将仅匹配文件名中包含 `intentService` 的测试文件并执行,从而大幅提高 TDD(测试驱动开发)环节下的反馈效率。
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
169
docs/guide/project-caffeine-coding-specification-guidelines.md
Normal file
169
docs/guide/project-caffeine-coding-specification-guidelines.md
Normal file
@@ -0,0 +1,169 @@
|
||||
<!--
|
||||
---
|
||||
title: "Project Caffeine 代码编写规范指南"
|
||||
description: "为 Project Caffeine 项目开发人员提供一致的编码风格、系统架构指引和最佳实践的规范文档"
|
||||
type: "Guide"
|
||||
version: "v0.1.0"
|
||||
file: project-caffeine-coding-specification-guidelines.md
|
||||
author: "Gitconomy Research-郭晧"
|
||||
date: 2026-03-02
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- 代码规范
|
||||
- TypeScript
|
||||
- MCP
|
||||
- Node.js
|
||||
license: "CC BY-SA 4.0"
|
||||
status: "Active"
|
||||
---
|
||||
-->
|
||||
# Project Caffeine 代码编写指南
|
||||
## 1. 目的与概述
|
||||
|
||||
本代码编写规范旨在为 **Project Caffeine** 项目的开发人员提供一致的编码风格和最佳实践,确保代码的可读性、可维护性和团队协作的效率。遵循这些规范将有助于提升代码质量、减少错误并优化开发过程。
|
||||
|
||||
---
|
||||
|
||||
## 2. 技术栈与工程化基础
|
||||
|
||||
所有代码必须遵循清晰、简洁、结构化的原则,并基于以下核心技术栈构建:
|
||||
|
||||
- **核心语言**:所有核心功能必须使用 TypeScript 编写。强烈建议优先使用 TypeScript,以利用其静态类型检查减少运行时错误。
|
||||
- **运行环境**:服务端必须使用 Node.js (LTS v20+)。需确保高效的异步执行和非阻塞 I/O 操作。
|
||||
- **工程架构**:采用原生 npm Workspaces 进行 Monorepo(单体仓库)包管理。必须在根目录统一管控共享的 JSON-RPC Schema 与多个微服务子包。
|
||||
|
||||
---
|
||||
|
||||
## 3. 代码风格与命名规则
|
||||
|
||||
## 3.1 格式与排版
|
||||
|
||||
- **缩进**:使用 **2 个空格**作为缩进(严禁使用 Tab)。
|
||||
- **行长度**:每行代码的字符数应不超过 **120 个字符**,避免横向滚动条,便于阅读和维护。
|
||||
- **文件结尾**:每个文件的结尾必须保留一个空行。
|
||||
- **函数复杂度**:长函数或复杂逻辑必须拆分为多个函数,确保每个函数的职责单一。
|
||||
|
||||
## 3.2 命名规范
|
||||
|
||||
- **变量与函数**:使用 `camelCase`(小驼峰命名法),如 `userProfile`, `generateReport`。
|
||||
- **类与接口**:使用 `PascalCase`(大驼峰命名法),如 `UserService`, `ReportGenerator`。
|
||||
- **常量**:使用 `UPPER_SNAKE_CASE`(全大写蛇形命名法),如 `MAX_RETRIES`, `API_TIMEOUT`。
|
||||
- **文件与目录**:使用 `kebab-case`(短横线命名法),如 `user-service.ts`, `data-fetcher.ts`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 注释与文档
|
||||
|
||||
每个模块、类和复杂函数必须包含文件头部文档或块级注释,简要说明其作用。必须使用标准的 **JSDoc** 格式来解释功能、参数和返回值:
|
||||
|
||||
```TypeScript
|
||||
/**
|
||||
* 计算两个数的和。
|
||||
* @param {number} a - 第一个加数。
|
||||
* @param {number} b - 第二个加数。
|
||||
* @returns {number} - 返回两数之和。
|
||||
*/
|
||||
function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## 5. 开源许可证声明
|
||||
|
||||
**所有源代码文件的顶部必须包含开源相关的版权信息与 SPDX 格式的许可证标识。** 本项目源代码统一采用 **MIT 许可证**。在每个 `.ts` 或者 `。js` 文件头部必须添加如下块级注释:
|
||||
|
||||
```plaintext
|
||||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
```
|
||||
为了保障代码的可追溯性并尊重每一位开发者的劳动成果,当新的团队成员或开源社区开发者对该文件进行了**实质性修改或重构**时,应当在头部的 `Contributors` 列表中追加自己的信息。
|
||||
|
||||
在每个 `.ts` 或 `.js` 文件头部,必须添加如下块级注释模板:
|
||||
|
||||
|
||||
```plaintext
|
||||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
* - [新贡献者姓名/ID] <[联系邮箱]> ([简述贡献内容,例如:重构了 LRU 缓存模块 / 2026-03])
|
||||
* - [其他贡献者姓名] <[联系邮箱]> ([例如:修复了 MCP 握手超时的 Bug / 2026-04])
|
||||
*/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 目录结构与模块化解耦
|
||||
|
||||
系统应保持高度的模块化,功能模块应独立且彼此解耦。建议采用以下标准目录结构:
|
||||
|
||||
- `/src/controllers`:控制器,处理传入的 JSON-RPC 或 HTTP 请求。
|
||||
- `/src/services`:服务层,包含核心的 MCP 业务逻辑。
|
||||
- `/src/models`:数据模型与 Schema 定义。
|
||||
- `/src/routes`:路由定义(适用于 HTTP+SSE 传输模式)。
|
||||
- `/src/utils`:跨模块共享的工具函数。
|
||||
- `/config`:环境与系统配置文件。
|
||||
- `/tests`:单元测试与集成测试文件。
|
||||
|
||||
---
|
||||
|
||||
## 7. MCP 协议与核心原语
|
||||
|
||||
系统全面采用 JSON 格式作为基础数据承载体,并依赖 **JSON-RPC 2.0** 协议规范来管理消息交换。
|
||||
|
||||
- **传输层**:本地服务必须采用 STDIO 协议进行无网络开销的直接通信。云端部署则采用 HTTP + SSE 模式。
|
||||
- **Tools (工具)**:暴露给 LLM 的操作必须通过 `tools/list` 注册,并包含严谨的 `inputSchema`。
|
||||
- **Resources (资源)**:被动的静态上下文数据需通过 `resources/list` 和 `resources/read` 暴露。
|
||||
- **Prompts (提示词)**:作为复用模板,通过 `prompts/list` 暴露,指导模型构造标准化交互结构。
|
||||
|
||||
---
|
||||
|
||||
## 8. 异常处理与日志记录
|
||||
|
||||
## 7.1 错误捕获与响应
|
||||
|
||||
- 在业务逻辑中,必须使用 `try-catch` 语句来捕获和处理可能的错误。
|
||||
- 对于 HTTP/SSE 传输层,遇到预期错误需返回适当的 HTTP 状态码(如 400 错误请求、404 未找到资源)。对于不可预见的严重异常,返回 500 错误。
|
||||
- 在 MCP 协议层,所有错误必须被标准封装为 JSON-RPC 错误对象,并返回给客户端。
|
||||
|
||||
## 7.2 日志系统
|
||||
|
||||
- 必须使用成熟的日志库(如 `winston` 或 `log4js`)来记录事件。
|
||||
- 明确日志级别:`info`(正常操作流程)、`warn`(潜在问题)、`error`(系统异常)。
|
||||
- 记录重要事件(如用户操作、API 调用),以便追踪、审计和发现模型幻觉。
|
||||
- **安全红线**:绝对禁止在生产环境日志中记录敏感信息(如用户密码、API 密钥、未脱敏的凭证)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 性能优化与上下文管理
|
||||
|
||||
- **异步编程**:必须使用 `async/await` 或 `Promise` 处理网络与文件 I/O,确保不阻塞 Node.js 事件循环。
|
||||
- **语义分块**:对于长篇文档的读取,服务端必须在本地完成文本解析与切割,仅将高度相关的片段同步给客户端,防止 LLM Token 耗尽。
|
||||
- **缓存与数据库**:对于频繁查询的数据,采用 LRU(最近最少使用)缓存策略或引入外部缓存(如 Redis)以减少负载。同时需使用合适的索引、查询优化和分页技术,避免数据库性能瓶颈。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9. 安全性与权限管控
|
||||
|
||||
协议必须遵循“零信任架构原则”,将 AI 生成的指令视为不可信负载。
|
||||
|
||||
- **数据加密**:敏感信息必须加密存储,使用 `bcrypt` 或 `argon2` 进行密码哈希处理。
|
||||
- **认证与授权**:系统通信应使用 JWT 或 OAuth 2.0/2.1 进行认证,并采用作用域限定防止令牌滥用。
|
||||
- **Roots 隔离**:必须实现 Roots(根目录)机制,服务端依据宿主应用传递的 URI 列表划定沙箱,阻断越权访问和路径遍历漏洞。
|
||||
- 定期进行安全审计,确保代码没有易受攻击的注入漏洞。
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
Reference in New Issue
Block a user