Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/echoVic/boss-skill --skill backend-testing-guide명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | backend/testing-guide |
| description | 后端测试编写指南,包括单元测试、集成测试和E2E测试的编写方法和最佳实践 |
| type | methodology |
| agent | boss-backend |
职责边界:Backend Agent 是测试的编写者,QA Agent 是测试的验证者。
| 测试类型 | 占比 | 要求 |
|---|---|---|
| 单元测试 | ~70% | Service 层、业务逻辑必须有测试 |
| 集成测试 | ~20% | API 端点、数据库操作测试 |
| E2E 测试 | ~10% | 必须编写,完整 API 流程测试 |
// 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 = { : , : };
productRepository..({ : , : });
result = orderService.(order);
(result.).();
});
(, () => {
order = { : , : };
productRepository..({ : , : });
result = orderService.(order);
(result.).();
(result.).();
});
});
});
// 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).(userData);
(response...).();
});
(, () => {
userData = {
: ,
: ,
};
response = (app)
.()
.(userData)
.();
(response..).();
(response...).();
});
(, () => {
userData = {
: ,
: ,
};
(app).().(userData);
response = (app)
.()
.(userData)
.();
(response...).();
});
});
(, {
(, () => {
createResponse = (app)
.()
.({ : , : });
userId = createResponse...;
response = (app)
.()
.();
(response...).(userId);
(response...).();
});
(, () => {
response = (app)
.()
.();
(response...).();
});
});
(, {
(, () => {
createResponse = (app)
.()
.({ : , : });
userId = createResponse...;
response = (app)
.()
.({ : })
.();
(response...).();
(response...).();
});
});
(, {
(, () => {
createResponse = (app)
.()
.({ : , : });
userId = createResponse...;
(app)
.()
.();
(app)
.()
.();
});
});
});
// repositories/userRepository.test.ts
import { UserRepository } from './userRepository';
import { db } from '../db';
describe('UserRepository', () => {
let userRepository: UserRepository;
beforeEach(async () => {
userRepository = new UserRepository();
await db.user.deleteMany();
});
afterAll(async () => {
await db.$disconnect();
});
describe('create', () => {
it('creates user in database', async () => {
const userData = { name: 'Alice', email: 'alice@example.com' };
const user = await userRepository.create(userData);
expect(user.id).toBeDefined();
expect(user.name).toBe('Alice');
// 验证数据库中存在
const found = await db.user.({ : { : user. } });
(found).();
});
});
(, {
(, () => {
userRepository.({ : , : });
user = userRepository.();
(user).();
(user?.).();
});
(, () => {
user = userRepository.();
(user).();
});
});
});
// e2e/user-crud.test.ts
import request from 'supertest';
import { app } from '../app';
import { db } from '../db';
describe('User CRUD E2E', () => {
beforeAll(async () => {
await db.user.deleteMany();
});
afterAll(async () => {
await db.$disconnect();
});
it('completes full user lifecycle', async () => {
// 1. 创建用户
const createResponse = await request(app)
.post('/api/users')
.send({
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
})
.expect(201);
expect(createResponse.body.success).toBe(true);
const userId = createResponse.body.data.id;
// 2. 读取用户
const getResponse = (app)
.()
.();
(getResponse...).();
(getResponse...).();
updateResponse = (app)
.()
.({ : })
.();
(updateResponse...).();
verifyResponse = (app)
.()
.();
(verifyResponse...).();
(app)
.()
.();
(app)
.()
.();
});
});
// e2e/auth-flow.test.ts
describe('Authentication Flow E2E', () => {
it('completes registration and login flow', async () => {
// 1. 注册
const registerResponse = await request(app)
.post('/api/auth/register')
.send({
name: 'Alice',
email: 'alice@example.com',
password: 'password123',
})
.expect(201);
expect(registerResponse.body.data.token).toBeDefined();
const token = registerResponse.body.data.token;
// 2. 使用 Token 访问受保护资源
const profileResponse = await request(app)
.get('/api/auth/profile')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(profileResponse.body.data.email).toBe('alice@example.com');
(app)
.()
.(, )
.();
loginResponse = (app)
.()
.({
: ,
: ,
})
.();
(loginResponse...).();
});
(, () => {
(app)
.()
.({
: ,
: ,
})
.();
});
});
// e2e/order-flow.test.ts
describe('Order Flow E2E', () => {
let authToken: string;
let productId: string;
beforeAll(async () => {
// 注册并登录
const registerResponse = await request(app)
.post('/api/auth/register')
.send({ name: 'Buyer', email: 'buyer@example.com', password: 'pass123' });
authToken = registerResponse.body.data.token;
// 创建测试商品
const productResponse = await request(app)
.post('/api/products')
.set('Authorization', `Bearer ${authToken}`)
.send({ name: 'Test Product', price: 100, stock: 10 });
productId = productResponse.body.data.id;
});
it('completes order creation and payment flow', async () => {
orderResponse = (app)
.()
.(, )
.({
: [{ productId, : }],
})
.();
orderId = orderResponse...;
(orderResponse...).();
(orderResponse...).();
paymentResponse = (app)
.()
.(, )
.({ : })
.();
(paymentResponse...).();
productResponse = (app)
.()
.();
(productResponse...).();
ordersResponse = (app)
.()
.(, )
.();
(ordersResponse...).();
(ordersResponse...[].).(orderId);
});
});
使用测试数据库或事务回滚:
// 方案 1:使用测试数据库
beforeAll(async () => {
process.env.DATABASE_URL = 'postgresql://localhost:5432/test_db';
await db.$connect();
});
// 方案 2:使用事务回滚
beforeEach(async () => {
await db.$transaction(async (tx) => {
// 测试在事务中运行
});
});
// 模拟第三方 API
jest.mock('../services/paymentGateway', () => ({
processPayment: jest.fn().mockResolvedValue({ success: true, transactionId: 'tx123' }),
}));
// 模拟邮件服务
jest.mock('../services/emailService', () => ({
sendEmail: jest.fn().mockResolvedValue(true),
}));
describe('Boundary conditions', () => {
it('handles empty list', async () => {
const response = await request(app).get('/api/users').expect(200);
expect(response.body.data.items).toEqual([]);
});
it('handles maximum page size', async () => {
const response = await request(app)
.get('/api/users?pageSize=1000')
.expect(400);
expect(response.body.error.message).toContain('pageSize');
});
it('handles invalid UUID', async () => {
await request(app)
.get('/api/users/invalid-uuid')
.expect(400);
});
});
it('handles concurrent requests', async () => {
const requests = Array(10).fill(null).map(() =>
request(app).post('/api/users').send({ name: 'User', email: `user${Math.random()}@example.com` })
);
const responses = await Promise.all(requests);
responses.forEach(response => {
expect(response.status).toBe(201);
});
});
运行覆盖率报告:
npm test -- --coverage
实现完成后,在输出中包含:
测试添加:
| 类型 | 文件 | 描述 |
|---|---|---|
| 单元测试 | src/services/userService.test.ts | UserService 业务逻辑测试 |
| 集成测试 | src/controllers/userController.test.ts | User API 端点集成测试 |
| E2E 测试 | e2e/user-crud.test.ts | 用户 CRUD 完整流程 E2E 测试 |
测试结果: