🧪 YARP 网关测试覆盖计划
分析日期:2026-02-28
当前状态:无任何测试代码
测试项目结构
tests/
└── YarpGateway.Tests/
├── YarpGateway.Tests.csproj
├── Unit/
│ ├── Middleware/
│ │ ├── JwtTransformMiddlewareTests.cs
│ │ └── TenantRoutingMiddlewareTests.cs
│ ├── Services/
│ │ ├── RouteCacheTests.cs
│ │ ├── RedisConnectionManagerTests.cs
│ │ └── PgSqlConfigChangeListenerTests.cs
│ ├── LoadBalancing/
│ │ └── DistributedWeightedRoundRobinPolicyTests.cs
│ └── Config/
│ ├── DatabaseRouteConfigProviderTests.cs
│ └── DatabaseClusterConfigProviderTests.cs
├── Integration/
│ ├── Controllers/
│ │ ├── GatewayConfigControllerTests.cs
│ │ └── PendingServicesControllerTests.cs
│ └── Middleware/
│ └── MiddlewarePipelineTests.cs
└── TestHelpers/
├── MockDbContext.cs
├── MockRedis.cs
└── TestFixtures.cs
P0 - 必须覆盖(核心安全)
JwtTransformMiddlewareTests
| 测试用例 |
描述 |
输入 |
预期输出 |
Mock 需求 |
ShouldValidateJwtSignature |
应验证 JWT 签名 |
有效签名的 JWT |
解析成功,Claims 正确 |
IOptions<JwtConfig> |
ShouldRejectInvalidToken |
应拒绝无效 Token |
伪造/过期 JWT |
返回 401 或跳过处理 |
- |
ShouldExtractTenantClaim |
应正确提取租户 ID |
含 tenant claim 的 JWT |
X-Tenant-Id header 设置正确 |
- |
ShouldHandleMissingToken |
应处理无 Token 请求 |
无 Authorization header |
继续处理(不设置 headers) |
- |
ShouldHandleMalformedToken |
应处理格式错误 Token |
无效 JWT 格式 |
记录警告,继续处理 |
- |
TenantRoutingMiddlewareTests
| 测试用例 |
描述 |
输入 |
预期输出 |
Mock 需求 |
ShouldValidateTenantIdAgainstJwt |
应验证 header 与 JWT 一致 |
X-Tenant-Id ≠ JWT tenant |
返回 403 Forbidden |
IRouteCache |
ShouldExtractServiceNameFromPath |
应正确解析服务名 |
/api/user-service/users |
serviceName = "user-service" |
- |
ShouldFindRouteInCache |
应从缓存找到路由 |
有效租户+服务名 |
设置正确的 clusterId |
IRouteCache |
ShouldHandleRouteNotFound |
应处理路由未找到 |
不存在的服务名 |
记录警告,继续处理 |
- |
ShouldPrioritizeTenantRouteOverGlobal |
租户路由优先于全局 |
同时存在两种路由 |
使用租户路由 |
- |
P1 - 重要覆盖(核心业务)
RouteCacheTests
| 测试用例 |
描述 |
输入 |
预期输出 |
Mock 需求 |
ShouldLoadGlobalRoutes |
应加载全局路由 |
全局路由数据 |
_globalRoutes 填充 |
IDbContextFactory<GatewayDbContext> |
ShouldLoadTenantRoutes |
应加载租户路由 |
租户路由数据 |
_tenantRoutes 填充 |
- |
ShouldReturnCorrectRoute |
应返回正确路由 |
查询请求 |
正确的 RouteInfo |
- |
ShouldReturnNullForMissingRoute |
不存在路由返回 null |
不存在的服务名 |
null |
- |
ShouldHandleConcurrentReads |
并发读取应安全 |
多线程读取 |
无异常,数据一致 |
- |
ShouldReloadCorrectly |
应正确重载 |
Reload 调用 |
旧数据清除,新数据加载 |
- |
RedisConnectionManagerTests
| 测试用例 |
描述 |
输入 |
预期输出 |
Mock 需求 |
ShouldAcquireLock |
应获取分布式锁 |
有效 key |
锁获取成功 |
IConnectionMultiplexer |
ShouldReleaseLockCorrectly |
应正确释放锁 |
已获取的锁 |
锁释放成功 |
- |
ShouldNotReleaseOthersLock |
不应释放他人锁 |
其他实例的锁 |
释放失败(安全) |
- |
ShouldHandleConnectionFailure |
应处理连接失败 |
Redis 不可用 |
记录错误,返回失败 |
- |
ShouldExecuteInLock |
应在锁内执行操作 |
操作委托 |
操作执行,锁正确释放 |
- |
DistributedWeightedRoundRobinPolicyTests
| 测试用例 |
描述 |
输入 |
预期输出 |
Mock 需求 |
ShouldSelectByWeight |
应按权重选择 |
权重 [3, 1, 1] |
约 60% 选第一个 |
IConnectionMultiplexer |
ShouldFallbackOnLockFailure |
锁失败应降级 |
Redis 不可用 |
降级选择第一个可用 |
- |
ShouldReturnNullWhenNoDestinations |
无目标返回 null |
空目标列表 |
null |
- |
ShouldPersistStateToRedis |
状态应持久化到 Redis |
多次选择 |
状态存储正确 |
- |
ShouldExpireStateAfterTTL |
状态应在 TTL 后过期 |
1 小时后 |
状态重新初始化 |
- |
PgSqlConfigChangeListenerTests
| 测试用例 |
描述 |
输入 |
预期输出 |
Mock 需求 |
ShouldListenForNotifications |
应监听通知 |
NOTIFY 事件 |
触发重载 |
NpgsqlConnection |
ShouldFallbackToPolling |
应回退到轮询 |
通知失败 |
定时轮询检测 |
- |
ShouldReconnectOnFailure |
失败应重连 |
连接断开 |
自动重连 |
- |
ShouldDetectVersionChange |
应检测版本变化 |
版本号增加 |
触发重载 |
- |
P2 - 推荐覆盖(业务逻辑)
GatewayConfigControllerTests
| 测试用例 |
描述 |
输入 |
预期输出 |
Mock 需求 |
ShouldCreateTenant |
应创建租户 |
有效 DTO |
201 Created |
IDbContextFactory |
ShouldRejectDuplicateTenant |
应拒绝重复租户 |
已存在的 TenantCode |
400 BadRequest |
- |
ShouldCreateRoute |
应创建路由 |
有效 DTO |
201 Created |
- |
ShouldDeleteTenant |
应删除租户 |
有效 ID |
204 NoContent |
- |
ShouldReturn404ForMissingTenant |
不存在租户返回 404 |
无效 ID |
404 NotFound |
- |
ShouldReloadConfig |
应重载配置 |
POST /config/reload |
200 OK |
IRouteCache |
PendingServicesControllerTests
| 测试用例 |
描述 |
输入 |
预期输出 |
Mock 需求 |
ShouldListPendingServices |
应列出待处理服务 |
GET 请求 |
待处理服务列表 |
IDbContextFactory |
ShouldAssignService |
应分配服务 |
有效请求 |
服务实例创建 |
- |
ShouldRejectInvalidCluster |
应拒绝无效集群 |
不存在的 ClusterId |
400 BadRequest |
- |
ShouldRejectService |
应拒绝服务 |
reject 请求 |
状态更新为 Rejected |
- |
测试依赖
<!-- YarpGateway.Tests.csproj -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.0" />
<PackageReference Include="Testcontainers.Redis" Version="3.7.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="3.7.0" />
</ItemGroup>
运行测试命令
# 运行所有测试
dotnet test
# 运行特定测试类
dotnet test --filter "FullyQualifiedName~JwtTransformMiddlewareTests"
# 生成覆盖率报告
dotnet test --collect:"XPlat Code Coverage"
reportgenerator -reports:**/coverage.cobertura.xml -targetdir:coverage
覆盖率目标
| 组件 |
目标覆盖率 |
优先级 |
| JwtTransformMiddleware |
90% |
P0 |
| TenantRoutingMiddleware |
85% |
P0 |
| RouteCache |
80% |
P1 |
| DistributedWeightedRoundRobinPolicy |
80% |
P1 |
| Controllers |
70% |
P2 |
| 整体项目 |
75% |
- |
测试计划由分析生成,建议按优先级逐步实现。