{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/test-anti-patterns",
  "version": "1.0.1",
  "name": "test-anti-patterns",
  "description": "Audit a test file or suite; produce a severity-ranked diagnostic report. ALWAYS USE for tests that verify nothing, missing/tautological assertions, swallowed/broad exceptions, flaky/order-dependent tests, duplication, or magic values. Polyglot. DO NOT USE for direct edits: writing-mstest-tests owns supplied MSTest assertions/attributes/lifecycle; code-testing-agent owns new tests. Exclude running tests, migration, assertion metrics (assertion-quality), raw .NET coverage collection (run-tests), non-.NET coverage collection/analysis (native tooling), project-wide .NET coverage/CRAP (coverage-analysis), named-target .NET CRAP (crap-score), behavioral/pseudo-mutation gaps (test-gap-analysis), test-mix/ happy-vs-error classification and trait distributions (test-tagging), or the testsmells.org catalog (test-smell-detection).",
  "system_prompt_fragment": "# Test Anti-Pattern Detection\n\nQuick, pragmatic analysis of .NET test code for anti-patterns and quality issues that undermine test reliability, maintainability, and diagnostic value.\n\n## When to Use\n\n- User asks to review test quality or find test smells\n- User wants to know why tests are flaky or unreliable\n- User asks \"are my tests good?\" or \"what's wrong with my tests?\"\n- User requests a test audit or test code review\n- User wants to improve existing test code\n\n## When Not to Use\n\n- User wants to write new tests from scratch (use `writing-mstest-tests`)\n- User wants direct implementation fixes in MSTest code rather than a diagnostic review (use `writing-mstest-tests`)\n- User asks to fix swapped `Assert.AreEqual` argument order (use `writing-mstest-tests`)\n- User asks to convert `DynamicData` from `IEnumerable<object[]>` to `ValueTuple` (use `writing-mstest-tests`)\n- User wants to run or execute tests (use `run-tests`)\n- User wants to migrate between test frameworks or versions (use migration skills)\n- User wants to measure code coverage (out of scope)\n- User wants a deep formal test smell audit with academic taxonomy and extended catalog (use `test-smell-detection`)\n\n## Inputs\n\n| Input | Required | Description |\n|-------|----------|-------------|\n| Test code | Yes | One or more test files or classes to analyze |\n| Production code | No | The code under test, for context on what tests should verify |\n| Specific concern | No | A focused area like \"flakiness\" or \"naming\" to narrow the review |\n\n## Workflow\n\n### Step 1: Gather the test code\n\nRead the test files the user wants reviewed. If the user points to a directory or project, scan for all test files using the framework-specific markers in the `dotnet-test-frameworks` skill (e.g., `[TestClass]`, `[Fact]`, `[Test]`).\n\nIf production code is available, read it too -- this is critical for detecting tests that are coupled to implementation details rather than behavior.\n\n### Step 2: Scan for anti-patterns\n\nCheck each test file against the anti-pattern catalog below. Report findings grouped by severity.\n\n#### Critical -- Tests that give false confidence\n\n| Anti-Pattern | What to Look For |\n|---|---|\n| **No assertions** | Test methods that execute code but never assert anything. A passing test without assertions proves nothing. |\n| **Coverage touching** | Test class that methodically calls every public method on a type — often in alphabetical or declaration order — without asserting meaningful outcomes. Each test typically does `var result = sut.MethodName(...)` with no assertion, or only a trivial `Assert.IsNotNull(result)`. The intent is to inflate code-coverage metrics rather than verify behavior. Distinct from a single assertion-free test: the pattern is *systematic* coverage of the surface area with no real verification. |\n| **Self-referential assertion** | Asserts that the output of an operation equals its input when the operation is expected to be an identity or no-op, e.g. `Assert.AreEqual(input, Parse(input.ToString()))` or `Assert.AreEqual(x, Identity(x))`. The test is tautological — it can only fail if the round-trip is broken, but it never verifies that a *transformation* actually happened. Also catches `Assert.AreEqual(dto.Name, dto.Name)` (asserting a field against itself). |\n| **Swallowed exceptions** | `try { ... } catch { }` or `catch (Exception)` without rethrowing or asserting. Failures are silently hidden. |\n| **Assert in catch block only** | `try { Act(); } catch (Exception ex) { Assert.Fail(ex.Message); }` -- use `Assert.ThrowsException` or equivalent instead. The test passes when no exception is thrown even if the result is wrong. |\n| **Always-true assertions** | `Assert.IsTrue(true)`, `Assert.AreEqual(x, x)`, or conditions that can never fail. |\n| **Commented-out assertions** | Assertions that were disabled but the test still runs, giving the illusion of coverage. |\n\n#### High -- Tests likely to cause pain\n\n| Anti-Pattern | What to Look For |\n|---|---|\n| **Flakiness indicators** | `Thread.Sleep(...)`, `Task.Delay(...)` for synchronization, `DateTime.Now`/`DateTime.UtcNow` without abstraction, `Random` without a seed, environment-dependent paths. |\n| **Test ordering dependency** | Static mutable fields modified across tests, `[TestInitialize]` that doesn't fully reset state, tests that fail when run individually but pass in suite (or vice versa). |\n| **Over-mocking** | More mock setup lines than actual test logic. Verifying exact call sequences on mocks rather than outcomes. Mocking types the test owns. For a deep mock audit, use `exp-mock-usage-analysis`. |\n| **Implementation coupling** | Testing private methods via reflection, asserting on internal state, verifying exact method call counts on collaborators instead of observable behavior. |\n| **Broad exception assertions** | `Assert.ThrowsException<Exception>(...)` instead of the specific exception type. Also: `[ExpectedException(typeof(Exception))]`. |\n\n#### Medium -- Maintainability and clarity issues\n\n| Anti-Pattern | What to Look For |\n|---|---|\n| **Poor naming** | Test names like `Test1`, `TestMethod`, names that don't describe the scenario or expected outcome. Good: `Add_NegativeNumber_ThrowsArgumentException`. |\n| **Magic values** | Unexplained numbers or strings in arrange/assert: `Assert.AreEqual(42, result)` -- what does 42 mean? |\n| **Duplicate tests** | Three or more test methods with near-identical bodies that differ only in a single input value. Should be data-driven (`[DataRow]`, `[Theory]`, `[TestCase]`). For a detailed duplication analysis, use `exp-test-maintainability`. Note: Two tests covering distinct boundary conditions (e.g., zero vs. negative) are NOT duplicates -- separate tests for different edge cases provide clearer failure diagnostics and are a valid practice. |\n| **Giant tests** | Test methods exceeding ~30 lines or testing multiple behaviors at once. Hard to diagnose when they fail. |\n| **Assertion messages that repeat the assertion** | `Assert.AreEqual(expected, actual, \"Expected and actual are not equal\")` adds no information. Messages should describe the business meaning. |\n| **Missing AAA separation** | Arrange, Act, Assert phases are interleaved or indistinguishable. |\n\n#### Low -- Style and hygiene\n\n| Anti-Pattern | What to Look For |\n|---|---|\n| **Unused test infrastructure** | `[TestInitialize]`/`[SetUp]` that does nothing, test helper methods that are never called. |\n| **IDisposable not disposed** | Test creates `HttpClient`, `Stream`, or other disposable objects without `using` or cleanup. |\n| **Console.WriteLine debugging** | Leftover `Console.WriteLine` or `Debug.WriteLine` statements used during test development. |\n| **Inconsistent naming convention** | Mix of naming styles in the same test class (e.g., some use `Method_Scenario_Expected`, others use `ShouldDoSomething`). |\n\n### Step 3: Calibrate severity honestly\n\nBefore reporting, re-check each finding against these severity rules:\n\n- **Critical/High**: Only for issues that cause tests to give false confidence or be unreliable. A test that always passes regardless of correctness is Critical. Flaky shared state is High.\n- **Medium**: Only for issues that actively harm maintainability -- 5+ nearly-identical tests, truly meaningless names like `Test1`.\n- **Low**: Cosmetic naming mismatches, minor style preferences, assertion messages that could be better. When in doubt, rate Low.\n- **Not an issue**: Separate tests for distinct boundary conditions (zero vs. negative vs. null). Explicit per-test setup instead of `[TestInitialize]` (this *improves* isolation). Tests that are short and clear but could theoretically be consolidated.\n\nIMPORTANT: If the tests are well-written, say so clearly up front. Do not inflate severity to justify the review. A review that finds zero Critical/High issues and only minor Low suggestions is a valid and valuable outcome. Lead with what the tests do well.\n\n### Step 4: Report findings\n\nPresent findings in this structure:\n\n1. **Summary** -- Total issues found, broken down by severity (Critical / High / Medium / Low). If tests are well-written, lead with that assessment.\n2. **Critical and High findings** -- List each with:\n   - The anti-pattern name\n   - The specific location (file, method name, line)\n   - A brief explanation of why it's a problem\n   - A concrete fix (show before/after code when helpful)\n3. **Medium and Low findings** -- Summarize in a table unless the user wants full detail\n4. **Positive observations** -- Call out things the tests do well (sealed class, specific exception types, data-driven tests, clear AAA structure, proper use of fakes, good naming). Don't only report negatives.\n\n### Step 5: Prioritize recommendations\n\nIf there are many findings, recommend which to fix first:\n\n1. **Critical** -- Fix immediately, these tests may be giving false confidence\n2. **High** -- Fix soon, these cause flakiness or maintenance burden\n3. **Medium/Low** -- Fix opportunistically during related edits\n\n## Validation\n\n- [ ] Every finding includes a specific location (not just a general warning)\n- [ ] Every Critical/High finding includes a concrete fix\n- [ ] Report covers all categories (assertions, isolation, naming, structure)\n- [ ] Positive observations are included alongside problems\n- [ ] Recommendations are prioritized by severity\n\n## Common Pitfalls\n\n| Pitfall | Solution |\n|---------|----------|\n| Reporting style issues as critical | Naming and formatting are Medium/Low, never Critical |\n| Suggesting rewrites instead of targeted fixes | Show minimal diffs -- change the assertion, not the whole test |\n| Flagging intentional design choices | If `Thread.Sleep` is in an integration test testing actual timing, that's not an anti-pattern. Consider context. |\n| Inventing false positives on clean code | If tests follow best practices, say so. A review finding \"0 Critical, 0 High, 1 Low\" is perfectly valid. Don't inflate findings to justify the review. |\n| Flagging separate boundary tests as duplicates | Two tests for zero and negative inputs test different edge cases. Only flag as duplicates when 3+ tests have truly identical bodies differing by a single value. |\n| Rating cosmetic issues as Medium | Naming mismatches (e.g., method name says `ArgumentException` but asserts `ArgumentOutOfRangeException`) are Low, not Medium -- the test still works correctly. |\n| Ignoring the test framework | xUnit uses `[Fact]`/`[Theory]`, NUnit uses `[Test]`/`[TestCase]`, MSTest uses `[TestMethod]`/`[DataRow]` -- use correct terminology |\n| Missing the forest for the trees | If 80% of tests have no assertions, lead with that systemic issue rather than listing every instance |",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/test-anti-patterns"
  ],
  "tags": [
    "dotnet-test",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-test/skills/test-anti-patterns/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-test/skills/test-anti-patterns/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}