- 描述整体基于ASP.NET Core的分层架构与领域驱动设计 - 详细说明表现层、视图模型层、配置层和基础设施层职责 - 介绍用户认证、OAuth2授权码与令牌颁发的数据流过程 - 抽象说明用户与租户、声明和授权实体设计 - 说明应用启动入口和关键HTTP端点 - 列出错误处理策略和跨领域关注点(日志、追踪、安全) docs(concerns): 新增代码库问题与关注点分析文档 - 汇总并详述安全漏洞如配置文件泄露、Cookie策略不当 - 记录技术债务包括缺乏单元测试、依赖注入不统一等 - 罗列性能问题和具体代码缺陷 - 给出优先级明确的修复建议和改进措施 - 涵盖架构设计问题和依赖兼容性风险 - 说明测试覆盖缺口及高风险未测试区域 docs(conventions): 新增编码约定与规范文档 - 明确文件、类、方法、变量等命名规则 - 规范代码风格包括命名空间、主构造函数使用 - 制定日志记录、审计日志和依赖注入的标准 - 规定控制器路由、异步模式和错误处理方式 - 说明DTO命名及特性使用规范 - 描述配置管理、注释规范及常用代码注释样例 docs(integrations): 添加外部系统集成文档 - 介绍数据库连接和PostgreSQL客户端库版本 - 描述身份认证与授权服务及默认用户信息 - 说明可观测性方案及OpenTelemetry组件 - 涵盖容器化部署相关Docker与Kubernetes配置 - 说明CI/CD流水线触发条件与构建流程 - 列出环境变量需求和主要API端点 - 强调生产环境密钥管理与安全存储机制
441 lines
11 KiB
Markdown
441 lines
11 KiB
Markdown
# 测试模式
|
||
|
||
**分析日期:** 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*
|