---
name: testing-rules
description: "团队通用测试与 Mock 隔离规范。适用于所有新增业务逻辑与逻辑修改的单元测试编写。"
globs: ["*"]
alwaysApply: true
updated: 2026-05-22
---

# 团队通用测试与 Mock 隔离规范

> [!IMPORTANT]
> 稳定的测试是保障系统持续交付的基石。在修改业务逻辑或编写新测试用例时，AI 助手必须无条件确保测试用例具备极高隔离性与对边界条件的完备覆盖。

## 1. 适用场景

当您新增核心业务逻辑、修改异常分支，或在项目中新增测试脚本时，必须遵循本规范编写单元测试或集成测试。

## 2. 操作规则

1. **测试彻底隔离 (Strict Isolation)**：
   - 单元测试**绝对禁止**产生真实的外部网络调用、物理数据库读写或本地文件系统破坏性操作。
   - 所有依赖的外部 HTTP API、第三方 SDK、以及内部复杂的数据库交互，必须一律使用 Mock（例如 Python 的 `unittest.mock`，或 JS/TS 的 `jest.mock` / `msw`）进行拦截隔离。
2. **正负边界全覆盖 (Boundary Conditions)**：
   - 每个测试套件必须同时包含**正向用例**（正常输入并断言成功）与**负向边界用例**（空值、异常输入、超时、溢出等，并断言其能够抛出指定异常或返回特定错误结构）。
3. **有效性断言 (Meaningful Assertions)**：
   - 禁止编写无实质断言的“空跑测试”。必须针对函数核心返回对象的关键字段值进行深度比较，或对 Mock 对象的被调用次数与入参（如 `toHaveBeenCalledWith`）进行精确断言。

## 3. 禁止事项

- 严禁硬编码真实的测试账号密码、在线 API Token。
- 禁止在测试中连接生产环境或预发环境的数据库。
- 严禁为了逃避流水线失败而使用空 catch 块吞掉测试异常，或将报错的测试用例裸加 `@skip`。

## 4. 验证方式

- **Python 后端**：如果项目使用 Pytest，运行 `pytest tests/ -v --cov=app`；如果测试目录或包名不同，应使用项目 README/CI 中声明的等价命令。
- **JS/TS 前端**：如果项目使用 Jest/Vitest，运行 `npm test`、`npm run test` 或 CI 中声明的覆盖率命令。
- **通用要求**：无法运行测试时，必须在回复或 PR 描述中明确说明原因，并列出已做的静态检查与手工验证。

## 5. 代码对比示例

### ❌ 错误示例（产生真实网络请求，无 Mock，脆弱且易受网络波动影响）

```typescript
// 违反规则 2.1：直接发起了真实的外网请求，极易在 CI/CD 中因网络抖动而报错失败
import { fetchUserProfile } from "./userService";

test("get user profile returns active status", async () => {
  const profile = await fetchUserProfile("12345"); // 发送了真实的 HTTP 请求
  expect(profile.status).toBe("active");
});
```

###  正确方向（采用 Mock 模拟 API 响应，稳定且秒级返回）

```typescript
import { fetchUserProfile } from "./userService";
import axios from "axios";

// 遵循规则 2.1：对网络请求客户端 axios 进行 Mock 拦截
jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;

describe("fetchUserProfile boundary test", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  // 遵循规则 2.2：正向用例
  test("should resolve with profile data when API responds successfully", async () => {
    mockedAxios.get.mockResolvedValueOnce({
      status: 200,
      data: { id: "12345", status: "active", email: "user@example.com" }
    });

    const profile = await fetchUserProfile("12345");
    
    // 遵循规则 2.3：对关键数据进行深度校验断言
    expect(profile.id).toBe("12345");
    expect(profile.status).toBe("active");
    expect(mockedAxios.get).toHaveBeenCalledWith("/api/v1/users/12345");
    expect(mockedAxios.get).toHaveBeenCalledTimes(1);
  });

  // 遵循规则 2.2：负向测试，捕获并处理 500 服务器错误边界
  test("should throw custom user service exception when API returns 500", async () => {
    mockedAxios.get.mockRejectedValueOnce(new Error("Internal Server Error"));

    await expect(fetchUserProfile("12345")).rejects.toThrow("Failed to load user profile");
  });
});
```
