Chuyển đến nội dung chính

第 20 課:測試微服務 — 單元、整合與 E2E

測試微服務金字塔。使用模擬/存根進行單元測試。與測試容器的整合測試。 E2E測試策略。服務虛擬化。生產中測試(金絲雀、功能標誌)。

🏗️ 建築 — 第 20 課 第 20 課:測試微服務 — 單元, 整合&端到端

微服務與微前端系統設計-從基礎到生產

第 7 部分:測試全端微服務和微前端

亞洲開發網

簡介

由於分散式依賴關係,測試微服務比單體服務更困難。本文建構了全面的測試策略:從孤立的單元測試到跨服務的端到端測試。


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 課:測試微前端 — 組件、視覺與集成