簡介
由於分散式依賴關係,測試微服務比單體服務更困難。本文建構了全面的測試策略:從孤立的單元測試到跨服務的端到端測試。
1. 測試微服務金字塔
┌─────┐
│ E2E │ Ít test, chậm, expensive
│Tests│ (5-10 critical flows)
┌┴─────┴┐
│Contract│ Verify API contracts
│ Tests │ between services
┌┴───────┴┐
│Integration│ Service + DB + deps
│ Tests │ (Testcontainers)
┌┴─────────┴┐
│ Unit Tests │ Fast, isolated
│ (70-80%) │ Business logic
└─────────────┘
2. 單元測試
測試業務邏輯與資料庫、網路、外部服務隔離:
// Order Service - business logic
describe('OrderService', () => {
it('should calculate total with discount', () => {
const items = [
{ productId: '1', price: 100, quantity: 2 },
{ productId: '2', price: 50, quantity: 1 },
];
const discount = { type: 'percentage', value: 10 };
const total = calculateOrderTotal(items, discount);
expect(total).toBe(225); // (200 + 50) * 0.9
});
it('should reject order with empty items', () => {
expect(() => createOrder([])).toThrow('Order must have items');
});
});
模擬外部服務
// Mock Product Service call
const mockProductService = {
getProduct: jest.fn().mockResolvedValue({
id: '1', name: 'Laptop', price: 999, inStock: true
}),
};
describe('OrderService.placeOrder', () => {
it('should validate product exists', async () => {
const order = await placeOrder(
{ productId: '1', qty: 1 },
{ productService: mockProductService }
);
expect(mockProductService.getProduct).toHaveBeenCalledWith('1');
expect(order.status).toBe('PENDING');
});
});
3.整合測試(測試容器)
使用真實資料庫和依賴項測試服務:
// Jest + Testcontainers
import { PostgreSqlContainer } from '@testcontainers/postgresql';
describe('OrderRepository (Integration)', () => {
let container;
let db;
beforeAll(async () => {
container = await new PostgreSqlContainer()
.withDatabase('test_orders')
.start();
db = await connectDB(container.getConnectionUri());
await runMigrations(db);
});
afterAll(async () => {
await container.stop();
});
it('should save and retrieve order', async () => {
const order = await orderRepo.create({
userId: 'user-1',
items: [{ productId: 'p1', qty: 2, price: 100 }],
});
const found = await orderRepo.findById(order.id);
expect(found.userId).toBe('user-1');
expect(found.items).toHaveLength(1);
expect(found.total).toBe(200);
});
});
4. 元件測試(服務等級)
隔離測試整個服務(真實的 HTTP、真實的資料庫、模擬的外部服務):
describe('Product Service (Component Test)', () => {
let app;
let dbContainer;
beforeAll(async () => {
dbContainer = await new PostgreSqlContainer().start();
app = await createApp({
dbUrl: dbContainer.getConnectionUri(),
inventoryService: mockInventoryService,
});
});
it('GET /api/products returns product list', async () => {
const res = await request(app)
.get('/api/products')
.expect(200);
expect(res.body.data).toBeInstanceOf(Array);
});
it('POST /api/products requires auth', async () => {
await request(app)
.post('/api/products')
.send({ name: 'Test' })
.expect(401);
});
});
5. E2E 測試(僅限關鍵流程)
// E2E: Complete order flow
describe('Order Flow E2E', () => {
it('user can browse, add to cart, and place order', async () => {
// 1. Browse products
const products = await api.get('/api/products');
expect(products.status).toBe(200);
// 2. Add to cart
const cartRes = await api.post('/api/cart/items', {
productId: products.body.data[0].id,
quantity: 1,
});
expect(cartRes.status).toBe(201);
// 3. Place order
const orderRes = await api.post('/api/orders', {
cartId: cartRes.body.cartId,
shippingAddress: { /* ... */ },
});
expect(orderRes.status).toBe(201);
expect(orderRes.body.status).toBe('PENDING');
});
});
6. 測試最佳實踐
| 實踐 | 描述 |
|---|---|
| 測試金字塔 | 70% 單元,20% 集成,10% E2E |
| 測試容器 | 用於整合測試的真實資料庫而不是模擬 |
| 並行測試 | 每個測試都有自己的資料庫容器 |
| 測試資料建構器 | 測試夾具的工廠功能 |
| CI 管道 | 單元→整合→E2E(快速失敗) |
| 功能標誌 | 安全生產測試 |
總結
- 單元測試:快速、隔離、測試業務邏輯 (70-80%)
- 整合測試:用於真實資料庫測試的測試容器
- 元件測試:整個服務,模擬外部部門
- E2E測試:僅限關鍵業務流程(5-10個場景)
- 合約測驗:驗證 API 合約(請參閱第 22 課)
下一篇文章: 第 21 課:測試微前端 — 組件、視覺與集成