.NET 测试生成实战指南:基于 code-testing-extensions 的 dotnet.md 扩展规范
【免费下载链接】skillsRepository for skills to assist AI coding agents with .NET and C#项目地址: https://gitcode.com/GitHub_Trending/skills17/skills
导读
本文以 code-testing-extensions 技能体系中面向 .NET(C#/F#/VB)的语言扩展文件 extensions/dotnet.md 为核心,系统讲解 AI 编码 Agent 在为 .NET 仓库生成单元测试时必须遵守的项目系统识别、构建/测试命令选择、项目引用校验、CS 错误码处理与 MSTest 模板规范。阅读本文后,你将掌握一套可直接落地的 .NET 测试生成决策框架,包括如何区分 SDK-style 与经典非 SDK 项目、如何把新测试文件正确注册进构建系统、如何验证测试能被 CI/评测 harness 发现,以及如何写出与仓库既有框架版本兼容的 MSTest 测试代码。
一、扩展文件在测试生成管线中的定位
在dotnet-test插件中,code-testing-extensions 是一个user-invocable: false、disable-model-invocation: true的引用型技能,它本身不直接执行任何逻辑,而是向测试生成管线提供一份"语言扩展文件清单"。其中 extensions/dotnet.md 就是面向 .NET 的权威语言指南,覆盖构建命令、测试命令、项目引用校验、常见 CS 错误码、MSTest 模板与覆盖率工具策略。
该文件被 code-testing-agent 的多 Agent 管线(Research → Plan → Implement → Build → Test → Fix → Lint)按需加载:code-testing-implementer在写测试前调用code-testing-extensions技能并读取dotnet.md,以确认 .NET 特有的命令与注册规则;unit-test-generation.prompt.md 也明确要求"Project reference validation: Before writing test code, verify the test project references all source projects the tests will use. Call thecode-testing-extensionsskill and read the language-specific extension file for guidance (e.g.,dotnet.mdfor .NET)"。因此,dotnet.md是 .NET 场景下生成测试前必须读入的"操作手册"。
二、项目系统识别:写代码前的第一道决策
dotnet.md的开篇就强调:在选定任何命令或编辑任何清单文件之前,必须先判定项目系统,因为 SDK-style 与经典非 SDK 项目的后续一切操作(构建、测试、文件注册、依赖管理)都完全不同。
| 信号 | 项目系统 | 后果 |
|---|---|---|
根节点<Project Sdk="...">或存在Sdk属性 | SDK-style | dotnet build/dotnet test通常有效;新建的*.cs文件一般会被 glob 通配自动包含 |
出现ToolsVersion、Microsoft.Common.props/Microsoft.CSharp.targets导入、显式<Reference>与<Compile Include>项 | 经典非 SDK | 必须保留仓库既有的 MSBuild / 测试运行器命令;每个新增源文件与测试文件都必须手工加入项目 |
项目旁存在packages.config | 经典 NuGet 依赖管理 | 必须保留packages.config与程序集引用;除非用户明确要求迁移,否则禁止运行dotnet add package或引入PackageReference |
这一判断在仓库测试夹具中有真实案例可循:tests/dotnet-test/code-testing-agent/fixtures/classic-mstest/tests/Discounts.Tests.csproj就是一个典型经典项目,根节点没有Sdk属性,代之以ToolsVersion="15.0"、Microsoft.Common.props导入、显式<Reference>(含MSTest.TestFramework、Moq、NBuilder的HintPath)以及逐文件列出的<Compile Include>项,并配有packages.config。生成测试时必须对这种项目采用"保留式"策略,而不是试图现代化改造。
对于经典项目,dotnet.md要求以仓库中已检入的脚本、CI 配置、README*与AGENTS.md作为构建/测试命令的权威来源。常见命令是MSBuild.exe后接vstest.console.exe或MSTest.exe,但"仓库里检入的命令说了算"。如果当前机器没有安装兼容的运行器,应如实报告阻塞原因,而不是迁移项目或谎称dotnet test已成功。
三、构建与测试命令速查
3.1 构建命令
| 范围 | 命令 |
|---|---|
| SDK-style 测试项目 | dotnet build MyProject.Tests.csproj |
| SDK-style 解决方案(最终校验) | dotnet build MySolution.sln --no-incremental |
| 经典非 SDK 项目 | 使用仓库既有的 MSBuild 命令(通常是MSBuild.exe MySolution.sln /t:Build) |
关键约定:
- 依赖已还原时可加
--no-restore跳过还原; - 用
-v:q(quiet)减少输出噪音; - 最终校验构建必须使用
--no-incremental——增量构建会掩盖 CS7036 这类仅在全新编译时暴露的错误。这一点与 unit-test-generation.prompt.md 中"Final full-workspace build: run a full non-incremental build from the workspace root to catch cross-project errors"的要求一致。
3.2 测试命令
| 范围 | 命令 |
|---|---|
| SDK-style 全部测试 | dotnet test |
| SDK-style 按类过滤 | dotnet test --filter "FullyQualifiedName~ClassName" |
| SDK-style 构建后运行 | dotnet test --no-build |
| 经典非 SDK | 使用仓库检入的运行器命令,常见为 MSBuild 之后执行vstest.console.exe <test.dll> |
辅助约定:已构建过则加--no-build;需要安静输出则加-v:q。注意,dotnet.md给出的是基础形态;仓库内 run-tests 技能进一步细化了 VSTest 模式、VSTest-to-MTP 桥接模式与 SDK 10+ 原生 MTP 模式的命令差异,并强调--project只在 SDK 10+ 原生 MTP 命令模式下有效,而桥接模式必须保留--分隔符——这是生成测试后实际运行时的进阶参考。
3.3 格式化(Lint)命令
dotnet format --include path/to/file.cs dotnet format MySolution.sln # 整个解决方案四、项目引用校验:消灭 CS0234 / CS0246
写测试代码之前,必须读取测试项目的.csproj,确认其中包含测试所依赖程序集的<ProjectReference>项。缺失引用会导致:
- CS0234("namespace not found")—— 命名空间未找到;
- CS0246("type not found")—— 类型未找到。
修复方式是补充引用:
<ItemGroup> <ProjectReference Include="../SourceProject/SourceProject.csproj" /> </ItemGroup>对于经典项目,要保留其既有<ProjectReference>的元数据(如ProjectGUID、Name)与配置映射,而不是替换成 SDK-style 的简写形式。以tests/dotnet-test/code-testing-agent/fixtures/classic-mstest/tests/Discounts.Tests.csproj为例,其引用写法是带<Project>{4560D11B-...}</Project>与<Name>Discounts</Name>的完整形式,生成测试时不得破坏这种结构。
五、常见 CS 错误码对照表
dotnet.md提供了一张在测试生成与修复循环中高频出现的 C# 编译错误对照表,是code-testing-fixer诊断构建失败时的直接查表依据:
| 错误码 | 含义 | 修复方法 |
|---|---|---|
| CS0234 | 命名空间未找到 | 在测试.csproj中为源项目添加<ProjectReference> |
| CS0246 | 类型未找到 | 添加using Namespace;或补充缺失的<ProjectReference> |
| CS0103 | 名称未找到 | 检查拼写,添加using语句 |
| CS1061 | 缺少成员 | 核对方法/属性名与源码完全一致 |
| CS0029 | 类型不匹配 | 强转或把类型改成与期望签名一致 |
| CS7036 | 缺少必需参数 | 阅读构造函数/方法签名并传入全部必需参数 |
以 dotnet-examples.md 中的修复循环为例:error CS0246: The type or namespace name 'Moq' could not be found的根因是测试项目缺少 Moq NuGet 包,修复命令为dotnet add tests/Contoso.Billing.Tests/Contoso.Billing.Tests.csproj package Moq;而error CS7036: There is no argument given that corresponds to the required parameter 'repository'则是因为测试代码对使用主构造函数(primary constructor)的InvoiceService调用了无参构造,正确做法是先创建Mock<IInvoiceRepository>再传入repositoryMock.Object。这两个案例恰好演示了错误码表在"Build → Fix → Rebuild"循环中的实际用法。
六、.csproj/.sln处理与强制注册规则
6.1 构建范围策略
- 分阶段实现期间,只构建具体测试
.csproj以换取速度; - 最终校验时用
--no-incremental构建完整.sln; - 全解决方案构建能捕获跨项目引用错误,而作用域受限的构建会漏掉这些错误。
6.2 把测试代码注册进构建(MANDATORY)
写新的 C# 测试文件之前,必须检查测试项目的编译项:
- SDK-style:
*.cs通常由 glob 自动包含,除非默认编译项被禁用,否则不要添加冗余的<Compile Include>; - 经典非 SDK:每个新文件都必须显式登记,路径相对于项目,保留其路径分隔符与排序:
<Compile Include="Services\OrderServiceTests.cs" />编辑完成后要重新打开项目,确认新测试路径恰好出现一次。磁盘上存在但未登记进经典项目编译项的文件不属于测试程序集,绝不能将其报告为已生成的覆盖率。
6.3 注册新测试项目(使用dotnet new后 MANDATORY)
新建的.csproj对dotnet test <solution>、仓库根目录下的dotnet test、以及任何 CI/基准测试 harness 都是不可见的,直到它被加入解决方案。必须在创建项目后立即执行dotnet sln add(对应 code-testing-agent 流程中的 Step 3 "Register Test Project with Build System"),绝不能推迟到后续步骤:
- 使用研究/计划文档中
<TESTAGENT_DIR>下标识的精确解决方案或解决方案筛选器目标(.sln/.slnx/.slnf),不要自行搜索或替换其他目标; - 若目标是
.sln或.slnx,运行dotnet sln <solution> add <test-project.csproj>; - 若目标是
.slnf(解决方案筛选器),还要确保新项目被包含进筛选器——只添加到底层.sln可能不足以让测试被发现; - 若项目已包含在测试所用的解决方案或筛选器中,则跳过此步;
- 优先使用研究得出的测试命令。只有针对 .NET SDK 10+ 且使用 MTP 风格语法的仓库,才用
dotnet test --solution <solution>;否则使用标准位置参数形式dotnet test <solution>。
6.4 Harness 发现校验(成功报告的硬门槛)
在报告成功之前,必须从仓库根目录运行与 harness 等价的发现命令,确认测试数量至少增加"你生成的测试数"。harness(CI、msbench、覆盖率工具)并不知道你针对的是哪个.csproj——它运行的是解决方案级命令,所以即使dotnet test MyProject.Tests.csproj能通过,只要dotnet test <solution> --list-tests无法枚举出新测试,就毫无价值:
# 从仓库根目录,针对 <TESTAGENT_DIR>/research.md 中识别的解决方案执行 dotnet test <solution> --list-tests --no-build 2>&1 | grep -c '^ [A-Za-z]'若增量为0,说明新项目不在解决方案里,执行dotnet sln <solution> add <test-project.csproj>后重新校验。在 harness 命令看到新测试之前,不得报告成功。
对于经典非 SDK 项目,改用仓库的正常构建与发现命令,最低可接受的校验是:
- 新文件以
<Compile Include="...">的形式恰好出现一次; - 经典项目按其文档化的 MSBuild 命令构建成功;
- 仓库的测试运行器能发现新测试。
若环境缺少 Visual Studio/MSBuild/测试运行器工具链,应验证项注册、报告执行被阻塞,不得用dotnet test顶替或现代化改造项目。
七、测试框架检测与版本兼容
从测试项目的.csproj、packages.config与引用程序集HintPath检测框架及其安装版本,并匹配仓库既有的框架、模拟库、基础夹具与 API 级别:
| 包引用 | 框架 | 特性 | 断言风格 |
|---|---|---|---|
MSTest.Sdk或MSTest.TestFramework | MSTest | [TestClass]、[TestMethod]、[DataRow] | Assert.AreEqual(expected, actual) |
xunit | xUnit | [Fact]、[Theory]、[InlineData] | Assert.Equal(expected, actual) |
NUnit | NUnit | [TestFixture]、[Test]、[TestCase] | Assert.That(actual, Is.EqualTo(expected)) |
使用仓库既有框架,不要引入不同的框架。
针对 MSTest,只有当安装版本支持对应 API 时才加载writing-mstest-tests的示例。特别是Assert.ThrowsExactly与统一集合断言要求MSTest 3.8+,旧版套件应保持Assert.ThrowsException、StringAssert、CollectionAssert等兼容写法。绝不为了使用更新示例而升级 MSTest、Moq、NBuilder 或其他测试依赖。这一点与 writing-mstest-tests 中"Treat this as a hard gate"(硬门槛)的规则互为印证:例如 MSTest 3.5.x 不能用Assert.ThrowsExactly、Assert.Contains、ValueTuple 版DynamicData或构造函数注入的TestContext,MSTEST0039诊断也提示 3.8+ 才应把Assert.ThrowsException迁移到Assert.Throws/Assert.ThrowsExactly。仓库夹具tests/dotnet-test/code-testing-agent/fixtures/classic-mstest/tests/Discounts.Tests.csproj引用的正是 MSTest.TestFramework 3.5.2,属于必须使用Assert.ThrowsException<T>的老版本场景。
八、MSTest 模板与数据驱动写法
dotnet.md给出了生成测试时的标准 MSTest 模板,包含[TestClass]、sealed类、AAA 结构、MethodName_Scenario_ExpectedResult命名与[DataRow]参数化:
using Microsoft.VisualStudio.TestTools.UnitTesting; namespace ProjectName.Tests; [TestClass] public sealed class ClassNameTests { [TestMethod] public void MethodName_Scenario_ExpectedResult() { // Arrange var sut = new ClassName(); // Act var result = sut.MethodName(input); // Assert Assert.AreEqual(expected, result); } [TestMethod] [DataRow(2, 3, 5, DisplayName = "Positive numbers")] [DataRow(-1, 1, 0, DisplayName = "Negative and positive")] public void Add_ValidInputs_ReturnsSum(int a, int b, int expected) { // Act var result = _sut.Add(a, b); // Assert Assert.AreEqual(expected, result); } }在 dotnet-examples.md 的端到端样例中可以看到这一模板的完整落地:InvoiceServiceTests使用 Moq 模拟IInvoiceRepository,用[DataRow]覆盖CalculateTotal的多个输入组合(含DisplayName),用Assert.ThrowsExactly<ArgumentNullException>/Assert.ThrowsExactlyAsync<KeyNotFoundException>覆盖异常路径,并用_repositoryMock.Verify(r => r.UpdateAsync(invoice), Times.Once)校验状态迁移的副作用——这正是"行为固定"式断言的实践。若要使用 ValueTuple 版DynamicData或TestDataRow<T>(MSTest 3.7+/3.8+)等更进阶的数据驱动写法,可进一步参考 writing-mstest-tests 的 Step 4。
九、覆盖率工具策略:默认不介入
dotnet.md明确要求:默认情况下不要配置或运行代码覆盖率测量工具(coverlet、dotnet-coverage、XPlat Code Coverage),因为这些工具在不同配置下行为不一致且浪费大量时间。覆盖率由评测 harness 另行测量。
例外规则:
- SDK-style 例外:仅当用户或评测 harness 明确要求 Cobertura/XML 产物时,才以
PackageReference形式添加coverlet.collector,让 harness 能产出该产物; - 经典非 SDK 项目:保留
packages.config,只使用仓库既有的覆盖率工作流,绝不注入PackageReference; - 无论哪种情况,都不要自己运行覆盖率命令。
这与 dotnet-test/README.md 中"Coverage and CRAP analysis accept existing Cobertura reports; they do not inject SDK-style coverage packages into classic projects"的表述一致。
十、典型工作流串联:从研究到最终报告
把上述规则放入 code-testing-agent 的完整管线中,一条典型的 .NET 测试生成链路如下:
- Research:
code-testing-researcher产出<TESTAGENT_DIR>/research.md,记录项目路径、目标框架(如 .NET 9)、测试框架版本(如 MSTest 3.8)、构建/测试/格式化命令、待测文件清单与优先级; - Plan:
code-testing-planner产出<TESTAGENT_DIR>/plan.md,为每个待测方法规划 happy path、边界与错误用例,并给出分阶段命令(如dotnet build tests/Contoso.Billing.Tests/Contoso.Billing.Tests.csproj); - Implement:
code-testing-implementer读取dotnet.md,确认项目系统 → 校验<ProjectReference>→ 按需dotnet sln add→ 生成测试文件 → 执行 scoped build/test/fix 循环; - Harness 校验:按 §6.4 从仓库根目录运行解决方案级
--list-tests,确认新测试被枚举; - 最终校验:
dotnet build MySolution.sln --no-incremental全量构建,跑通所有测试; - Final Report:
code-testing-generator产出汇总报告,列出创建的测试文件、通过/失败数量、覆盖的方法矩阵以及 scoped/full build 的通过状态(详见 dotnet-examples.md 的 Sample Final Report)。
贯穿全程的三条铁律:保留仓库既有项目系统与依赖栈、新文件必须注册进构建、harness 命令看不到的测试等于不存在。遵循这套规范,AI Agent 生成的 .NET 测试才能既编译通过、又真正进入 CI 与评测体系,而非"看起来成功"。
【免费下载链接】skillsRepository for skills to assist AI coding agents with .NET and C#项目地址: https://gitcode.com/GitHub_Trending/skills17/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考