Files
Project-Caffeine/docs/guide/project-caffeine-code-testing-specification-guide.md
2026-03-07 14:21:51 +08:00

255 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
---
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.