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

5.7 KiB
Raw Blame History

测试模式

分析日期: 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
  • 测试方法: {方法名}_{场景}_{预期结果}

测试结构

套件组织(预期模式)

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 - 用于仓储测试

模式

// 带参数的模拟设置
_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

测试夹具和工厂

测试数据

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
public class PlatformDbContextTests
{
    private readonly PlatformDbContext _context;
    
    public PlatformDbContextTests()
    {
        var options = new DbContextOptionsBuilder<PlatformDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;
        _context = new PlatformDbContext(options);
    }
}

端到端测试

  • 不适用: 这是一个库/项目,不是应用程序

常见模式

异步测试

[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);
}

错误测试

[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);
}

空参数测试

[Fact]
public async Task FindByIdAsync_WithNullId_ReturnsNull()
{
    // Act
    var result = await _tenantManager.FindByIdAsync(null);
    
    // Assert
    Assert.Null(result);
}

覆盖率

要求

  • 未强制 - 目前未定义覆盖率目标

建议目标

  • 最低: 领域逻辑 70%
  • 目标: 关键路径(租户 CRUD80%

查看覆盖率

dotnet test --collect:"XPlat Code Coverage"
# 或使用 coverlet
dotnet test /p:CollectCoverage=true /p:Threshold=80

运行测试

命令(预期)

dotnet test                              # 运行所有测试
dotnet test --filter "FullyQualifiedName~TenantManagerTests"  # 运行特定类
dotnet test --verbosity normal           # 详细输出
dotnet test --collect:"XPlat Code Coverage"  # 带覆盖率

测试分析: 2026-02-28