关于 exp-test-maintainability
用于分析 .NET 测试代码可维护性问题并提出具体重构建议的技能,不会修改文件。它帮助开发者识别真实重复,同时兼顾测试清晰度与独立性。
主要功能
- 01查找重复构造与断言
- 02识别复制粘贴的测试方法
- 03检查初始化与测试基础设施
- 04要求模式至少出现三次
- 05提供重构示例及权衡
适用场景
- 01评估 .NET 测试项目中的重复
- 02寻找参数化测试候选项
- 03检查多个测试类的共享初始化
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dotnet/ skills --skill exp-test-maintainability安装后在 Claude Code 中输入/exp-test-maintainability确认已加载。
详细介绍
测试可维护性评估
分析 .NET 测试代码中的可维护性问题:重复的样板代码、复制粘贴的测试方法,以及测试方法和类之间的结构重复。生成一份报告,列出重构机会,并提供具体的重构前后建议。目标仅为分析——不要修改任何文件。
适用场景
- 用户要求查找测试中的重复代码或样板代码
- 用户想知道如何减少测试代码中的重复
- 用户要求减少测试重复、提升测试可读性或精简测试样板代码
- 用户要求找出测试套件中的重构机会
- 用户想找出适合共享的初始化或清理逻辑
- 用户问“我的测试中有哪些重复模式?”
- 用户想集中管理测试数据、引入构建器或辅助方法
不适用场景
- 用户想从头编写新测试(使用
writing-mstest-tests) - 用户想检测反模式或代码异味(使用
test-anti-patterns) - 用户想实际执行重构(直接协助他们;此技能仅用于分析)
输入
| 输入 | 必填 | 说明 |
|---|---|---|
| 测试代码 | 是 | 一个或多个待分析的测试文件,或测试项目目录 |
| 生产代码 | 否 | 被测代码,可用于了解哪些抽象可能有帮助 |
| 范围 | 否 | 指定在单个类内分析,还是跨多个类分析 |
工作流
第 1 步:收集测试代码
读取用户提供或引用的所有测试文件。如果用户指定目录或项目,请使用以下框架标记扫描所有测试文件:
| 框架 | 测试类标记 | 测试方法标记 |
|---|---|---|
| MSTest | [TestClass] | [TestMethod]、[DataTestMethod] |
| xUnit | (无——基于约定) | [Fact]、[Theory] |
| NUnit | [TestFixture] | [Test]、[TestCase]、[TestCaseSource] |
| TUnit | (无——基于约定) | [Test] |
第 2 步:识别可维护性问题
按以下类别扫描:
类别 1:重复构造对象
查找在 3 个或更多测试方法中使用相同或几乎相同参数构造的同一对象。
判断依据:
- 多个测试中出现参数相同的
new ClassName(...) - 多个测试创建配置相似的同一“被测系统”
- 重复创建并进行相同设置的模拟对象/伪对象/桩对象
潜在重构方案:
- 提取工厂方法或测试辅助方法(例如
CreateSut()、CreateDefaultOrder()) - 使用
[TestInitialize]/构造函数/[SetUp]共享对象构造逻辑 - 对具有多种变体的复杂对象引入构建器模式
示例——重构前:
[TestMethod]
public void Process_ValidOrder_Succeeds()
{
var logger = new FakeLogger();
var email = new FakeEmailService();
var inventory = new FakeInventory(stock: 100);
var processor = new OrderProcessor(logger, email, inventory);
// ...
}
[TestMethod]
public void Process_EmptyItems_Fails()
{
var logger = new FakeLogger();
var email = new FakeEmailService();
var inventory = new FakeInventory(stock: 100);
var processor = new OrderProcessor(logger, email, inventory);
// ...
}
重构后——提取工厂方法:
private static OrderProcessor CreateProcessor(int stock = 100)
{
return new OrderProcessor(new FakeLogger(), new FakeEmailService(), new FakeInventory(stock));
}
类别 2:重复断言模式
查找在 3 个或更多测试方法中重复出现的同一组断言。
判断依据:
- 多个测试对结果对象断言相同的一组属性
- 重复出现先检查非空、再检查值的断言序列
- 不同方法中重复出现相同的一组
Assert.AreEqual调用
潜在重构方案:
- 提取自定义断言辅助方法(例如
AssertValidOrder(order, expectedTotal, expectedStatus)) - 使用框架专用的断言扩展
- 引入
Verify方法,检查一组标准属性
类别 3:复制粘贴式测试方法
查找方法体几乎相同、仅输入值或单个参数不同的测试方法。
判断指标:
- 3 个或更多方法的结构相同,但字面值不同
- 可以合并为
[DataRow]/[Theory]/[TestCase]的方法 - 测试名称遵循
Method_Input1_Result、Method_Input2_Result之类的模式
潜在重构方式:
- 将其转换为参数化测试,使用
[DataRow]/[InlineData]/[TestCase] - 对于复杂输入,使用
[DynamicData]/[MemberData]/[TestCaseSource] - 如果所有值都是编译时常量,优先使用带
DisplayName的[DataRow],而不是[DynamicData]。将[DynamicData]留给计算得出的值或复杂值。 - 为不直观的参数值添加
DisplayName。[DataRow("Gold", 100.0, 90.0)]一目了然;[DataRow(3, 7, 42)]则不然。
类别 4:重复的初始化/清理逻辑
查找在多个测试类中重复出现的初始化或清理代码。
判断指标:
- 多个
[TestInitialize]/[SetUp]方法的代码相似 - 重复进行数据库填充、文件创建或 HTTP 客户端配置
- 多个类中存在相同的
using/IDisposable清理模式
潜在重构方式:
- 提取共享测试基类或测试夹具
- 使用组合方式搭配共享辅助类
- 创建测试上下文工厂
类别 5:重复的测试基础设施
查找多个测试类中共有的结构模式。
判断指标:
- 多个类中以相同方式配置了相同的模拟接口
- 重复使用相似的
DelegatingHandler模式来设置HttpClient - 多个测试类中存在相同的日志记录/配置框架代码
潜在重构方式:
- 提取共享测试夹具或辅助库
- 创建可复用的伪实现
- 引入测试框架类
步骤 3:应用校准规则
报告之前,根据以下规则筛选发现项:
- 仅报告出现 3 次或更多的情况。 两处相似的设置不算样板代码,也可能是有意为之的清晰表达。
- 不要标记简单构造函数。
new Calculator()或new List<int>()不属于有意义的样板代码。也不要建议为new User(1, "Alice")使用构建器。 - 尊重有意为之的冗长写法。 如果每个测试都能独立完成且易于理解,那么在每个测试中显式设置是合理的做法。可以提及,但不要将其标记为问题。
- 区分结构相似与真正重复。 遵循 AAA(Arrange-Act-Assert)的测试自然会显得相似。只有实际代码(而不只是结构)重复时才标记。
- 考虑重构的影响范围。 一个被 20 个测试共享的辅助方法会引入耦合。请指出这一权衡。
- 如果测试套件已维护良好,请明确说明。 报告中只发现少量改进机会完全合理。肯定已有的优点。
步骤 4:报告发现项
按照以下结构呈现发现项:
- 摘要 — 按类别说明发现的模式数量。如果测试套件没有问题,开头就说明这一点。
- 按类别列出发现项 — 对于发现的每种模式:
- 类别名称和描述
- 位置:列出涉及的具体测试方法和文件
- 重复代码模式(展示一个典型示例)
- 建议的重构方式,并提供具体的重构前/后示例
- 预计影响:可精简多少行代码/方法
- 重构优先级 — 按以下因素排列发现项:
- 出现次数(次数越多,价值越高)
- 重复代码的复杂度(复杂设置优先于简单构造)
- 风险(优先进行低风险提取)
- 权衡 — 对于每项建议,请说明:
- 提升了哪些可读性
- 牺牲了哪些局部性/独立性
- 根据出现次数,这么做是否值得
验证
- 每条发现都包含具体的文件和方法位置
- 每条发现都展示实际重复的代码,而不只是描述
- 每条建议都包含具体的前后示例
- 发现已按出现次数至少 3 次的阈值筛选
- 不标记简单构造函数
- 每条建议都说明了权衡
- 如果测试全部通过,报告应在开头说明
常见问题
| 问题 | 解决方案 |
|---|---|
| 将 AAA 结构标记为重复代码 | Arrange-Act-Assert 模式不是样板代码——只有实际代码重复时才标记 |
| 仅出现 2 次就建议提取 | 等到出现 3 次或更多再建议提取 |
| 什么都建议用基类实现 | 优先使用组合(辅助函数、工厂),而非继承 |
| 忽视可读性成本 | 每次提取都会增加间接层——应说明这种权衡 |
将简单的 new X() 标记为样板代码 | 仅标记包含多个参数或配置的复杂构造 |
| 为追求 DRY 而牺牲测试隔离性 | 通过辅助函数共享可变状态的测试会产生耦合——应对此发出警告 |
评论
支持 Markdown