Skip to content

Commit 58557d5

Browse files
authored
chore: Add dotnet-unit-test-coverage skill (#3665)
1 parent 0e5cf49 commit 58557d5

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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.

CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ later turn. Instead:
166166

167167
## Testing conventions
168168

169+
- **New/changed code in `Core` and `NewRelic.Agent.Extensions` must ship with
170+
unit tests in the same change (unprompted), targeting 100% reachable
171+
coverage.** Enforced by the `dotnet-unit-test-coverage` skill
172+
(`.claude/skills/`), which also tells plan-writing to bake this in. Wrapper
173+
projects are exempt (see below).
169174
- Unit tests: `tests/Agent/UnitTests/` (NUnit primary, xUnit used in some
170175
places). Mocking is **JustMock Lite** (free tier) — interfaces and virtual
171176
members only. No sealed / static / non-virtual mocking. Design new code

0 commit comments

Comments
 (0)