Loading

测试与重构场景

让AI正确帮你写测试和重构代码

测试这个事

说句大实话,写测试这件事,大部分程序员都是拒绝的。不是不知道测试的重要性,实在是业务代码都写不过来,哪有那么多时间写测试。

但自从我用上AI编程工具之后,测试写得比以前勤多了。原因很简单:让AI帮我写测试,比我自己写快多了。当然前提是,你得告诉AI测试应该怎么写。

测试场景的AGENTS.md怎么写

# AGENTS.md - 测试场景配置

## 测试类型定义

### 单元测试
- 位置:`src/**/*.test.ts` 或 `tests/unit/`
- 框架:Vitest / Jest
- 要求:每个导出函数/类必须有对应测试
- 覆盖率目标:70%以上

### 集成测试
- 位置:`tests/integration/`
- 使用测试数据库/环境
- 测试前后自动清理测试数据
- 覆盖核心业务流程

### E2E 测试
- 位置:`tests/e2e/`
- 使用 Playwright / Cypress
- 覆盖关键用户路径
- 在 CI 中运行

## 测试命令

```bash
# 运行所有测试
npm test

# 监听模式(开发时使用)
npm test -- --watch

# 运行特定文件
npm test -- src/utils/helper.test.ts

# 运行特定标签的测试
npm test -- --grep "unit"

# 生成覆盖率报告
npm test -- --coverage

# E2E 测试
npm run test:e2e

测试数据管理

测试夹具(Fixtures)

  • 公共测试数据放在 tests/fixtures/ 目录
  • 使用工厂函数生成测试数据
  • 避免硬编码测试数据

Mock 策略

  • 外部 API 使用 MSW (Mock Service Worker)
  • 数据库使用测试数据库
  • 第三方服务使用 jest.mock()

示例:用户创建测试

// tests/unit/user.service.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { UserService } from '../../src/services/user.service';
import { UserRepository } from '../../src/repositories/user.repository';

describe('UserService', () => {
  let userService: UserService;
  let mockRepository: UserRepository;

  beforeEach(() => {
    // 创建 mock
    mockRepository = {
      create: vi.fn(),
      findById: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
    } as any;

    // 注入 mock
    userService = new UserService(mockRepository);
  });

  it('should create a user', async () => {
    const userData = { name: 'John', email: 'john@example.com' };
    const createdUser = { id: '1', ...userData };

    mockRepository.create.mockResolvedValue(createdUser);

    const result = await userService.createUser(userData);

    expect(result).toEqual(createdUser);
    expect(mockRepository.create).toHaveBeenCalledWith(userData);
  });
});

AI 辅助测试指南

生成测试时

  1. 先了解现有测试风格,模仿着写
  2. 使用真实数据模拟而非硬编码值
  3. 测试边界条件和异常情况
  4. 为异步代码编写正确的异步测试

测试命名规范

// 好的命名
describe('UserService', () => {
  it('should create user with valid data', () => {});
  it('should throw error with invalid email', () => {});
  it('should return user by id', () => {});
});

// 不好的命名
describe('UserService', () => {
  it('test1', () => {});
  it('create', () => {});
});

必须覆盖的场景

  • 正常业务流程
  • 边界条件(空值、零值、极大值)
  • 异常情况(网络错误、超时、权限不足)
  • 并发场景(如果适用)

注意事项

  • 测试文件与源文件放在相同目录或 __tests__ 目录
  • 使用 describe/test 结构组织测试
  • 每个测试只测试一件事
  • 测试名称使用描述性名称
  • 避免测试之间的依赖
  • 失败的测试必须立即修复

## 代码重构场景

重构这个事,风险很高。改好了皆大欢喜,改差了就是事故现场。让AI帮忙重构,必须得把规矩定清楚。

```markdown
# AGENTS.md - 代码重构场景配置

## 重构原则

1. **小步前进**:每次只重构一小部分,改完立即测试
2. **保持功能**:重构后必须通过所有测试,功能行为不变
3. **频繁提交**:每个重构步骤单独提交,便于回滚
4. **红绿重构**:先写失败的测试,再改代码让测试通过

## 重构前检查清单

```bash
# 确保测试全部通过
npm test

# 检查类型错误
npm run typecheck

# 运行 lint
npm run lint

# 确保没有未提交的更改
git status

常见重构类型指南

1. 命名重构

  • 使用有意义的变量/函数名
  • 遵循语言命名规范(PascalCase, camelCase)
  • 避免缩写(除非是公认的,如 id, url, api)
  • 函数名应该表达意图

2. 函数重构

  • 单一职责:每个函数只做一件事
  • 参数控制:不超过 3 个参数
  • 行数控制:单个函数不超过 50 行
  • 提取重复代码为独立函数

3. 类重构

  • 遵循 SOLID 原则
  • 依赖注入代替直接依赖
  • 使用接口抽象具体实现
  • 避免上帝类(God Class)

4. 模块重构

  • 高内聚、低耦合
  • 循环依赖必须消除
  • 使用 barrel 文件导出公共 API
  • 明确模块的公开接口

5. 条件语句重构

  • 优先使用多态而非 switch/if
  • 使用策略模式替代复杂条件
  • 提早返回减少嵌套

重构安全规则

禁止事项

  • 禁止在重构时添加新功能
  • 禁止改变现有 API 行为
  • 禁止跳过测试直接提交
  • 禁止大规模重写
  • 禁止删除测试

必须事项

  • 重构前必须创建备份分支
  • 重构前必须确认所有测试通过
  • 重构后必须重新运行所有测试
  • 必须使用 refactor 类型的 commit message

回滚策略

# 如果重构出问题,快速回滚
git stash
git checkout backup-branch

# 或者使用 git reflog 找回重构前的状态
git reflog
git reset --hard HEAD@{n}

注意事项

  • 重构前确保有足够的测试覆盖
  • 大型重构拆分成多个小步骤
  • 重构完成后更新相关文档
  • 复杂重构先在本地验证
  • 重要重构需要代码审查

## 实际案例:让AI帮忙写测试

我用一个真实例子来说明测试场景下AGENTS.md的作用。

假设你有一个用户服务,里面有个注册功能。你让AI帮你写测试,如果你没有AGENTS.md,AI可能给你写出这样的测试:

```typescript
it('test register', () => {
  const result = register({ name: 'John', email: 'john@test.com' });
  expect(result).toBeDefined();
});

这个测试有啥用?基本上啥也验证不了。

但如果你在AGENTS.md里写清楚了测试规范,AI就能写出这样的测试:

describe('UserService.register', () => {
  it('should create user with valid data', async () => {
    const input = { name: 'John', email: 'john@test.com', password: 'Password123!' };
    const result = await userService.register(input);

    expect(result.id).toBeDefined();
    expect(result.email).toBe(input.email);
    expect(result.password).not.toBe(input.password); // 密码应该被哈希
  });

  it('should throw error with duplicate email', async () => {
    const input = { name: 'John', email: 'exists@test.com', password: 'Password123!' };
    await userService.register(input);

    await expect(userService.register(input)).rejects.toThrow('Email already exists');
  });

  it('should throw error with invalid email', async () => {
    const input = { name: 'John', email: 'invalid', password: 'Password123!' };

    await expect(userService.register(input)).rejects.toThrow('Invalid email format');
  });

  it('should throw error with weak password', async () => {
    const input = { name: 'John', email: 'john@test.com', password: '123' };

    await expect(userService.register(input)).rejects.toThrow('Password too weak');
  });
});

看到了吗?有AGENTS.md和没有AGENTS.md,AI写出来的测试质量完全不在一个水平上。

小结

测试和重构场景下的AGENTS.md,关键在于把规范写清楚。测试要覆盖哪些场景、重构要遵守什么规则,这些都说清楚了,AI才能帮你写出高质量的测试代码和安全可靠的重构。

下期聊聊高级技巧:怎么让AGENTS.md自己迭代进化。这是一个很有意思的话题,值得展开说说。


posted @ 2026-03-17 16:39  饭勺oO  阅读(1223)  评论(0)    收藏  举报