跳至主要内容

測試策略總覽

本文檔說明花店管理系統的測試策略與方法。

測試金字塔

花店系統採用標準的測試金字塔策略:

       /\
/ \ E2E Tests (少量,關鍵流程)
/────\
/ \ Integration Tests (中量,API 與資料庫)
/────────\
/ \ Unit Tests (大量,業務邏輯)
/────────────\
測試層級數量執行速度涵蓋範圍
單元測試70%單一函數/類別
整合測試20%多個組件協作
E2E 測試10%完整使用者流程

前端測試

測試工具

  • Vitest - 單元測試框架(類 Jest)
  • React Testing Library - 組件測試
  • MSW - API Mock
  • Playwright - E2E 測試

測試類型

類型工具範例
單元測試Vitest工具函數、Hooks
組件測試React Testing LibraryUI 組件
整合測試Vitest + MSWAPI 整合
E2E 測試Playwright完整流程

測試範例

// components/ProductCard/ProductCard.test.tsx
import { render, screen } from '@testing-library/react';
import { ProductCard } from './ProductCard';

describe('ProductCard', () => {
it('renders product information', () => {
const product = {
id: '1',
name: 'Rose Bouquet',
price: 1500,
};

render(<ProductCard product={product} />);

expect(screen.getByText('Rose Bouquet')).toBeInTheDocument();
expect(screen.getByText('$1500')).toBeInTheDocument();
});
});

詳細指南:前端測試

後端測試

測試工具

  • JUnit 5 - 單元測試框架
  • Mockito - Mock 工具
  • Spring Boot Test - 整合測試
  • TestContainers - 資料庫整合測試
  • Bruno - API 手動測試與規格維護

測試類型

類型工具範例
單元測試JUnit + MockitoService 邏輯
Repository 測試@DataJpaTest資料庫查詢
API 測試@WebMvcTestREST 端點
整合測試@SpringBootTest完整流程

測試範例

// domain/product/ProductServiceTest.java
@ExtendWith(MockitoExtension.class)
class ProductServiceTest {

@Mock
private ProductRepository repository;

@InjectMocks
private ProductService service;

@Test
void findById_shouldReturnProduct_whenExists() {
// Given
Product product = new Product("tenant1", "Rose", BigDecimal.valueOf(1500));
when(repository.findById(1L)).thenReturn(Optional.of(product));

// When
Product result = service.findById(1L);

// Then
assertThat(result.getName()).isEqualTo("Rose");
verify(repository).findById(1L);
}

@Test
void findById_shouldThrowException_whenNotExists() {
// Given
when(repository.findById(1L)).thenReturn(Optional.empty());

// When & Then
assertThrows(ProductNotFoundException.class, () -> service.findById(1L));
}
}

詳細指南:後端測試

E2E 測試

測試工具

  • Playwright - E2E 測試框架

測試範例

// e2e/product-management.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Product Management', () => {
test('should create new product', async ({ page }) => {
await page.goto('/products');

// 點擊新增商品
await page.click('text=新增商品');

// 填寫表單
await page.fill('[name="name"]', 'Rose Bouquet');
await page.fill('[name="price"]', '1500');

// 送出
await page.click('button[type="submit"]');

// 驗證成功
await expect(page.locator('text=Rose Bouquet')).toBeVisible();
});
});

詳細指南:E2E 測試

測試執行

前端測試

# 執行所有測試
npm run test

# 監視模式
npm run test:watch

# 涵蓋率報告
npm run test:coverage

# E2E 測試
npm run test:e2e

後端測試

# 執行所有測試
./gradlew test

# 執行單一測試類別
./gradlew test --tests ProductServiceTest

# 涵蓋率報告
./gradlew jacocoTestReport

測試涵蓋率目標

層級目標當前
後端整體80%75%
Service 層90%85%
Repository 層70%68%
前端整體70%65%
組件80%72%
工具函數90%88%

CI/CD 整合

GitLab CI

# .gitlab-ci.yml
stages:
- test
- build

test:backend:
stage: test
script:
- ./gradlew test
- ./gradlew jacocoTestReport
artifacts:
reports:
junit: build/test-results/test/TEST-*.xml
paths:
- build/reports/jacoco/

test:frontend:
stage: test
script:
- npm ci
- npm run test:coverage
artifacts:
reports:
junit: coverage/junit.xml
paths:
- coverage/

測試最佳實踐

1. AAA 模式

@Test
void testName() {
// Arrange - 準備測試資料
Product product = new Product(...);

// Act - 執行待測試的方法
Product result = service.create(product);

// Assert - 驗證結果
assertThat(result.getId()).isNotNull();
}

2. 測試命名

使用描述性名稱:

推薦

@Test
void findById_shouldReturnProduct_whenExists() { }

@Test
void findById_shouldThrowException_whenNotExists() { }

避免

@Test
void test1() { }

@Test
void testFindById() { }

3. 獨立性

每個測試應該獨立運行:

推薦

@Test
void test1() {
Product product = createTestProduct();
// ...
}

@Test
void test2() {
Product product = createTestProduct();
// ...
}

避免

private Product sharedProduct;  // 共用狀態

@Test
void test1() {
sharedProduct = new Product(...);
}

@Test
void test2() {
// 依賴 test1 的執行結果
}

4. Mock 使用原則

  • 只 mock 外部依賴
  • 不 mock 被測試的類別
  • 使用真實的值物件

下一步