測試策略總覽
本文檔說明花店管理系統的測試策略與方法。
測試金字塔
花店系統採用標準的測試金字塔策略:
/\
/ \ 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 Library | UI 組件 |
| 整合測試 | Vitest + MSW | API 整合 |
| 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 + Mockito | Service 邏輯 |
| Repository 測試 | @DataJpaTest | 資料庫查詢 |
| API 測試 | @WebMvcTest | REST 端點 |
| 整合測試 | @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 被測試的類別
- 使用真實的值物件