dramaling-vocab-learning/backend/DramaLing.Api/Tests/README.md

269 lines
6.4 KiB
Markdown

# 測試架構說明
## 概述
本測試架構採用三層測試策略:單元測試、整合測試、端到端測試。支援 xUnit 測試框架,並整合 Moq 進行 Mock 測試。
## 測試目錄結構
```
Tests/
├── README.md # 本文檔 - 測試架構說明
├── Unit/ # 單元測試
│ ├── Services/ # 服務層單元測試
│ ├── Controllers/ # 控制器單元測試
│ └── Repositories/ # Repository 單元測試
├── Integration/ # 整合測試
└── E2E/ # 端到端測試
```
## 測試框架與工具
### 核心測試框架
- **xUnit**: 主要測試框架
- **Moq**: Mock 物件框架
- **FluentAssertions**: 流暢斷言庫
- **Microsoft.AspNetCore.Mvc.Testing**: ASP.NET Core 測試支援
### 測試資料庫
- **SQLite In-Memory**: 用於快速單元測試
- **TestContainers**: 用於整合測試的容器化資料庫
## 單元測試規範
### 命名規範
```
{TestedMethod}_{Scenario}_{ExpectedResult}
例如:
- GetUserAsync_WithValidId_ReturnsUser()
- AnalyzeSentenceAsync_WithEmptyText_ThrowsArgumentException()
```
### 測試結構 (Arrange-Act-Assert)
```csharp
[Fact]
public async Task GetUserAsync_WithValidId_ReturnsUser()
{
// Arrange
var userId = 1;
var expectedUser = new User { Id = userId, Name = "Test User" };
var mockRepository = new Mock<IUserRepository>();
mockRepository.Setup(r => r.GetByIdAsync(userId))
.ReturnsAsync(expectedUser);
var service = new UserService(mockRepository.Object);
// Act
var result = await service.GetUserAsync(userId);
// Assert
result.Should().NotBeNull();
result.Id.Should().Be(userId);
result.Name.Should().Be("Test User");
}
```
## 服務層測試指南
### 測試重點服務
1. **GeminiService** - AI 服務核心功能
2. **AuthService** - 認證服務
3. **AnalysisService** - 分析服務
4. **RefactoredHybridCacheService** - 快取服務
### Mock 策略
- **外部 API 呼叫**: 使用 Mock HttpClient
- **資料庫操作**: Mock Repository 介面
- **檔案操作**: Mock 檔案系統相關服務
## 整合測試策略
### WebApplicationFactory
使用 ASP.NET Core 的 `WebApplicationFactory` 進行整合測試:
```csharp
public class IntegrationTestBase : IClassFixture<WebApplicationFactory<Program>>
{
protected readonly WebApplicationFactory<Program> Factory;
protected readonly HttpClient Client;
public IntegrationTestBase(WebApplicationFactory<Program> factory)
{
Factory = factory.WithWebHostBuilder(builder =>
{
builder.UseEnvironment("Testing");
builder.ConfigureServices(services =>
{
// 替換為測試資料庫
services.RemoveAll<DbContextOptions<DramaLingDbContext>>();
services.AddDbContext<DramaLingDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
});
});
Client = Factory.CreateClient();
}
}
```
### 測試資料管理
```csharp
public class TestDataSeeder
{
public static async Task SeedAsync(DramaLingDbContext context)
{
// 清理現有資料
context.Users.RemoveRange(context.Users);
// 新增測試資料
context.Users.Add(new User { Id = 1, Name = "Test User" });
await context.SaveChangesAsync();
}
}
```
## 測試執行命令
### 執行所有測試
```bash
dotnet test
```
### 執行特定類別的測試
```bash
dotnet test --filter "ClassName=GeminiServiceTests"
```
### 執行特定類型的測試
```bash
# 只執行單元測試
dotnet test --filter "Category=Unit"
# 只執行整合測試
dotnet test --filter "Category=Integration"
```
### 產生測試覆蓋率報告
```bash
dotnet test --collect:"XPlat Code Coverage"
```
## 測試資料工廠模式
### 實體建立工廠
```csharp
public static class TestDataFactory
{
public static User CreateUser(int id = 1, string name = "Test User")
{
return new User
{
Id = id,
Name = name,
Email = $"test{id}@example.com",
CreatedAt = DateTime.UtcNow
};
}
public static Flashcard CreateFlashcard(int id = 1, int userId = 1)
{
return new Flashcard
{
Id = id,
UserId = userId,
Front = "Test Front",
Back = "Test Back",
CreatedAt = DateTime.UtcNow
};
}
}
```
## CI/CD 整合
### GitHub Actions 設定範例
```yaml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal --collect:"XPlat Code Coverage"
- name: Upload coverage reports
uses: codecov/codecov-action@v1
```
## 效能測試指南
### 基準測試
使用 BenchmarkDotNet 進行效能測試:
```csharp
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80)]
public class CachingBenchmarks
{
private ICacheService _cacheService;
[GlobalSetup]
public void Setup()
{
// 初始化快取服務
}
[Benchmark]
public async Task<string> GetFromCache()
{
return await _cacheService.GetAsync<string>("test-key");
}
}
```
## 測試最佳實踐
### DRY 原則
- 建立共用的測試基類
- 使用測試資料工廠
- 抽取共同的 Setup 邏輯
### 測試隔離
- 每個測試應該獨立執行
- 避免測試之間的依賴關係
- 使用 `IDisposable` 清理資源
### 可讀性
- 使用描述性的測試名稱
- 明確的 Arrange-Act-Assert 結構
- 適量的註解說明複雜邏輯
## 未來擴展計劃
1. **測試覆蓋率目標**: 達到 80% 以上的程式碼覆蓋率
2. **自動化測試**: 整合 CI/CD 管道
3. **效能回歸測試**: 建立效能基準測試
4. **安全性測試**: 加入安全相關的測試案例
---
**版本**: 1.0
**建立日期**: 2025-09-30
**維護者**: DramaLing 開發團隊