ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ECC C 测试规范实战:基于 xUnit、FluentAssertions 与 Testcontainers 的 .NET 测试体系搭建指南

ECC C 测试规范实战:基于 xUnit、FluentAssertions 与 Testcontainers 的 .NET 测试体系搭建指南 ECC C# 测试规范实战基于 xUnit、FluentAssertions 与 Testcontainers 的 .NET 测试体系搭建指南【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC本篇技术指南以 ECCAgent Harness Performance Optimization System仓库中的 C# 测试规则 为核心骨架面向在 Claude Code、Codex、Opencode 等 Agent 工作流中编写与维护 C# / .NET 代码的开发者系统讲解测试框架选型、测试目录组织、AAA 命名规范、ASP.NET Core 集成测试与覆盖率门禁的完整落地方法。读完本文你将掌握一套可直接复制的 .NET 测试工程结构并能借助 ECC 的csharp-reviewerAgent 与csharp-testingSkill 自动化执行测试与质量评审。一、为什么需要语言级测试规则ECC 的规则体系采用「通用层 语言层」的分层设计rules/common/存放语言无关的通用原则如 common/testing.md 中要求的 80% 覆盖率与 TDD 工作流而rules/csharp/testing.md这类语言专属文件则在通用基础上叠加 C# / .NET 生态特有的框架选型与集成测试方案。依据 rules/README.md 的优先级约定当语言级规则与通用规则冲突时语言级规则优先——这正体现了 C# 测试实践的生态特殊性。该文件的 frontmatter 声明了其适用路径范围paths: - **/*.cs - **/*.csx - **/*.csproj这意味着任何涉及.cs、.csx、.csproj文件的改动都会触发该规则的约束。其标题明确注明「此文件扩展自 common/testing.md」因此阅读时应与通用规则配套理解。二、测试框架选型四大组件各司其职原规则对测试技术栈给出了清晰、克制的四行约束每一行都对应一个生态位关注点推荐工具定位单元与集成测试框架xUnit.NET 事实标准的测试框架优先于 NUnit / MSTest断言可读性FluentAssertions流式断言语法替代Assert.Equal的样板代码依赖隔离Moq或NSubstitute两者择一即可用于 mock 外部依赖真实基础设施集成测试Testcontainers以 Docker 容器启动真实数据库/消息队列在 csharp-testing Skill 的「Test Framework Stack」表中这套栈得到了进一步扩充除上述四者外还加入了WebApplicationFactoryASP.NET Core 集成测试入口与Bogus测试数据生成器构成一套完整的测试工具矩阵。Skill 的激活场景明确包括「编写新测试」「评审测试质量与覆盖率」「搭建 .NET 测试基础设施」「调试 flaky 或慢测试」意味着这套栈不仅用于手写测试也用于 Agent 自动化生成与审查测试。三、测试目录组织与命名规范3.1 目录镜像 src 结构规则要求测试代码在tests/下镜像src/的目录结构并明确分离单元、集成、端到端三类覆盖单元测试聚焦单个函数、工具类、组件不触碰外部依赖集成测试覆盖 API 端点、数据库操作等跨模块行为E2E 测试覆盖关键用户流程框架按语言自选。common/testing.md 进一步强调三类测试全部必需而非可选项。Skill 给出了一份可直接照搬的目录模板tests/ MyApp.UnitTests/ Services/ OrderServiceTests.cs PaymentServiceTests.cs Validators/ EmailValidatorTests.cs MyApp.IntegrationTests/ Api/ OrderApiTests.cs Repositories/ OrderRepositoryTests.cs MyApp.TestHelpers/ Builders/ OrderBuilder.cs Fixtures/ DatabaseFixture.cs将测试工程与主工程分离MyApp.UnitTests、MyApp.IntegrationTests再单独抽出一个MyApp.TestHelpers存放 Builder 与 Fixture可让测试工程之间的依赖关系清晰可控也便于 CI 中按项目粒度单独执行测试。3.2 以行为命名而非实现细节规则强调「用行为而非实现细节为测试命名」。Skill 将这一原则固化为命名模式Method_ExpectedResult_WhenCondition并给出了正反对照——测试名描述实现如Test1、Method1_Test属于反模式正确写法是描述被测行为public sealed class OrderServiceTests { [Fact] public async Task FindByIdAsync_ReturnsOrder_WhenOrderExists() { // Arrange // Act // Assert } }common/testing.md 中的 TypeScript 示例returns empty array when no markets match query印证了这一约定是跨语言的通用要求测试名称必须能独立说明「方法名_期望结果_触发条件」使失败的测试成为可读的需求文档。四、AAA 结构与参数化测试4.1 Arrange-Act-Assert 三段式原规则示例中的// Arrange / Act / Assert注释标记对应 common/testing.md 中明确要求的AAAArrange-Act-Assert测试结构。Skill 给出了完整的实战化版本包括通过构造函数注入被测对象SUT与 mock 依赖public sealed class OrderServiceTests { private readonly IOrderRepository _repository Substitute.ForIOrderRepository(); private readonly ILoggerOrderService _logger Substitute.ForILoggerOrderService(); private readonly OrderService _sut; public OrderServiceTests() { _sut new OrderService(_repository, _logger); } [Fact] public async Task PlaceOrderAsync_ReturnsSuccess_WhenRequestIsValid() { // Arrange var request new CreateOrderRequest { CustomerId cust-123, Items [new OrderItem(SKU-001, 2, 29.99m)] }; // Act var result await _sut.PlaceOrderAsync(request, CancellationToken.None); // Assert result.IsSuccess.Should().BeTrue(); result.Value.Should().NotBeNull(); result.Value!.CustomerId.Should().Be(cust-123); } [Fact] public async Task PlaceOrderAsync_ReturnsFailure_WhenNoItems() { // Arrange var request new CreateOrderRequest { CustomerId cust-123, Items [] }; // Act var result await _sut.PlaceOrderAsync(request, CancellationToken.None); // Assert result.IsSuccess.Should().BeFalse(); result.Error.Should().Contain(at least one item); } }注意其中的result.Value!.CustomerId——null 抑制符的使用边界在 csharp-reviewer Agent 中属于 HIGH 级类型安全检查项测试代码同样适用。4.2 Theory 参数化测试对「同一逻辑、多组输入」的校验场景xUnit 的[Theory]比复制多个[Fact]更高效这也是 Skill 中独立成节的模式[Theory] [InlineData(, false)] [InlineData(a, false)] [InlineData(abc.d, false)] [InlineData(userexample.com, true)] [InlineData(usertagexample.co.uk, true)] public void IsValidEmail_ReturnsExpected(string email, bool expected) { EmailValidator.IsValid(email).Should().Be(expected); }当参数较复杂时改用[MemberData]搭配强类型的TheoryDataT1, T2将用例集中定义为一个静态属性便于集中管理非法输入的边界组合public static TheoryDataCreateOrderRequest, string InvalidOrderCases new() { { new() { CustomerId , Items [ValidItem()] }, CustomerId }, { new() { CustomerId c1, Items [] }, at least one item }, { new() { CustomerId c1, Items [new(, 1, 10m)] }, SKU }, };4.3 NSubstitute 依赖隔离示例规则允许 Moq 或 NSubstitute 二选一Skill 的示例统一使用 NSubstitute。其核心能力是行为验证——不仅模拟返回值还能断言依赖确实以预期参数被调用过[Fact] public async Task GetOrderAsync_ReturnsNull_WhenNotFound() { // Arrange var orderId Guid.NewGuid(); _repository.FindByIdAsync(orderId, Arg.AnyCancellationToken()) .Returns((Order?)null); // Act var result await _sut.GetOrderAsync(orderId, CancellationToken.None); // Assert result.Should().BeNull(); } [Fact] public async Task PlaceOrderAsync_PersistsOrder() { // Arrange var request ValidOrderRequest(); // Act await _sut.PlaceOrderAsync(request, CancellationToken.None); // Assert — verify the repository was called await _repository.Received(1).AddAsync( Arg.IsOrder(o o.CustomerId request.CustomerId), Arg.AnyCancellationToken()); }Received(1)与Arg.IsT(predicate)组合可以精确验证「调用次数 调用参数」这是纯 mock 方案难以做到的契约级保障。注意所有异步方法都显式传递CancellationToken——common/testing.md 与 Skill 的反模式清单都强调「忽略 CancellationToken」属于必须避免的反模式测试与生产代码一视同仁。五、ASP.NET Core 集成测试5.1 WebApplicationFactory 全链路测试原规则对集成测试有一条关键要求不要绕过中间件而是「通过 HTTP 测试认证、验证、序列化」——即让请求真正走完完整的 HTTP 管道包括认证中间件、模型绑定与 JSON 序列化从而验证的是系统真实行为而非被截断的内部路径。Skill 给出了完整的落地示例使用WebApplicationFactoryTEntryPoint并利用WithWebHostBuilder在测试环境替换真实数据库public sealed class OrderApiTests : IClassFixtureWebApplicationFactoryProgram { private readonly HttpClient _client; public OrderApiTests(WebApplicationFactoryProgram factory) { _client factory.WithWebHostBuilder(builder { builder.ConfigureServices(services { // Replace real DB with in-memory for tests services.RemoveAllDbContextOptionsAppDbContext(); services.AddDbContextAppDbContext(options options.UseInMemoryDatabase(TestDb)); }); }).CreateClient(); } [Fact] public async Task GetOrder_Returns404_WhenNotFound() { var response await _client.GetAsync($/api/orders/{Guid.NewGuid()}); response.StatusCode.Should().Be(HttpStatusCode.NotFound); } [Fact] public async Task CreateOrder_Returns201_WithValidRequest() { var request new CreateOrderRequest { CustomerId cust-1, Items [new(SKU-001, 1, 19.99m)] }; var response await _client.PostAsJsonAsync(/api/orders, request); response.StatusCode.Should().Be(HttpStatusCode.Created); response.Headers.Location.Should().NotBeNull(); } }这套模式的关键点在于HttpClient直接面向内存中的测试服务器发起真实 HTTP 请求Program作为入口点保证了测试的宿主配置与生产一致替换的只是数据存储层HTTP 管道本身保持原样。5.2 Testcontainers 真实基础设施当「InMemory 数据库」不足以模拟生产行为如约束、事务、特定 SQL 方言时规则要求使用Testcontainers拉起真实基础设施。Skill 以 PostgreSQL 为例展示了完整的生命周期管理public sealed class PostgresOrderRepositoryTests : IAsyncLifetime { private readonly PostgreSqlContainer _postgres new PostgreSqlBuilder() .WithImage(postgres:16-alpine) .Build(); private AppDbContext _db null!; public async Task InitializeAsync() { await _postgres.StartAsync(); var options new DbContextOptionsBuilderAppDbContext() .UseNpgsql(_postgres.GetConnectionString()) .Options; _db new AppDbContext(options); await _db.Database.MigrateAsync(); } public async Task DisposeAsync() { await _db.DisposeAsync(); await _postgres.DisposeAsync(); } [Fact] public async Task AddAsync_PersistsOrder() { var repo new SqlOrderRepository(_db); var order Order.Create(cust-1, [new OrderItem(SKU-001, 2, 10m)]); await repo.AddAsync(order, CancellationToken.None); var found await repo.FindByIdAsync(order.Id, CancellationToken.None); found.Should().NotBeNull(); found!.Items.Should().HaveCount(1); } }通过IAsyncLifetime接口将容器启动InitializeAsync与清理DisposeAsync纳入 xUnit 生命周期测试即可获得与生产一致的真实数据库行为且容器随测试结束自动回收。六、覆盖率门禁80% 基线及其落点规则对覆盖率提出三条硬性约束行覆盖率 ≥ 80%覆盖重点集中在领域逻辑、验证、认证、失败路径在 CI 中启用覆盖率采集并执行dotnet test。80% 的基线同时来自 common/testing.md 的通用要求「Minimum Test Coverage: 80%」且三类测试全部必需属于 ECC 全语言统一的硬门禁。值得注意的是规则并非要求「平均覆盖」而是强调失败路径——即catch分支、404/400 响应、校验失败等异常分支必须覆盖这部分往往是行覆盖率统计中最容易遗漏、也最能暴露缺陷的区域。Skill 提供了配套的覆盖采集命令# Run with coverage dotnet test --collect:XPlat Code CoverageXPlat Code Coverage是 .NET 官方跨平台覆盖收集器可在 Linux/macOS/Windows CI 上统一使用采集结果cobertura 格式可进一步接入覆盖率报告与门禁检查工具。七、将测试融入 Agent 工作流7.1 TDD 强制工作流common/testing.md 明确将 TDD 列为MANDATORY强制工作流先写测试RED运行测试——应当失败编写最小实现GREEN运行测试——应当通过重构IMPROVE验证覆盖率 ≥ 80%遇到测试失败时优先使用tdd-guideAgent 排查并依次检查测试隔离性 → mock 是否正确 → 修复实现而非测试除非确认测试本身写错。在 Agent 驱动的开发模式下这一流程可以保证新功能从第一行代码起就处于可验证状态。7.2 自动化评审与钩子代码评审csharp-reviewerAgent见 agents/csharp-reviewer.md在评审 C# 改动时会依次执行git diff -- *.cs、dotnet build、dotnet format --verify-no-changes并给出诊断命令矩阵其中测试相关命令包括dotnet build # Compilation check dotnet format --verify-no-changes # Format check dotnet test --no-build # Run tests dotnet test --collect:XPlat Code Coverage # Coverage评审输出采用[SEVERITY] 问题标题 / File / Issue / Fix四段式格式审批规则为无 CRITICAL 或 HIGH 级问题方可 Approve。自动化钩子参照 csharp 钩子规则可在~/.claude/settings.json中配置三类钩子让测试成为编辑动作的自然延伸PostToolUsedotnet format自动格式化并应用分析器修复dotnet build验证编辑后工程仍可编译dotnet test --no-build在行为变更后重跑最近的测试工程Stop会话结束前执行最终dotnet build对修改过的appsettings*.json发出警告防止密钥被提交。7.3 测试运行速查Skill 提供了覆盖各场景的运行命令可直接用于本地开发与 CI# Run all tests dotnet test # Run with coverage dotnet test --collect:XPlat Code Coverage # Run specific project dotnet test tests/MyApp.UnitTests/ # Filter by test name dotnet test --filter FullyQualifiedName~OrderService # Watch mode during development dotnet watch test --project tests/MyApp.UnitTests/八、常见反模式速查Skill 将规则背后的意图沉淀为一张反模式对照表可作为自检清单反模式正确做法测试实现细节测试行为与结果共享可变测试状态每个测试使用全新实例xUnit 构造函数天然支持异步测试中使用Thread.Sleep用Task.Delay配超时或使用轮询辅助断言ToString()输出断言类型化属性单个测试塞入巨型断言每个测试只做一条逻辑断言测试名描述实现按Method_ExpectedResult_WhenCondition命名忽略CancellationToken始终传递并验证取消行为九、规则落地路径小结综合以上内容在 ECC 工作流中落地 C# 测试体系只需四步选型xUnit FluentAssertions NSubstitute/Moq Testcontainers与 csharp 测试规则 保持一致组织tests/镜像src/分离 UnitTests / IntegrationTests / TestHelpers 三个工程按方法_期望_条件命名分层测试单元层用 AAA Theory mock集成层用WebApplicationFactoryTEntryPoint走完整 HTTP 管道真实基础设施场景用 Testcontainers守门禁CI 中dotnet test --collect:XPlat Code Coverage执行并卡 80% 行覆盖重点覆盖领域逻辑、验证、认证与失败路径配合csharp-reviewerAgent 与 PostToolUse/Stop 钩子实现测试、格式、编译的全自动守护。这套规范的价值在于它把「可测试性」从个人习惯提升为 Agent 与 CI 共同执行的工程契约——从写测试到跑测试再到评审测试每一个环节都有明确的工具、命令与门槛支撑最终让 .NET 代码库的每次变更都处于可验证、可回归的安全状态。【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表