|
| 1 | +--- |
| 2 | +name: dotnet-unit-test-coverage |
| 3 | +description: Mandatory unit-test and code-coverage rules for the .NET agent. Use when writing an implementation plan, writing or modifying unit tests, or adding/changing production code in the Core project or the NewRelic.Agent.Extensions project. Apply without being prompted by the user. |
| 4 | +--- |
| 5 | + |
| 6 | +# .NET Agent Unit-Test and Coverage Requirements |
| 7 | + |
| 8 | +These rules are mandatory and apply automatically -- the user does not need to |
| 9 | +ask for tests or for coverage. Honor them when planning work, when writing or |
| 10 | +editing tests, and when adding or changing production code. |
| 11 | + |
| 12 | +## Scope |
| 13 | + |
| 14 | +- **In scope (must be unit-tested):** all production code in the `Core` project |
| 15 | + (`src/Agent/NewRelic/Agent/Core/`) and the `NewRelic.Agent.Extensions` project |
| 16 | + (`src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/`). |
| 17 | +- **Out of scope:** the wrapper projects under |
| 18 | + `src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/*`. They have no unit |
| 19 | + tests by design (covered by the Integration / Unbounded / Container test |
| 20 | + solutions). When adding non-trivial logic to a wrapper, lift it into a helper |
| 21 | + in `NewRelic.Agent.Extensions` and unit-test the helper -- see the wrapper |
| 22 | + guidance in `tests/CLAUDE.md` and the top-level `CLAUDE.md`. |
| 23 | + |
| 24 | +## Rule 1: New testable code gets unit tests in the same change |
| 25 | + |
| 26 | +Adding or changing in-scope production code without accompanying unit tests is |
| 27 | +incomplete work, not a follow-up task. Write the tests as part of the same |
| 28 | +change, unprompted. Use TDD where practical: write the failing test first, see |
| 29 | +it fail, implement, see it pass. |
| 30 | + |
| 31 | +## Rule 2: Aim for 100% code coverage |
| 32 | + |
| 33 | +When writing or modifying unit tests, cover every reachable line and branch of |
| 34 | +the code under test: |
| 35 | + |
| 36 | +- Include error / `catch` paths, early returns, and boundary conditions |
| 37 | + (just-under / just-over a cap, empty / null inputs, first-and-last iterations). |
| 38 | +- The only acceptable uncovered code is a genuinely unreachable defensive guard |
| 39 | + (for example an `IsNaN` check on a value that the parser can never produce a |
| 40 | + NaN for, or a `default:` no input can hit). Call these out explicitly rather |
| 41 | + than contorting tests to fake-cover dead code. |
| 42 | +- Do not pad coverage with assertion-free tests. Every test must verify |
| 43 | + behavior, not merely execute lines. |
| 44 | + |
| 45 | +## When writing implementation plans |
| 46 | + |
| 47 | +Bake these rules into the plan so the implementer applies them without |
| 48 | +re-deriving them: |
| 49 | + |
| 50 | +- In the plan's **Global Constraints** header, state: "All new/changed code in |
| 51 | + `Core` and `NewRelic.Agent.Extensions` must ship with unit tests in the same |
| 52 | + task; target 100% reachable line and branch coverage." |
| 53 | +- Every task that creates or modifies in-scope production code must include |
| 54 | + explicit TDD steps (write failing test -> run/fail -> implement -> run/pass -> |
| 55 | + commit) with the actual test code, not a "write tests for the above" |
| 56 | + placeholder. |
| 57 | +- A task that only touches wrapper code (and cannot have its logic lifted into a |
| 58 | + helper) is the sole exception -- note that it is covered by integration tests |
| 59 | + instead. |
| 60 | + |
| 61 | +## Mechanics |
| 62 | + |
| 63 | +Test layout, frameworks (NUnit + JustMock Lite, interfaces/virtual only), the |
| 64 | +`SolutionDir` build caveat, and the "run against the built DLL for |
| 65 | +`NewRelic.Agent.Extensions.Tests`" caveat are in the top-level `CLAUDE.md` |
| 66 | +("Building and testing from the CLI", "Testing conventions") and |
| 67 | +`tests/CLAUDE.md`. |
| 68 | + |
| 69 | +## Achieving coverage without piercing encapsulation |
| 70 | + |
| 71 | +JustMock Lite mocks only interfaces and virtual members -- no sealed, static, |
| 72 | +or non-virtual mocking -- and `InternalsVisibleTo` is banned in every |
| 73 | +production and test assembly. So when in-scope code is hard to reach from a |
| 74 | +test, do NOT widen visibility or expose internals to the test assembly. Make |
| 75 | +the code coverable by refactoring the production type to expose a proper seam: |
| 76 | +extract an interface, make the member virtual, inject a mockable dependency, |
| 77 | +or lift the logic into a public helper. If a line is only reachable by mocking |
| 78 | +a private/sealed/static member or via `InternalsVisibleTo`, the design -- not |
| 79 | +the test -- is what must change. |
0 commit comments