backend-testing
此技能用于后端测试,涵盖新功能开发时的测试驱动开发、API端点测试、代码重构后的验证、错误修复及性能测试,确保后端稳定可靠。
npx skills add supercent-io/skills-template --skill backend-testingBefore / After 效果对比
1 组缺乏系统性的后端测试,新功能上线后常出现意想不到的bug,API接口不稳定,性能问题频发,导致用户体验差,修复成本高昂,严重影响了产品的质量和团队的开发信心。
实施全面的后端测试策略后,我们通过测试驱动开发、API端点测试和性能测试,在开发早期就发现并解决了大量潜在问题,显著提升了后端服务的稳定性、可靠性和响应速度。
backend-testing
Backend Testing
When to use this skill
Specific situations that should trigger this skill:
-
New feature development: Write tests first using TDD (Test-Driven Development)
-
Adding API endpoints: Test success and failure cases for REST APIs
-
Bug fixes: Add tests to prevent regressions
-
Before refactoring: Write tests that guarantee existing behavior
-
CI/CD setup: Build automated test pipelines
Input Format
Format and required/optional information to collect from the user:
Required information
-
Framework: Express, Django, FastAPI, Spring Boot, etc.
-
Test tool: Jest, Pytest, Mocha/Chai, JUnit, etc.
-
Test target: API endpoints, business logic, DB operations, etc.
Optional information
-
Database: PostgreSQL, MySQL, MongoDB (default: in-memory DB)
-
Mocking library: jest.mock, sinon, unittest.mock (default: framework built-in)
-
Coverage target: 80%, 90%, etc. (default: 80%)
-
E2E tool: Supertest, TestClient, RestAssured (optional)
Input example
Test the user authentication endpoints for an Express.js API:
- Framework: Express + TypeScript
- Test tool: Jest + Supertest
- Target: POST /auth/register, POST /auth/login
- DB: PostgreSQL (in-memory for tests)
- Coverage: 90% or above
Instructions
Step-by-step task order to follow precisely.
Step 1: Set up the test environment
Install and configure the test framework and tools.
Tasks:
-
Install test libraries
-
Configure test database (in-memory or separate DB)
-
Separate environment variables (.env.test)
-
Configure jest.config.js or pytest.ini
Example (Node.js + Jest + Supertest):
npm install --save-dev jest ts-jest @types/jest supertest @types/supertest
jest.config.js:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/__tests__/**'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts']
};
setup.ts (global test configuration):
import { db } from '../database';
// Reset DB before each test
beforeEach(async () => {
await db.migrate.latest();
await db.seed.run();
});
// Clean up after each test
afterEach(async () => {
await db.migrate.rollback();
});
// Close connection after all tests complete
afterAll(async () => {
await db.destroy();
});
Step 2: Write Unit Tests (business logic)
Write unit tests for individual functions and classes.
Tasks:
-
Test pure functions (no dependencies)
-
Isolate dependencies via mocking
-
Test edge cases (boundary values, exceptions)
-
AAA pattern (Arrange-Act-Assert)
Decision criteria:
-
No external dependencies (DB, API) -> pure Unit Test
-
External dependencies present -> use Mock/Stub
-
Complex logic -> test various input cases
Example (password validation function):
// src/utils/password.ts
export function validatePassword(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain lowercase letter');
}
if (!/\d/.test(password)) {
errors.push('Password must contain number');
}
if (!/[!@#$%^&*]/.test(password)) {
errors.push('Password must contain special character');
}
return { valid: errors.length === 0, errors };
}
// src/__tests__/utils/password.test.ts
import { validatePassword } from '../../utils/password';
describe('validatePassword', () => {
it('should accept valid password', () => {
const result = validatePassword('Password123!');
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should reject password shorter than 8 characters', () => {
const result = validatePassword('Pass1!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must be at least 8 characters');
});
it('should reject password without uppercase', () => {
const result = validatePassword('password123!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain uppercase letter');
});
it('should reject password without lowercase', () => {
const result = validatePassword('PASSWORD123!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain lowercase letter');
});
it('should reject password without number', () => {
const result = validatePassword('Password!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain number');
});
it('should reject password without special character', () => {
const result = validatePassword('Password123');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain special character');
});
it('should return multiple errors for invalid password', () => {
const result = validatePassword('pass');
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(1);
});
});
Step 3: Integration Test (API endpoints)
Write integration tests for API endpoints.
Tasks:
-
Test HTTP requests/responses
-
Success cases (200, 201)
-
Failure cases (400, 401, 404, 500)
-
Authentication/authorization tests
-
Input validation tests
Checklist:
-
Verify status code
-
Validate response body structure
-
Confirm database state changes
-
Validate error messages
Example (Express.js + Supertest):
// src/__tests__/api/auth.test.ts
import request from 'supertest';
import app from '../../app';
import { db } from '../../database';
describe('POST /auth/register', () => {
it('should register new user successfully', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'Password123!'
});
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('user');
expect(response.body).toHaveProperty('accessToken');
expect(response.body.user.email).toBe('test@example.com');
// Verify the record was actually saved to DB
const user = await db.user.findUnique({ where: { email: 'test@example.com' } });
expect(user).toBeTruthy();
expect(user.username).toBe('testuser');
});
it('should reject duplicate email', async () => {
// Create first user
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'user1',
password: 'Password123!'
});
// Second attempt with same email
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'user2',
password: 'Password123!'
});
expect(response.status).toBe(409);
expect(response.body.error).toContain('already exists');
});
it('should reject weak password', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'weak'
});
expect(response.status).toBe(400);
expect(response.body.error).toBeDefined();
});
it('should reject missing fields', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com'
// username, password omitted
});
expect(response.status).toBe(400);
});
});
describe('POST /auth/login', () => {
beforeEach(async () => {
// Create test user
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'Password123!'
});
});
it('should login with valid credentials', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'Password123!'
});
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('accessToken');
expect(response.body).toHaveProperty('refreshToken');
expect(response.body.user.email).toBe('test@example.com');
});
it('should reject invalid password', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'WrongPassword123!'
});
expect(response.status).toBe(401);
expect(response.body.error).toContain('Invalid credentials');
});
it('should reject non-existent user', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'nonexistent@example.com',
password: 'Password123!'
});
expect(response.status).toBe(401);
});
});
Step 4: Authentication/Authorization Tests
Test JWT tokens and role-based access control.
Tasks:
-
Confirm 401 when accessing without a token
-
Confirm successful access with a valid token
-
Test expired token handling
-
Role-based permission tests
Example:
describe('Protected Routes', () => {
let accessToken: string;
let adminToken: string;
beforeEach(async () => {
// Regular user token
const userResponse = await request(app)
.post('/api/auth/register')
.send({
email: 'user@example.com',
username: 'user',
password: 'Password123!'
});
accessToken = userResponse.body.accessToken;
// Admin token
const adminResponse = await request(app)
.post('/api/auth/register')
.send({
email: 'admin@example.com',
username: 'admin',
password: 'Password123!'
});
// Update role to 'admin' in DB
await db.user.update({
where: { email: 'admin@example.com' },
data: { role: 'admin' }
});
// Log in again to get a new token
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: 'admin@example.com',
password: 'Password123!'
});
adminToken = loginResponse.body.accessToken;
});
describe('GET /api/auth/me', () => {
it('should return current user with valid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(200);
expect(response.body.user.email).toBe('user@example.com');
});
it('should reject request without token', async () => {
const response = await request(app)
.get('/api/auth/me');
expect(response.status).toBe(401);
});
it('should reject request with invalid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', 'Bearer invalid-token');
expect(response.status).toBe(403);
});
});
describe('DELETE /api/users/:id (Admin only)', () => {
it('should allow admin to delete user', async () => {
const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });
const response = await request(app)
.delete(`/api/users/${targetUser.id}`)
.set('Authorization', `Bearer ${adminToken}`);
expect(response.status).toBe(200);
});
it('should forbid non-admin from deleting user', async () => {
const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });
const response = await request(app)
.delete(`/api/users/${targetUser.id}`)
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(403);
});
});
});
Step 5: Mocking and Test Isolation
Mock external dependencies to isolate tests.
Tasks:
-
Mock external APIs
-
Mock email sending
-
Mock file system
-
Mock time-related functions
Example (mocking an external API):
// src/services/emailService.ts
export async function sendVerificationEmail(email: string, token: string): Promise<void> {
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}` },
body: JSON.stringify({
to: email,
subject: 'Verify your email',
html: `<a href="https://example.com/verify?token=${token}">Verify</a>`
})
});
if (!response.ok) {
throw new Error('Failed to send email');
}
}
// src/__tests__/services/emailService.test.ts
import { sendVerificationEmail } from '../../services/emailService';
// Mock fetch
global.fetch = jest.fn();
describe('sendVerificationEmail', () => {
beforeEach(() => {
(fetch as jest.Mock).mockClear();
});
it('should send email successfully', async () => {
(fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200
});
await expect(sendVerificationEmail('test@example.com', 'token123'))
.resolves
.toBeUndefined();
expect(fetch).toHaveBeenCalledWith(
'https://api.sendgrid.com/v3/mail/send',
expect.objectContaining({
method: 'POST'
})
);
});
it('should throw error if email sending fails', async () => {
(fetch as jest.Mock).mockResolvedValueOnce({
ok: false,
status: 500
});
await expect(sendVerificationEmail('test@example.com', 'token123'))
.rejects
.toThrow('Failed to send email');
});
});
Output format
Defines the exact format that outputs must follow.
Basic structure
project/
├── src/
│ ├── __tests__/
│ │ ├── setup.ts # Global test configuration
│ │ ├── utils/
│ │ │ └── password.test.ts # Unit tests
│ │ ├── services/
│ │ │ └── emailService.test.ts
│ │ └── api/
│ │ ├── auth.test.ts # Integration tests
│ │ └── users.test.ts
│ └── ...
├── jest.config.js
└── package.json
Test run scripts (package.json)
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --maxWorkers=2"
}
}
Coverage report
$ npm run test:coverage
--------------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
--------------------------|---------|----------|---------|---------|
All files | 92.5 | 88.3 | 95.2 | 92.8 |
auth/
...
用户评价 (0)
发表评价
暂无评价
统计数据
用户评分
为此 Skill 评分