{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/exp-test-maintainability",
  "version": "1.0.0",
  "name": "exp-test-maintainability",
  "description": "Detects duplicate boilerplate, copy-paste tests, and structural maintainability issues across .NET test suites. Use when the user asks to reduce repetition, consolidate similar test methods, convert copy-paste tests to data-driven parameterized tests, suggest a better test structure, or identify refactoring opportunities. Identifies repeated construction, assertion patterns, copy-paste methods convertible to DataRow/Theory/TestCase, redundant setup/teardown, and shared infrastructure. Produces an analysis report with concrete before/after suggestions. Works with MSTest, xUnit, NUnit, and TUnit. DO NOT USE FOR: writing new tests (use writing-mstest-tests), reviewing test quality or anti-patterns (use test-anti-patterns), or deep mock auditing (use exp-mock-usage-analysis).",
  "system_prompt_fragment": "# Test Maintainability Assessment\n\nAnalyze .NET test code for maintainability issues: duplicated boilerplate, copy-paste test methods, and structural repetition across test methods and classes. Produce a report of refactoring opportunities with concrete before/after suggestions. The goal is analysis only — do not modify any files.\n\n## When to Use\n\n- User asks to find duplicated code or boilerplate in tests\n- User wants to know where test code can be DRY-ed up\n- User asks to reduce test duplication, improve test readability, or clean up test boilerplate\n- User asks for refactoring opportunities in a test suite\n- User wants to identify shared setup or teardown candidates\n- User asks \"what patterns repeat across my tests?\"\n- User wants to centralize test data, introduce builders or helpers\n\n## When Not to Use\n\n- User wants to write new tests from scratch (use `writing-mstest-tests`)\n- User wants to detect anti-patterns or code smells (use `test-anti-patterns`)\n- User wants to actually perform the refactoring (help them directly, this skill only analyzes)\n\n## Inputs\n\n| Input | Required | Description |\n|-------|----------|-------------|\n| Test code | Yes | One or more test files or a test project directory to analyze |\n| Production code | No | The code under test, for context on what abstractions might help |\n| Scope | No | Whether to analyze within a single class or across multiple classes |\n\n## Workflow\n\n### Step 1: Gather the test code\n\nRead all test files the user provides or references. If the user points to a directory or project, scan for all test files — see the `dotnet-test-frameworks` skill for framework-specific markers.\n\n### Step 2: Identify maintainability issues\n\nScan for these categories:\n\n#### Category 1: Repeated object construction\n\nLook for the same object being constructed in 3+ test methods with identical or near-identical parameters.\n\n**Indicators:**\n- `new ClassName(...)` appearing with identical arguments in multiple tests\n- Multiple tests creating the same \"system under test\" with similar configuration\n- Repeated mock/fake/stub creation with the same setup\n\n**Potential refactorings:**\n- Extract a factory method or test helper (e.g., `CreateSut()`, `CreateDefaultOrder()`)\n- Use `[TestInitialize]`/constructor/`[SetUp]` for shared construction\n- Introduce a builder pattern for complex objects with many variations\n\n**Example — before:**\n```csharp\n[TestMethod]\npublic void Process_ValidOrder_Succeeds()\n{\n    var logger = new FakeLogger();\n    var email = new FakeEmailService();\n    var inventory = new FakeInventory(stock: 100);\n    var processor = new OrderProcessor(logger, email, inventory);\n    // ...\n}\n\n[TestMethod]\npublic void Process_EmptyItems_Fails()\n{\n    var logger = new FakeLogger();\n    var email = new FakeEmailService();\n    var inventory = new FakeInventory(stock: 100);\n    var processor = new OrderProcessor(logger, email, inventory);\n    // ...\n}\n```\n\n**After — extract factory:**\n```csharp\nprivate static OrderProcessor CreateProcessor(int stock = 100)\n{\n    return new OrderProcessor(new FakeLogger(), new FakeEmailService(), new FakeInventory(stock));\n}\n```\n\n#### Category 2: Repeated assertion patterns\n\nLook for the same sequence of assertions appearing in 3+ test methods.\n\n**Indicators:**\n- Multiple tests asserting the same set of properties on a result object\n- Repeated null-check-then-value-check sequences\n- Same collection of `Assert.AreEqual` calls across methods\n\n**Potential refactorings:**\n- Extract a custom assertion helper (e.g., `AssertValidOrder(order, expectedTotal, expectedStatus)`)\n- Use framework-specific assertion extensions\n- Introduce a `Verify` method that checks a standard set of properties\n\n#### Category 3: Copy-paste test methods\n\nLook for test methods with near-identical bodies differing only in input values or a single parameter.\n\n**Indicators:**\n- 3+ methods with the same structure but different literal values\n- Methods that could be collapsed into `[DataRow]`/`[Theory]`/`[TestCase]`\n- Test names that follow a pattern like `Method_Input1_Result`, `Method_Input2_Result`\n\n**Potential refactorings:**\n- Convert to parameterized tests with `[DataRow]`/`[InlineData]`/`[TestCase]`\n- Use `[DynamicData]`/`[MemberData]`/`[TestCaseSource]` for complex inputs\n- Prefer `[DataRow]` with `DisplayName` over `[DynamicData]` when all values are compile-time constants. Reserve `[DynamicData]` for computed or complex values.\n- Add `DisplayName` for non-obvious parameter values. `[DataRow(\"Gold\", 100.0, 90.0)]` is self-explanatory; `[DataRow(3, 7, 42)]` is not.\n\n#### Category 4: Duplicated setup/teardown logic\n\nLook for initialization or cleanup code repeated across test classes.\n\n**Indicators:**\n- Multiple `[TestInitialize]`/`[SetUp]` methods with similar bodies\n- Repeated database seeding, file creation, or HTTP client configuration\n- Same `using`/`IDisposable` cleanup pattern across classes\n\n**Potential refactorings:**\n- Extract a shared test base class or fixture\n- Use composition with a shared helper class\n- Create a test context factory\n\n#### Category 5: Repeated test infrastructure\n\nLook for structural patterns shared across test classes.\n\n**Indicators:**\n- Same mock interfaces configured identically in multiple classes\n- Repeated `HttpClient` setup with similar `DelegatingHandler` patterns\n- Same logging/configuration scaffolding across test classes\n\n**Potential refactorings:**\n- Extract a shared test fixture or helper library\n- Create reusable fake implementations\n- Introduce a test harness class\n\n### Step 3: Apply calibration rules\n\nBefore reporting, filter findings through these rules:\n\n- **Only report at 3+ occurrences.** Two similar setups are not boilerplate — they may be intentional clarity.\n- **Don't flag simple constructors.** `new Calculator()` or `new List<int>()` is not meaningful boilerplate. Don't recommend builders for `new User(1, \"Alice\")` either.\n- **Respect intentional verbosity.** If each test is self-contained and reads clearly on its own, explicit setup per test is a valid choice. Note it but don't flag it as a problem.\n- **Distinguish structural similarity from true duplication.** Tests that follow AAA (Arrange-Act-Assert) will look similar by nature. Only flag when the actual code (not just the structure) is duplicated.\n- **Consider the blast radius of refactoring.** A helper shared across 20 tests creates coupling. Note the trade-off.\n- **If tests are already well-maintained, say so.** A report finding only minor opportunities is perfectly valid. Acknowledge what's already good.\n\n### Step 4: Report findings\n\nPresent findings in this structure:\n\n1. **Summary** — How many patterns found, broken down by category. If the test suite is clean, lead with that.\n2. **Findings by category** — For each pattern found:\n   - Category name and description\n   - Locations: list the specific test methods and files involved\n   - The duplicated code pattern (show a representative sample)\n   - Suggested refactoring with a concrete before/after example\n   - Estimated impact: how many lines/methods would be simplified\n3. **Refactoring priority** — Rank findings by:\n   - Occurrence count (more occurrences = higher value)\n   - Complexity of the duplicated code (complex setup > simple construction)\n   - Risk (low-risk extractions first)\n4. **Trade-offs** — For each suggestion, note:\n   - What readability is gained\n   - What locality/independence is lost\n   - Whether it's worth it given the occurrence count\n\n## Validation\n\n- [ ] Every finding includes specific file and method locations\n- [ ] Every finding shows the actual duplicated code, not just a description\n- [ ] Every suggestion includes a concrete before/after example\n- [ ] Findings are filtered through the 3+ occurrence threshold\n- [ ] Simple constructors are not flagged\n- [ ] Trade-offs are acknowledged for each suggestion\n- [ ] If tests are clean, the report says so upfront\n\n## Common Pitfalls\n\n| Pitfall | Solution |\n|---------|----------|\n| Flagging AAA structure as duplication | The Arrange-Act-Assert pattern is not boilerplate — flag only when the actual code repeats |\n| Suggesting extraction for 2 occurrences | Wait for 3+ before recommending extraction |\n| Recommending base classes for everything | Prefer composition (helpers, factories) over inheritance |\n| Ignoring the readability cost | Every extraction adds indirection — note the trade-off |\n| Flagging simple `new X()` as boilerplate | Only flag complex construction with multiple parameters or configuration |\n| Recommending DRY at the expense of test isolation | Tests that share mutable state through helpers become coupled — warn about this |",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/exp-test-maintainability"
  ],
  "tags": [
    "dotnet-experimental",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-experimental/skills/exp-test-maintainability/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-experimental/skills/exp-test-maintainability/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}