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

441 lines
11 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
## 测试框架概述
此代码库目前**未建立独立的测试项目**,但具备测试基础设施支持。代码库使用以下测试相关配置:
- `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<bool>("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<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** - 断言库
**示例测试结构:**
```csharp
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 配置:**
```xml
<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 使用示例
**控制器依赖模拟:**
```csharp
// 模拟 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 数据库:**
```csharp
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 模式
```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<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.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*