fengling-auth-service/.planning/codebase/TESTING.md
movingsam 2a60caae80 docs(architecture): 添加系统架构分析文档
- 描述整体基于ASP.NET Core的分层架构与领域驱动设计
- 详细说明表现层、视图模型层、配置层和基础设施层职责
- 介绍用户认证、OAuth2授权码与令牌颁发的数据流过程
- 抽象说明用户与租户、声明和授权实体设计
- 说明应用启动入口和关键HTTP端点
- 列出错误处理策略和跨领域关注点(日志、追踪、安全)

docs(concerns): 新增代码库问题与关注点分析文档

- 汇总并详述安全漏洞如配置文件泄露、Cookie策略不当
- 记录技术债务包括缺乏单元测试、依赖注入不统一等
- 罗列性能问题和具体代码缺陷
- 给出优先级明确的修复建议和改进措施
- 涵盖架构设计问题和依赖兼容性风险
- 说明测试覆盖缺口及高风险未测试区域

docs(conventions): 新增编码约定与规范文档

- 明确文件、类、方法、变量等命名规则
- 规范代码风格包括命名空间、主构造函数使用
- 制定日志记录、审计日志和依赖注入的标准
- 规定控制器路由、异步模式和错误处理方式
- 说明DTO命名及特性使用规范
- 描述配置管理、注释规范及常用代码注释样例

docs(integrations): 添加外部系统集成文档

- 介绍数据库连接和PostgreSQL客户端库版本
- 描述身份认证与授权服务及默认用户信息
- 说明可观测性方案及OpenTelemetry组件
- 涵盖容器化部署相关Docker与Kubernetes配置
- 说明CI/CD流水线触发条件与构建流程
- 列出环境变量需求和主要API端点
- 强调生产环境密钥管理与安全存储机制
2026-03-01 11:28:44 +08:00

11 KiB
Raw Permalink Blame History

测试模式

分析日期: 2026-02-28

测试框架概述

此代码库目前未建立独立的测试项目,但具备测试基础设施支持。代码库使用以下测试相关配置:

  • appsettings.Testing.json - 测试环境配置文件
  • Microsoft.EntityFrameworkCore.InMemory - 内存数据库提供程序(用于测试场景)
  • 支持通过配置切换测试模式

测试配置

测试环境配置

配置文件位置: src/appsettings.Testing.json

配置内容:

{
  "Testing": true,
  "ConnectionStrings": {
    "DefaultConnection": "..."
  },
  "OpenIddict": {
    "Issuer": "...",
    "Audience": "..."
  }
}

测试模式切换

约定: 通过配置项 Testing 控制是否启用完整 OpenIddict 配置

示例:

var isTesting = builder.Configuration.GetValue<bool>("Testing", false);

if (!isTesting)
{
    builder.AddServer(options =>
    {
        // 生产环境 OpenIddict 配置
        options.SetIssuer(configuration["OpenIddict:Issuer"] ?? "http://localhost:5132");
        // ... 其他配置
    });
}

内存数据库

依赖包: Microsoft.EntityFrameworkCore.InMemory

使用场景: 测试环境中使用内存数据库,避免外部数据库依赖

示例:

// 在测试环境中,可以使用 InMemory 提供程序
options.UseInMemoryDatabase("TestDatabase");

测试现状分析

缺失的测试项目

问题: 当前代码库没有独立的测试项目xUnit、NUnit 或 MSTest

影响:

  • 缺少单元测试
  • 缺少集成测试
  • 缺少控制器测试
  • 无法进行回归测试

建议的测试策略

由于代码库采用 Clean Architecture 和 MediatR 模式,建议建立以下测试层次:

1. 单元测试xUnit/NUnit

目标: 验证业务逻辑、命令处理器、查询处理器

测试框架建议:

  • xUnit - 推荐,与 .NET 生态集成良好
  • NUnit - 传统选择,功能全面
  • FluentAssertions - 断言库,提供更自然的断言语法
  • Moq - 模拟框架

示例测试结构:

public class UsersControllerTests
{
    private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
    private readonly Mock<RoleManager<ApplicationRole>> _roleManagerMock;
    private readonly Mock<ILogger<UsersController>> _loggerMock;
    private readonly PlatformDbContext _context;
    private readonly UsersController _controller;

    public UsersControllerTests()
    {
        // 设置模拟对象
        var store = new Mock<IUserStore<ApplicationUser>>();
        _userManagerMock = new Mock<UserManager<ApplicationUser>>(
            store.Object, null, null, null, null, null, null, null, null);
        
        _roleManagerMock = new Mock<RoleManager<ApplicationRole>>();
        _loggerMock = new Mock<ILogger<UsersController>>();
        
        // 使用内存数据库
        var options = new DbContextOptionsBuilder<PlatformDbContext>()
            .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 - 断言库

示例测试结构:

public class UsersControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    private readonly HttpClient _client;

    public UsersControllerIntegrationTests(WebApplicationFactory<Program> 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 配置:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="coverlet.collector" Version="*" />
    <PackageReference Include="FluentAssertions" Version="*" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="*" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="*" />
    <PackageReference Include="Moq" Version="*" />
    <PackageReference Include="xunit" Version="*" />
    <PackageReference Include="xunit.runner.visualstudio" Version="*">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\src\Fengling.AuthService.csproj" />
  </ItemGroup>
</Project>

模拟模式

Moq 使用示例

控制器依赖模拟:

// 模拟 UserManager
var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
var userManagerMock = new Mock<UserManager<ApplicationUser>>(
    userStoreMock.Object,
    null, null, null, null, null, null, null, null);

// 模拟 RoleManager
var roleStoreMock = new Mock<IRoleStore<ApplicationRole>>();
var roleManagerMock = new Mock<RoleManager<ApplicationRole>>(
    roleStoreMock.Object,
    null, null, null, null, null, null, null);

// 模拟 ILogger
var loggerMock = new Mock<ILogger<UsersController>>();

// 设置模拟行为
userManagerMock
    .Setup(x => x.FindByNameAsync(It.IsAny<string>()))
    .ReturnsAsync(new ApplicationUser { UserName = "testuser" });

userManagerMock
    .Setup(x => x.GetRolesAsync(It.IsAny<ApplicationUser>()))
    .ReturnsAsync(new List<string> { "User" });

数据库模拟

使用 InMemory 数据库:

var options = new DbContextOptionsBuilder<PlatformDbContext>()
    .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 模式

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 使用示例

using FluentAssertions;

// 对象断言
result.Should().NotBeNull();
result.Should().BeOfType<ApplicationUser>();
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<InvalidOperationException>()
    .WithMessage("User not found");

// HTTP 响应断言
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType.MediaType.Should().Be("application/json");

测试覆盖率

覆盖率目标建议

测试类型 覆盖率目标 说明
单元测试 ≥ 80% 业务逻辑、命令、查询
集成测试 ≥ 60% 控制器端点、数据访问
整体 ≥ 70% 综合覆盖率

覆盖率工具

工具: coverlet.collectorcoverlet.msbuild

运行命令:

# 使用 dotnet test
dotnet test --collect:"XPlat Code Coverage"

# 查看覆盖率报告
dotnet test --collect:"XPlat Code Coverage" --results-directory ./coverage

CI/CD 测试集成

建议的测试脚本

# 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