/testing-guide
后端测试编写指南,包括单元测试、集成测试和E2E测试的编写方法和最佳实践
$ npx -y skills add echoVic/boss-skill --skill testing-guide --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/testing-guide
Context preview
The summary Claude sees to decide when to auto-load this skill.
后端测试编写指南,包括单元测试、集成测试和E2E测试的编写方法和最佳实践
SKILL.md
testing-guide.SKILL.mdname: backend/testing-guide
description: 后端测试编写指南,包括单元测试、集成测试和E2E测试的编写方法和最佳实践
type: methodology
agent: boss-backend
后端测试编写指南
测试要求(强制)
> **职责边界**:Backend Agent 是测试的**编写者**,QA Agent 是测试的**验证者**。
测试金字塔
| 测试类型 | 占比 | 要求 | |----------|------|------| | **单元测试** | ~70% | Service 层、业务逻辑必须有测试 | | **集成测试** | ~20% | API 端点、数据库操作测试 | | **E2E 测试** | ~10% | **必须编写**,完整 API 流程测试 |
单元测试编写
Service 层测试
// services/userService.test.ts
import { UserService } from './userService';
import { UserRepository } from '../repositories/userRepository';
jest.mock('../repositories/userRepository');
describe('UserService', () => {
let userService: UserService;
let userRepository: jest.Mocked<UserRepository>;
beforeEach(() => {
userRepository = new UserRepository() as jest.Mocked<UserRepository>;
userService = new UserService(userRepository);
});
describe('getById', () => {
it('returns user when found', async () => {
const mockUser = { id: '1', name: 'Alice', email: 'alice@example.com' };
userRepository.findById.mockResolvedValue(mockUser);
const result = await userService.getById('1');
expect(result).toEqual(mockUser);
expect(userRepository.findById).toHaveBeenCalledWith('1');
});
it('throws NotFoundError when user not found', async () => {
userRepository.findById.mockResolvedValue(null);
await expect(userService.getById('999')).rejects.toThrow('User not found');
});
});
describe('create', () => {
it('creates user with valid data', async () => {
const createData = { name: 'Bob', email: 'bob@example.com' };
const mockUser = { id: '2', ...createData };
userRepository.findByEmail.mockResolvedValue(null);
userRepository.create.mockResolvedValue(mockUser);
const result = await userService.create(createData);
expect(result).toEqual(mockUser);
expect(userRepository.findByEmail).toHaveBeenCalledWith('bob@example.com');
expect(userRepository.create).toHaveBeenCalledWith(createData);
});
it('throws ConflictError when email already exists', async () => {
const createData = { name: 'Bob', email: 'existing@example.com' };
userRepository.findByEmail.mockResolvedValue({ id: '1', name: 'Existing', email: 'existing@example.com' });
await expect(userService.create(createData)).rejects.toThrow('Email already exists');
});
});
});业务逻辑测试
// services/orderService.test.ts
describe('OrderService', () => {
describe('calculateTotal', () => {
it('calculates total with discount', () => {
const items = [
{ price: 100, quantity: 2 },
{ price: 50, quantity: 1 },
];
const discount = 0.1; // 10% off
const total = orderService.calculateTotal(items, discount);
expect(total).toBe(225); // (200 + 50) * 0.9
});
it('handles zero discount', () => {
const items = [{ price: 100, quantity: 1 }];
const total = orderService.calculateTotal(items, 0);
expect(total).toBe(100);
});
});
describe('validateOrder', () => {
it('validates order with sufficient stock', async () => {
const order = { productId: '1', quantity: 5 };
productRepository.findById.mockResolvedValue({ id: '1', stock: 10 });
const result = await orderService.validateOrder(order);
expect(result.valid).toBe(true);
});
it('rejects order with insufficient stock', async () => {
const order = { productId: '1', quantity: 15 };
productRepository.findById.mockResolvedValue({ id: '1', stock: 10 });
const result = await orderService.validateOrder(order);
expect(result.valid).toBe(false);
expect(result.error).toBe('Insufficient stock');
});
});
});集成测试编写
API 端点测试
// controllers/userController.test.ts
import request from 'supertest';
import { app } from '../app';
import { db } from '../db';
describe('User API', () => {
beforeEach(async () => {
// 清理测试数据库
await db.user.deleteMany();
});
afterAll(async () => {
await db.$disconnect();
});
describe('POST /api/users', () => {
it('creates a new user', async () => {
const userData = {
name: 'Alice',
email: 'alice@example.com',
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.data).toMatchObject(userData);
expect(response.body.data.id).toBeDefined();
});
it('returns 400 for invalid email', async () => {
const userData = {
name: 'Bob',
email: 'invalid-email',
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(400);
expect(response.body.success).toBe(false);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
it('returns 409 for duplicate email', async () => {
const userData = {
name: 'Charlie',
email: 'duplicate@example.com',
};
// 创建第一个用户
await request(app).post('/api/users').send(userData);
// 尝试创建重复邮箱的用户
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(409);
expect(response.body.error.code).toBe('CONFLICT');
});
});
describe('GET /api/users/:id', () => {
it('returns user by id', async () => {
// 创建测试用户
const createResponse = await request(app)
.post('/api/users')
.send({ name: 'Dave', email: 'dave@example.com' });
const userId = createResponse.body.data.id;
// 获取用户
const response = await request(app)
.get(`/api/users/${userId}`)
.expect(200);
expect(response.body.data.id).toBe(userId);
expect(response.body.data.name).toBe('Dave');
});
it('returns 404 for non-existent user', asyncRead more
name: backend/testing-guide description: 后端测试编写指南,包括单元测试、集成测试和E2E测试的编写方法和最佳实践 type: methodology agent: boss-backend
后端测试编写指南
测试要求(强制)
> **职责边界**:Backend Agent 是测试的**编写者**,QA Agent 是测试的**验证者**。
测试金字塔
| 测试类型 | 占比 | 要求 | |----------|------|------| | **单元测试** | ~70% | Service 层、业务逻辑必须有测试 | | **集成测试** | ~20% | API 端点、数据库操作测试 | | **E2E 测试** | ~10% | **必须编写**,完整 API 流程测试 |
单元测试编写
Service 层测试
// services/userService.test.ts
import { UserService } from './userService';
import { UserRepository } from '../repositories/userRepository';
jest.mock('../repositories/userRepository');
describe('UserService', () => {
let userService: UserService;
let userRepository: jest.Mocked<UserRepository>;
beforeEach(() => {
userRepository = new UserRepository() as jest.Mocked<UserRepository>;
userService = new UserService(userRepository);
});
describe('getById', () => {
it('returns user when found', async () => {
const mockUser = { id: '1', name: 'Alice', email: 'alice@example.com' };
userRepository.findById.mockResolvedValue(mockUser);
const result = await userService.getById('1');
expect(result).toEqual(mockUser);
expect(userRepository.findById).toHaveBeenCalledWith('1');
});
it('throws NotFoundError when user not found', async () => {
userRepository.findById.mockResolvedValue(null);
await expect(userService.getById('999')).rejects.toThrow('User not found');
});
});
describe('create', () => {
it('creates user with valid data', async () => {
const createData = { name: 'Bob', email: 'bob@example.com' };
const mockUser = { id: '2', ...createData };
userRepository.findByEmail.mockResolvedValue(null);
userRepository.create.mockResolvedValue(mockUser);
const result = await userService.create(createData);
expect(result).toEqual(mockUser);
expect(userRepository.findByEmail).toHaveBeenCalledWith('bob@example.com');
expect(userRepository.create).toHaveBeenCalledWith(createData);
});
it('throws ConflictError when email already exists', async () => {
const createData = { name: 'Bob', email: 'existing@example.com' };
userRepository.findByEmail.mockResolvedValue({ id: '1', name: 'Existing', email: 'existing@example.com' });
await expect(userService.create(createData)).rejects.toThrow('Email already exists');
});
});
});业务逻辑测试
// services/orderService.test.ts
describe('OrderService', () => {
describe('calculateTotal', () => {
it('calculates total with discount', () => {
const items = [
{ price: 100, quantity: 2 },
{ price: 50, quantity: 1 },
];
const discount = 0.1; // 10% off
const total = orderService.calculateTotal(items, discount);
expect(total).toBe(225); // (200 + 50) * 0.9
});
it('handles zero discount', () => {
const items = [{ price: 100, quantity: 1 }];
const total = orderService.calculateTotal(items, 0);
expect(total).toBe(100);
});
});
describe('validateOrder', () => {
it('validates order with sufficient stock', async () => {
const order = { productId: '1', quantity: 5 };
productRepository.findById.mockResolvedValue({ id: '1', stock: 10 });
const result = await orderService.validateOrder(order);
expect(result.valid).toBe(true);
});
it('rejects order with insufficient stock', async () => {
const order = { productId: '1', quantity: 15 };
productRepository.findById.mockResolvedValue({ id: '1', stock: 10 });
const result = await orderService.validateOrder(order);
expect(result.valid).toBe(false);
expect(result.error).toBe('Insufficient stock');
});
});
});集成测试编写
API 端点测试
// controllers/userController.test.ts
import request from 'supertest';
import { app } from '../app';
import { db } from '../db';
describe('User API', () => {
beforeEach(async () => {
// 清理测试数据库
await db.user.deleteMany();
});
afterAll(async () => {
await db.$disconnect();
});
describe('POST /api/users', () => {
it('creates a new user', async () => {
const userData = {
name: 'Alice',
email: 'alice@example.com',
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.data).toMatchObject(userData);
expect(response.body.data.id).toBeDefined();
});
it('returns 400 for invalid email', async () => {
const userData = {
name: 'Bob',
email: 'invalid-email',
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(400);
expect(response.body.success).toBe(false);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
it('returns 409 for duplicate email', async () => {
const userData = {
name: 'Charlie',
email: 'duplicate@example.com',
};
// 创建第一个用户
await request(app).post('/api/users').send(userData);
// 尝试创建重复邮箱的用户
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(409);
expect(response.body.error.code).toBe('CONFLICT');
});
});
describe('GET /api/users/:id', () => {
it('returns user by id', async () => {
// 创建测试用户
const createResponse = await request(app)
.post('/api/users')
.send({ name: 'Dave', email: 'dave@example.com' });
const userId = createResponse.body.data.id;
// 获取用户
const response = await request(app)
.get(`/api/users/${userId}`)
.expect(200);
expect(response.body.data.id).toBe(userId);
expect(response.body.data.name).toBe('Dave');
});
it('returns 404 for non-existent user', asyncBoss is an auditable agent-team workflow for coding agents. It turns one coding agent into a structured engineering team: PM, Architect, UI Designer, Tech Lead, Scrum Master, Frontend, Backend, QA, and DevOps.
Repo: echoVic/boss-skill
Other skills on boss.
- /architecture-design
系统架构设计方法论,包含架构模式选择、系统分层、目录结构设计
Open skill - /data-api-design
数据模型和API设计方法论,包含ERD设计、数据字典、RESTful API规范
Open skill - /tech-research
技术调研方法论,通过系统性调研和对比分析,为技术选型提供数据支持
Open skill - /api-development
后端API开发方法论,包括RESTful/GraphQL设计、请求验证、错误处理和安全实现
Open skill - /brainstorming
需求澄清 Skill。当用户只给了模糊描述时自动触发,通过业务提问把一句话翻译成完整需求,交给 Boss 流水线执行。 Triggers: '我想做一个', '帮我做', '有个想法', 'brainstorm', '帮我规划一下', '做个XX', 'I want to build' Does NOT trigger: - 需求已经完整(包含做什么 + 给谁用 + 核心场景) - 纯技术问题或 bug 修复 Output: .boss/<feature>/design-brief.md 需求设计简报
Open skill - /changelog-generation
自动生成 CHANGELOG,基于 git 提交历史和 pipeline 产物信息,遵循 Conventional Commits 和 Keep a Changelog 规范
Open skill

