# 测试模式

**分析日期:** 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%
- **目标:** 关键路径（租户 CRUD）80%

### 查看覆盖率
```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*
