fengling-platform/.planning/codebase/TESTING.md
movingsam 1b8c937aa4
Some checks failed
Build and Push Docker / build (push) Failing after 23s
Publish NuGet Packages / build (push) Failing after 8s
feat: 添加 Gateway 路由实体到 Platform
- 新增 GatewayAggregate 领域实体 (GwTenant, GwTenantRoute, GwServiceInstance)
- 新增 IRouteStore, RouteStore, IInstanceStore, InstanceStore
- 新增 IRouteManager, RouteManager
- 合并 GatewayDbContext 到 PlatformDbContext
- 统一 Extensions.AddPlatformCore 注册所有服务
2026-02-28 23:53:00 +08:00

248 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 测试模式
**分析日期:** 2026-02-28
## 测试框架
**状态:** 此仓库中目前不存在测试项目。
### 预期框架(未实现)
基于项目结构和依赖,测试项目应使用:
- **测试运行器:** xUnit.NET 标准)
- **模拟:** Moq 或 NSubstitute
- **内存数据库:** `Microsoft.EntityFrameworkCore.InMemory` 用于 DbContext 测试
- **断言:** FluentAssertions可选用于可读断言
### 建议的项目结构
```
Fengling.Platform.Tests/
├── Fengling.Platform.Tests.csproj
├── Unit/
│ ├── TenantManagerTests.cs
│ ├── TenantStoreTests.cs
│ └── PlatformDbContextTests.cs
├── Integration/
│ └── TenantRepositoryTests.cs
└── Usings.cs
```
## 测试文件组织
### 位置
- **模式:** 单独测试项目(`Fengling.Platform.Tests/`
- **结构:** 镜像源项目结构
### 命名
- **测试类:** `{类名}Tests``{类名}Tests`
- **测试方法:** `{方法名}_{场景}_{预期结果}`
## 测试结构
### 套件组织(预期模式)
```csharp
public class TenantManagerTests
{
private readonly ITenantManager _tenantManager;
private readonly Mock<ITenantStore> _storeMock;
public TenantManagerTests()
{
_storeMock = new Mock<ITenantStore>();
_tenantManager = new TenantManager(_storeMock.Object);
}
[Fact]
public async Task FindByIdAsync_WithValidId_ReturnsTenant()
{
// Arrange
var tenantId = 1L;
var expectedTenant = new Tenant { Id = tenantId, Name = "Test" };
_storeMock.Setup(s => s.FindByIdAsync(tenantId, It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedTenant);
// Act
var result = await _tenantManager.FindByIdAsync(tenantId);
// Assert
Assert.NotNull(result);
Assert.Equal(tenantId, result.Id);
}
}
```
## 模拟
### 框架: Moq
**要模拟:**
- `ITenantStore` - `TenantManager` 的依赖
- `PlatformDbContext` - 用于仓储测试
### 模式
```csharp
// 带参数的模拟设置
_storeMock.Setup(s => s.FindByIdAsync(tenantId, It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedTenant);
// 空返回模拟
_storeMock.Setup(s => s.FindByIdAsync(It.IsAny<long?>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Tenant?)null);
// 验证调用
_storeMock.Verify(s => s.CreateAsync(It.IsAny<Tenant>(), It.IsAny<CancellationToken>()), Times.Once);
```
**不要模拟:**
- `Tenant` 实体(使用真实实例)
- 值类型和简单 DTO
## 测试夹具和工厂
### 测试数据
```csharp
public static class TenantFixture
{
public static Tenant CreateValidTenant(long id = 1)
=> new Tenant
{
Id = id,
TenantCode = "TEST",
Name = "Test Tenant",
ContactName = "John Doe",
ContactEmail = "john@test.com",
Status = TenantStatus.Active,
CreatedAt = DateTime.UtcNow
};
public static IEnumerable<Tenant> CreateTenantCollection(int count)
=> Enumerable.Range(1, count)
.Select(i => CreateValidTenant(i));
}
```
### 位置
- `Tests/Fixtures/``Tests/Factories/`
## 测试类型
### 单元测试
- **范围:** 隔离的单个类
- 目标: `TenantManager` - CRUD 操作
- 目标: `TenantStore` - 数据访问方法
- 目标: 实体验证逻辑
- **方法:** 模拟依赖,隔离测试
### 集成测试
- **范围:** 使用 `PlatformDbContext` 的数据库操作
- **方法:**
- 使用 `InMemoryDatabase` 隔离
- 使用 `DbContextOptionsBuilder` + `UseInMemoryDatabase`
```csharp
public class PlatformDbContextTests
{
private readonly PlatformDbContext _context;
public PlatformDbContextTests()
{
var options = new DbContextOptionsBuilder<PlatformDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_context = new PlatformDbContext(options);
}
}
```
### 端到端测试
- **不适用:** 这是一个库/项目,不是应用程序
## 常见模式
### 异步测试
```csharp
[Fact]
public async Task GetAllAsync_ReturnsAllTenants()
{
// Arrange
var tenants = new List<Tenant> { CreateValidTenant(), CreateValidTenant(2) };
_storeMock.Setup(s => s.GetAllAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(tenants);
// Act
var result = await _tenantManager.GetAllAsync();
// Assert
Assert.Equal(2, result.Count);
}
```
### 错误测试
```csharp
[Fact]
public async Task CreateAsync_WithDuplicateTenantCode_ReturnsFailure()
{
// Arrange
var tenant = CreateValidTenant();
_storeMock.Setup(s => s.FindByTenantCodeAsync(tenant.TenantCode, It.IsAny<CancellationToken>()))
.ReturnsAsync(tenant); // 模拟已存在的租户
// Act
var result = await _tenantManager.CreateAsync(tenant);
// Assert
Assert.False(result.Succeeded);
}
```
### 空参数测试
```csharp
[Fact]
public async Task FindByIdAsync_WithNullId_ReturnsNull()
{
// Act
var result = await _tenantManager.FindByIdAsync(null);
// Assert
Assert.Null(result);
}
```
## 覆盖率
### 要求
- **未强制** - 目前未定义覆盖率目标
### 建议目标
- **最低:** 领域逻辑 70%
- **目标:** 关键路径(租户 CRUD80%
### 查看覆盖率
```bash
dotnet test --collect:"XPlat Code Coverage"
# 或使用 coverlet
dotnet test /p:CollectCoverage=true /p:Threshold=80
```
## 运行测试
### 命令(预期)
```bash
dotnet test # 运行所有测试
dotnet test --filter "FullyQualifiedName~TenantManagerTests" # 运行特定类
dotnet test --verbosity normal # 详细输出
dotnet test --collect:"XPlat Code Coverage" # 带覆盖率
```
---
*测试分析: 2026-02-28*