# 测试模式 **分析日期:** 2026-02-28 ## 测试框架概述 此代码库目前**未建立独立的测试项目**,但具备测试基础设施支持。代码库使用以下测试相关配置: - `appsettings.Testing.json` - 测试环境配置文件 - `Microsoft.EntityFrameworkCore.InMemory` - 内存数据库提供程序(用于测试场景) - 支持通过配置切换测试模式 ## 测试配置 ### 测试环境配置 **配置文件位置:** `src/appsettings.Testing.json` **配置内容:** ```json { "Testing": true, "ConnectionStrings": { "DefaultConnection": "..." }, "OpenIddict": { "Issuer": "...", "Audience": "..." } } ``` ### 测试模式切换 **约定:** 通过配置项 `Testing` 控制是否启用完整 OpenIddict 配置 **示例:** ```csharp var isTesting = builder.Configuration.GetValue("Testing", false); if (!isTesting) { builder.AddServer(options => { // 生产环境 OpenIddict 配置 options.SetIssuer(configuration["OpenIddict:Issuer"] ?? "http://localhost:5132"); // ... 其他配置 }); } ``` ### 内存数据库 **依赖包:** `Microsoft.EntityFrameworkCore.InMemory` **使用场景:** 测试环境中使用内存数据库,避免外部数据库依赖 **示例:** ```csharp // 在测试环境中,可以使用 InMemory 提供程序 options.UseInMemoryDatabase("TestDatabase"); ``` ## 测试现状分析 ### 缺失的测试项目 **问题:** 当前代码库没有独立的测试项目(xUnit、NUnit 或 MSTest) **影响:** - 缺少单元测试 - 缺少集成测试 - 缺少控制器测试 - 无法进行回归测试 ### 建议的测试策略 由于代码库采用 Clean Architecture 和 MediatR 模式,建议建立以下测试层次: #### 1. 单元测试(xUnit/NUnit) **目标:** 验证业务逻辑、命令处理器、查询处理器 **测试框架建议:** - **xUnit** - 推荐,与 .NET 生态集成良好 - **NUnit** - 传统选择,功能全面 - **FluentAssertions** - 断言库,提供更自然的断言语法 - **Moq** - 模拟框架 **示例测试结构:** ```csharp public class UsersControllerTests { private readonly Mock> _userManagerMock; private readonly Mock> _roleManagerMock; private readonly Mock> _loggerMock; private readonly PlatformDbContext _context; private readonly UsersController _controller; public UsersControllerTests() { // 设置模拟对象 var store = new Mock>(); _userManagerMock = new Mock>( store.Object, null, null, null, null, null, null, null, null); _roleManagerMock = new Mock>(); _loggerMock = new Mock>(); // 使用内存数据库 var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: "TestDb") .Options; _context = new PlatformDbContext(options); _controller = new UsersController( _userManagerMock.Object, _roleManagerMock.Object, _loggerMock.Object, _context); } } ``` #### 2. 集成测试 **目标:** 测试控制器端点、数据库操作、OpenIddict 流程 **测试框架建议:** - **Microsoft.AspNetCore.Mvc.Testing** - ASP.NET Core 测试支持 - **TestHost** - 内存 Web 主机 - **FluentAssertions** - 断言库 **示例测试结构:** ```csharp public class UsersControllerIntegrationTests : IClassFixture> { private readonly WebApplicationFactory _factory; private readonly HttpClient _client; public UsersControllerIntegrationTests(WebApplicationFactory factory) { _factory = factory; _client = _factory.CreateClient(); } [Fact] public async Task GetUsers_ReturnsPagedResults() { // Arrange var request = "/api/users?page=1&pageSize=10"; // Act var response = await _client.GetAsync(request); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var content = await response.Content.ReadAsStringAsync(); // 验证响应内容 } } ``` #### 3. 端到端测试 **目标:** 验证完整用户流程(可选) **工具建议:** - **Playwright** - 现代 Web 测试框架 - **Selenium** - 传统选择 ## 测试文件组织 ### 建议的目录结构 ``` fengling-auth-service/ ├── src/ │ └── Fengling.AuthService.csproj └── tests/ ├── Fengling.AuthService.UnitTests/ │ ├── Fengling.AuthService.UnitTests.csproj │ ├── Controllers/ │ ├── Commands/ │ ├── Queries/ │ └── Services/ ├── Fengling.AuthService.IntegrationTests/ │ ├── Fengling.AuthService.IntegrationTests.csproj │ ├── Controllers/ │ └── Fixtures/ └── Fengling.AuthService.E2ETests/ ├── Fengling.AuthService.E2ETests.csproj └── Scenarios/ ``` ### 测试项目依赖 **建议的测试项目 csproj 配置:** ```xml net10.0 enable enable false runtime; build; native; contentfiles; analyzers; buildtransitive all ``` ## 模拟模式 ### Moq 使用示例 **控制器依赖模拟:** ```csharp // 模拟 UserManager var userStoreMock = new Mock>(); var userManagerMock = new Mock>( userStoreMock.Object, null, null, null, null, null, null, null, null); // 模拟 RoleManager var roleStoreMock = new Mock>(); var roleManagerMock = new Mock>( roleStoreMock.Object, null, null, null, null, null, null, null); // 模拟 ILogger var loggerMock = new Mock>(); // 设置模拟行为 userManagerMock .Setup(x => x.FindByNameAsync(It.IsAny())) .ReturnsAsync(new ApplicationUser { UserName = "testuser" }); userManagerMock .Setup(x => x.GetRolesAsync(It.IsAny())) .ReturnsAsync(new List { "User" }); ``` ### 数据库模拟 **使用 InMemory 数据库:** ```csharp var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; using var context = new PlatformDbContext(options); // 添加测试数据 context.Users.Add(new ApplicationUser { UserName = "testuser", Email = "test@example.com" }); await context.SaveChangesAsync(); ``` ## 测试数据工厂 ### 建议的 Fixture 模式 ```csharp public static class UserFixture { public static ApplicationUser CreateUser(string userName = "testuser") { return new ApplicationUser { UserName = userName, Email = $"{userName}@example.com", TenantInfo = new TenantInfo(new Tenant { TenantId = 1, TenantCode = "TEST" }), EmailConfirmed = true, CreatedTime = DateTimeOffset.UtcNow }; } public static ApplicationUser CreateAdminUser() { var user = CreateUser("admin"); return user; } } public static class RoleFixture { public static ApplicationRole CreateRole(string name = "User") { return new ApplicationRole { Name = name, DisplayName = name, Description = $"{name} Role", IsSystem = false, CreatedTime = DateTime.UtcNow }; } public static ApplicationRole CreateSystemRole() { var role = CreateRole("Admin"); role.IsSystem = true; return role; } } ``` ## 断言模式 ### FluentAssertions 使用示例 ```csharp using FluentAssertions; // 对象断言 result.Should().NotBeNull(); result.Should().BeOfType(); result.Id.Should().Be(1); result.UserName.Should().Be("testuser"); // 集合断言 users.Should().HaveCount(10); users.Should().Contain(u => u.UserName == "testuser"); users.Should().BeInAscendingOrder(u => u.CreatedTime); // 异常断言 action.Should().Throw() .WithMessage("User not found"); // HTTP 响应断言 response.StatusCode.Should().Be(HttpStatusCode.OK); response.Content.Headers.ContentType.MediaType.Should().Be("application/json"); ``` ## 测试覆盖率 ### 覆盖率目标建议 | 测试类型 | 覆盖率目标 | 说明 | |---------|-----------|------| | 单元测试 | ≥ 80% | 业务逻辑、命令、查询 | | 集成测试 | ≥ 60% | 控制器端点、数据访问 | | 整体 | ≥ 70% | 综合覆盖率 | ### 覆盖率工具 **工具:** `coverlet.collector` 或 `coverlet.msbuild` **运行命令:** ```bash # 使用 dotnet test dotnet test --collect:"XPlat Code Coverage" # 查看覆盖率报告 dotnet test --collect:"XPlat Code Coverage" --results-directory ./coverage ``` ## CI/CD 测试集成 ### 建议的测试脚本 ```yaml # azure-pipelines.yml 或 .github/workflows/test.yml trigger: - main pool: vmImage: 'ubuntu-latest' steps: - task: UseDotNet@2 inputs: packageType: 'sdk' version: '10.0.x' - script: | dotnet restore dotnet build --configuration Release displayName: 'Build' - script: | dotnet test --configuration Release --collect:"XPlat Code Coverage" --results-directory ./coverage displayName: 'Test' - script: | dotnet tool install --global dotnet-reportgenerator-globaltool reportgenerator -reports:./coverage/coverage.cobertura.xml -targetdir:./coverage-report -reporttypes:Html displayName: 'Generate Coverage Report' ``` ## 当前测试缺口 ### 未测试的关键领域 1. **控制器端点** - 缺少 API 端点测试 2. **业务逻辑** - 命令处理器和查询处理器未测试 3. **身份验证流程** - OAuth2/OIDC 流程未测试 4. **多租户隔离** - 租户数据隔离逻辑未验证 5. **角色权限** - RBAC 逻辑未测试 ### 优先测试建议 基于代码库特点,建议按以下优先级添加测试: 1. **高优先级** - `TokenController` - 令牌颁发逻辑 - `UsersController` - 用户 CRUD 操作 - `RolesController` - 角色管理操作 2. **中优先级** - `AuthorizationController` - 授权流程 - 审计日志功能 - 多租户数据隔离 3. **低优先级** - 健康检查端点 - 静态文件服务 --- *测试模式分析:2026-02-28*