Prompting for Testing and Documentation

Prompting for Testing and Documentation

GenAI-Powered .NET Β· double-click any text to add a note Β· hover dotted terms for definitions
Tutorial 5 of 27 Intermediate C#

1 Overview

Tests and documentation are the two artifacts developers most consistently under-produce — not because they are hard, but because they are repetitive, and repetition is exactly what language models excel at. A model will happily write the fifteenth boundary test or the twentieth parameter description with the same care as the first. Used well, prompting turns 'we should really have tests for this' into an afternoon's reviewed reality.

Used carelessly, it produces something worse than nothing: test suites that pass while verifying nothing, and documentation that states plausible falsehoods with authority. The difference — as everywhere in this course — is the prompt's context and your review discipline.

This tutorial covers the four skills: generating NUnit unit tests that actually test behavior, systematically eliciting test cases and edge scenarios, generating API documentation from code, and — the load-bearing skill — reviewing generated tests for coverage and correctness so you can trust green.

This is tutorial 5 of 27. It builds on the coding-prompt craft of tutorial 4 (context packs, exemplars, review discipline) and precedes Reliability, Safety & Best Practices in Prompting.

2 Learning Objectives

After completing this tutorial, you will be able to:

  • Write test-generation prompts that produce NUnit suites grounded in the behavior spec, not just the implementation.
  • Elicit test cases and edge scenarios systematically — boundaries, empties, duplicates, and failure paths — before any test code exists.
  • Use parameterized tests to turn boundary tables into compact [TestCase] rows.
  • Generate XML documentation comments, endpoint docs, and READMEs from code, and verify their claims.
  • Review generated tests for the failure modes that matter: assertion-free tests, tautological tests, and bug-enshrining characterization.
  • Apply the break-the-code check (manual mutation testing) to prove a test can actually fail.
  • Read code coverage numbers for what they are — execution evidence, not verification evidence.
  • Keep documentation from drifting by regenerating and reviewing it alongside code changes.

3 Prerequisites

  • Tutorial 4 (Prompt Engineering for Coding Tasks) — context packs, exemplars, standards profiles, and the review bar.
  • Basic familiarity with a .NET test framework (NUnit examples are used; xUnit/MSTest knowledge transfers directly).
  • Comfortable C#: attributes, async, exceptions; ability to run `dotnet test`.
  • Optional but ideal: a real method of your own that lacks tests — the walkthrough lands best on real code.

4 Why Tests and Docs Are Ideal — and Dangerous — GenAI Tasks

Test and documentation generation share three properties that make them model-friendly: the output is highly structured (a unit test has a fixed shape; a parameter description has a fixed shape), the input is complete (the code is right there in the prompt), and the volume is high (many cases, many members — exactly where human patience fails first). This is why test generation is consistently among the highest-satisfaction uses of AI coding tools.

The danger is equally structural. A test is only as good as its test oracle — the source of truth it checks against. If your prompt offers only the implementation, the model derives expectations from the implementation, and the resulting tests verify that the code does what the code does: green today, green after you break it the same way, tautological forever. The fix is supplying the behavior specification — what the method should do, in rules and examples — so expectations derive from intent, not from code.

Documentation inherits the same split. Docs generated purely from code describe mechanism ('sets the flag to true') and can only restate what the code says; docs generated from code plus intent describe purpose ('marks the invoice ready for the nightly export'). And both artifacts share one lifecycle risk — the code changes and they silently don't — which is why generation is never once-and-done but part of the change workflow.

The one-sentence version of this tutorial: give the model the spec, not just the source — and never trust a green test until you have seen it fail.

5 Prompts That Generate NUnit Unit Tests

A production-grade test-generation prompt packs four things: the code under test (the method plus the types it touches), the behavior spec (rules and expected outcomes in plain language — the oracle), an exemplar test (one existing test class, so naming and style match your suite), and the framework constraints (NUnit, one test fixture per class under test, naming convention like MethodName_Scenario_ExpectedResult, arrange-act-assert layout, one behavior per test).

🎬 From method to trustworthy NUnit suite
Watch the four inputs produce tests — and where trust actually gets earned.
Code under test + its types
➜
Behavior spec the oracle
➜
Exemplar test suite style
➜
Generated tests NUnit draft
➜
Run + review earn the green

Structure the ask in two turns for anything non-trivial: first 'list the test cases you would write — name, scenario, expected outcome — as a table; no code yet', then, after you've added the cases it missed and cut the redundant ones, 'now implement exactly this list as NUnit tests'. The case list is reviewable in a minute and turns test generation into spec review — the highest-value minute in this whole workflow.

What good generated NUnit output looks like
[TestFixture]
public class DiscountCalculatorTests
{
    [Test]
    public void Calculate_OrderBelowThreshold_ReturnsZeroDiscount()
    {
        // Arrange
        var calculator = new DiscountCalculator(threshold: 100m, rate: 0.1m);

        // Act
        decimal discount = calculator.Calculate(orderTotal: 99.99m);

        // Assert
        Assert.That(discount, Is.EqualTo(0m));
    }

    [TestCase(-1)]
    [TestCase(-100.50)]
    public void Calculate_NegativeTotal_ThrowsArgumentOutOfRange(decimal total)
    {
        var calculator = new DiscountCalculator(threshold: 100m, rate: 0.1m);

        Assert.Throws<ArgumentOutOfRangeException>(() => calculator.Calculate(total));
    }
}
Demand the naming convention in the prompt — MethodName_Scenario_ExpectedResult — because well-named tests are self-reviewing: a scan of the test names IS the coverage review of scenarios.

6 Generating Test Cases and Edge Scenarios

Models are superb edge-case brainstormers — they have seen every category of input that has ever broken code. The technique is to ask for scenarios before tests, systematically: 'For this method, list test cases in these categories: happy paths, boundary values for every numeric/size rule, empty/null/whitespace inputs, duplicates and ordering, culture and encoding, concurrency if applicable, and failure paths for each dependency. Table format: case name, input, expected outcome.'

Boundary value analysis deserves explicit demand. For every rule with a limit — 'age must be 18 to 65' — the cases are the values on each side of each edge: 17, 18, 65, 66 (plus the extremes: 0, negative, int.MaxValue). Prompted for a boundary table, the model produces it mechanically and completely; unprompted, it samples a few boundaries and moves on. Boundary tables then map one-to-one onto NUnit parameterized tests — one test method, one [TestCase] row per boundary — keeping the suite compact.

Category Prompt cue Example cases for ParseQuantity(string s) → int (1–999)
Happy path typical valid inputs "1" → 1, "500" → 500, "999" → 999
Boundaries each side of every limit "0" → throws, "1" → 1, "999" → 999, "1000" → throws
Empty/null the absent inputs null → throws ArgumentNullException, "" → throws, " " → throws
Format almost-valid shapes "12.5" → throws, "+12" → ?, " 12 " → ? (spec decides!)
Culture/encoding locale-sensitive parses "١٢" (Arabic digits) → spec decides, "1٢" mixed → throws
Hostile adversarial inputs very long string, "999999999999999999999" → throws, not overflows

Notice the two 'spec decides!' cells — this is the second gift of case generation. When the model lists a case and you realize you don't know the expected outcome, you have found a hole in the specification, before the code ships. Feed the answer back into both the spec and the tests. Edge-scenario prompting is requirements discovery wearing a testing costume.

7 Generating API Documentation from Code

Documentation generation runs on the same grounding rule as everything else: code alone yields mechanical descriptions; code plus intent yields useful ones. The workable prompt shape: paste the type or endpoint, state the audience ('consumers of our NuGet package', 'frontend developers calling this API'), supply the intent context the code can't carry (units, invariants, why parameters exist), and fix the format (XML documentation comments, an OpenAPI-style endpoint description, or a README section).

  • XML documentation comments: 'Write /// docs for every public member: summary (purpose, not mechanism), param (meaning, units, valid range), returns, exception (each condition), one example for non-obvious members.' These feed IntelliSense and doc generators, so they compound.
  • Endpoint documentation: paste the controller action plus DTOs; ask for description, parameter table, status codes with conditions, and a realistic request/response example pair. Feed the result into your OpenAPI annotations so rendered docs and code share one source.
  • README sections: give the model the public surface plus two working usage snippets and ask for a quick-start — models write excellent 'first five minutes' docs when given real, runnable examples to build around.
  • Explanatory docs from tests: pasting the test suite alongside the code is a power move — well-named tests are executable examples of intended behavior, and the docs inherit their precision.

Verification for docs means checking claims, not prose quality: every stated default, unit, range, exception condition, and status code must be confirmed against the code or a run. Models fluently document what code plausibly does, including behaviors it doesn't have. And schedule for doc drift: regenerate member docs in the same pull request that changes the member — a diff-sized doc update reviews in seconds, while 'update all the docs someday' never happens.

Never let generated docs state behavior you haven't verified. A wrong code comment misleads one reader; wrong API documentation misleads every consumer, with your authority behind it.

8 Reviewing Generated Tests for Coverage and Correctness

Generated tests fail in ways human tests rarely do, because the model optimizes for looking like a test suite. The review targets three specific pathologies. Assertion-free tests: code runs, nothing meaningful is asserted (or only 'is not null') — coverage rises, verification doesn't. Tautological tests: expectations derived from the implementation, so the test proves the code equals itself — including its bugs. Bug-enshrining characterization: when generating tests for existing code, current wrong behavior gets pinned as 'expected'; that's a feature for refactoring safety but a trap when you believe the suite validates correctness.

🎬 The honesty check: earning trust in a green test
A test earns trust the day you watch it fail. Step through the check.
Green test passes today
➜
Assertion check spec-derived?
➜
Break the code one-line sabotage
➜
Test fails? the moment of truth
➜
Trusted restore & keep

The coverage review is a different axis: not 'are these tests honest' but 'what behavior has no test at all'. Read the test names against your case list from section 6 — every category (boundaries, empties, failure paths) should be represented. A code coverage tool then finds unexecuted branches, but read its number correctly: coverage proves execution, not verification. An assertion-free suite can hit 95% coverage while catching zero bugs. Coverage tells you where tests are missing; only the honesty check tells you whether the existing ones matter.

Efficient review order: (1) scan test names against the case list — spots missing scenarios in seconds; (2) read assertions on the riskiest five tests; (3) sabotage the code twice and demand red. Fifteen minutes, and green becomes meaningful.

9 The .NET Testing and Docs Toolchain Around Your Prompts

  • Test frameworks: NUnit (used here — [TestFixture], [Test], [TestCase], Assert.That), xUnit, MSTest. Every prompt shape in this tutorial transfers; only attributes and assertion syntax change — say which framework in every prompt.
  • Runner: `dotnet test` executes the generated suite; it is the first honesty gate (does it compile, does it pass) and the sabotage-check runner.
  • Coverage: Coverlet + report generators produce the line/branch numbers — the where-are-tests-missing map, never the quality score.
  • GitHub Copilot: generates tests inline from open code (tutorials 8–9); the review discipline here applies unchanged to its output.
  • XML docs pipeline: /// comments → compiler XML file → IntelliSense, DocFX sites, and NuGet package docs — one generated artifact, three surfaces.
  • OpenAPI pipeline: ASP.NET Core annotations + Swashbuckle render endpoint docs; generated endpoint descriptions belong in the annotations so docs live with code.
  • CI: run tests and (optionally) coverage thresholds on every PR — generated tests enter the same gate as human ones.

The toolchain's role mirrors tutorial 4's: prompts produce candidates; deterministic tools judge them. `dotnet test` proves the suite runs; your sabotage check proves it verifies; coverage maps the gaps; CI keeps all three honest over time.

10 The Testing & Docs Task Catalog

Task Prompt shape Non-negotiable ingredient
New method needs tests Two-turn: case list first, then NUnit implementation of the approved list Behavior spec as the oracle
Legacy class, no tests Characterization tests: 'pin current behavior, flag anything that looks like a bug rather than intent' Knowing these prove stability, not correctness
Bug just fixed Regression test: failing input + fixed expectation, named after the bug Test fails on the pre-fix code
Boundary-heavy validation Boundary table → [TestCase] parameterized test Every limit's both sides
Public API surface XML documentation comments per member, purpose-not-mechanism Claims verified against code
HTTP endpoints Description + parameter table + status codes + example pair → OpenAPI annotations Realistic examples that actually run
Package/README Quick-start from public surface + two working snippets Snippets compile and run
PR with code changes Regenerate docs for changed members in the same PR Diff-sized doc review

One composite workflow ties the catalog together: when fixing a bug, generate the regression test first from the bug report (it must fail), fix the code (test goes green), then regenerate the affected member's docs — three generated artifacts, each verified by the one before it.

11 Code Examples in C#

The test-generation prompt template, packing oracle, style, and framework constraints:

Two-turn NUnit test-generation template
public static class TestPrompts
{
    // Turn 1: cases only — reviewed before any code exists.
    public static string BuildCaseListPrompt(string codeUnderTest, string behaviorSpec) => $"""
        List the test cases you would write for the method below.
        Categories: happy paths, boundary values (both sides of every limit),
        empty/null/whitespace, format edge cases, failure paths per dependency.
        Output a table: CaseName | Input | ExpectedOutcome. NO test code yet.

        Behavior specification (source of truth for expectations):
        {behaviorSpec}

        Code under test:
        ```csharp
        {codeUnderTest}
        ```
        """;

    // Turn 2: implement the human-approved list.
    public static string BuildTestImplPrompt(string approvedCases, string exemplarTest) => $"""
        Implement EXACTLY these approved test cases as NUnit tests:
        {approvedCases}

        Rules: [TestFixture] class; MethodName_Scenario_ExpectedResult names;
        arrange-act-assert with comments; one behavior per [Test];
        use [TestCase] rows for the boundary table; Assert.That syntax.
        Match the style of this existing test class:
        ```csharp
        {exemplarTest}
        ```
        """;
}

The documentation prompt for a public member, with intent the code cannot carry:

XML documentation generation prompt
public static string BuildXmlDocPrompt(string publicMembers, string intentNotes) => $"""
    Write XML documentation comments (///) for each public member below.
    - <summary>: the PURPOSE (why callers use it), not the mechanism.
    - <param>: meaning, units, valid range.
    - <returns> and <exception> for every documented throw condition.
    - <example> for any member whose use is not obvious.
    Document ONLY behavior visible in the code or stated in the notes —
    do not invent defaults, limits, or thread-safety claims.

    Intent notes (context the code cannot express):
    {intentNotes}

    Members:
    ```csharp
    {publicMembers}
    ```
    """;
Both templates are plain C# raw string literals (C# 11+), framework-free and version-safe. The 'do not invent' clause in the docs prompt is the anti-hallucination guard — keep it verbatim.

12 Step-by-Step: Test-Covering a Real Method

This walkthrough covers an untested `ParseQuantity(string input) → int` (valid range 1–999) with a trustworthy NUnit suite. Replay it on any method you own.

  1. Write the behavior spec in five lines: valid range 1–999; leading/trailing whitespace tolerated; null/empty/non-numeric throws ArgumentException; out-of-range throws ArgumentOutOfRangeException; no culture-specific digits accepted.
  2. Turn 1 — request the case list (template from section 11): the model returns ~18 cases across the categories. Review it: it missed the mixed-digit case ('1٢') and duplicated two happy paths. Add one, cut one.
  3. Notice the spec holes the list exposed: what about '+12'? The spec didn't say. Decide (reject it), update the spec, add the case — requirements discovery, on schedule.
  4. Turn 2 — request implementation of the approved list with your exemplar test class pasted; receive the [TestFixture] with parameterized boundary rows.
  5. Run `dotnet test`: 17 pass, 1 fails — the whitespace case. Investigate: the test is right, the CODE never trimmed input. You just found a real bug with a generated test; fix the code, not the test.
  6. Honesty check: sabotage the range check (999 → 998) — boundary tests go red ✓; remove the null guard — null test goes red ✓. Restore. The suite has earned its green.
  7. Coverage pass: Coverlet shows the culture-digit branch unexecuted — the model implemented the case but a copy-paste error asserted on the wrong input. One targeted Improve turn fixes it.
  8. Finish the artifact set: generate XML documentation comments for ParseQuantity from the now-verified spec — the docs and tests now tell the same story.
Count what the workflow caught: two case-list defects, one spec hole, one real production bug, one broken test — all before this method ships. That is what reviewed generation buys.

13 Limitations and Caveats

  • Implementation-derived expectations are the root failure: given only code, the model writes tests proving code == code. Always supply a behavior spec; when generating from legacy code deliberately (characterization), label the suite as stability protection, not correctness validation.
  • Assertion quality varies: watch for 'Is.Not.Null' where a value check belongs, and for asserting on the mock instead of the outcome. The sabotage check catches both.
  • Coverage numbers flatter generated suites: high execution with weak assertions is the signature pathology. Coverage locates missing tests; it cannot score existing ones.
  • Async, time, and randomness need explicit prompting: demand deterministic tests — injected clocks, seeded randomness, awaited assertions — or you'll inherit flaky tests that erode suite trust.
  • Mocking depth is a design signal: if generated tests need five mocks per test, the model is faithfully reflecting your coupling — fix the design, don't blame the tests.
  • Docs hallucinate specifics: invented defaults, ranges, and thread-safety claims read exactly like real ones. Verify every stated fact; keep the 'do not invent' clause in doc prompts.
  • Doc drift resumes the moment you stop: regenerate member docs in the same PR that changes the member, or accept that your docs describe a previous version.
API-accuracy disclosure: NUnit attribute and assertion usage shown ([TestFixture], [Test], [TestCase], Assert.That, Assert.Throws) is standard and stable across NUnit 3/4; verify version-specific details against current NUnit documentation. The prompt templates are plain C# (raw string literals, C# 11+) with no version-sensitive APIs.

14 Best Practices and Common Mistakes

Practices that make generated tests and docs trustworthy:

  • Spec before suite: the behavior specification is the test oracle — write it first, even five lines of it.
  • Two turns, always: case list → human review → implementation. The list review is requirements discovery.
  • Demand the boundary table for every limit, and implement it as [TestCase] rows.
  • Sabotage twice before trusting: a test earns green the day you watch it fail.
  • Name tests MethodName_Scenario_ExpectedResult so the name list doubles as the coverage review.
  • Docs state purpose, not mechanism; every claimed default/range/exception is verified; 'do not invent' stays in the prompt.
  • Regenerate docs and add regression tests in the same PR as the change they concern.

Mistakes that produce theatrical green:

  • Generating tests from implementation only — the suite becomes a mirror, bugs included.
  • Accepting a 40-test suite wholesale because it's long — volume is the model's cheapest trick.
  • Reading 95% coverage as 95% verified — coverage measures execution.
  • Keeping a test that never goes red under sabotage 'because it documents intent' — it documents nothing.
  • Letting characterization tests of legacy bugs masquerade as correctness validation.
  • Publishing generated docs without fact-checking the specifics.
  • Treating tests and docs as generation targets once, instead of regeneration targets on every change.

20 Summary & Key Takeaways

  • Tests and docs are the model-friendliest artifacts in development — structured, grounded, voluminous — and the easiest to generate convincingly wrong.
  • The oracle decides everything: supply a behavior spec, or the model derives expectations from the implementation and certifies its bugs.
  • Two turns always: case list → human review (which doubles as requirements discovery) → implementation of the approved list.
  • Demand boundary tables for every limit and implement them as [TestCase] rows; ask for edge scenarios by category, not by vibe.
  • Green is earned, not observed: read assertions against the spec and sabotage the code until the right tests go red.
  • Coverage maps missing tests; it cannot score existing ones — an assertion-free suite posts 95% while verifying nothing.
  • Docs need intent the code can't carry, a 'do not invent' clause, fact-checking of every specific, and regeneration bound to the changing PR.
  • Regression tests must fail before the fix; characterization suites must be labeled for what they are; flakiness constraints go in the prompt.

You can now direct a model to produce the two artifacts teams chronically lack — and, more importantly, you know exactly how to make their green trustworthy. Next, the course turns to reliability and safety in prompting itself: defending against injection, managing failure modes, and building prompting practices that hold up in production.

21 Next Steps

Continue with the next tutorial in the path: Reliability, Safety & Best Practices in Prompting — prompt injection and defenses, failure-mode handling, output validation at scale, and the operational discipline that makes prompting production-grade.

  • Practice: pick one untested method you own and run the full section-12 walkthrough — spec, case list, implementation, sabotage. Count what the workflow catches.
  • Practice: take an existing generated (or inherited) test suite and run the 15-minute review — names, assertions, sabotage, coverage. Report to yourself how much of its green was earned.
  • Practice: generate XML documentation comments for one public class using the intent-notes template, then fact-check every specific claim it made.
  • Practice: on your next bug fix, generate the regression test first and verify it fails before fixing.
  • Reading: the NUnit documentation ([TestCase], Assert.That), Coverlet for coverage, and Stryker.NET if you want automated mutation testing.
Path position: tutorial 5 of 27 · Previous: prompt-engineering-for-coding · Next: prompting-reliability-and-safety

15 Quiz

Pick an answer for each question, then press Check answer. (Notes are disabled in this tab.)

1. Why are testing and documentation especially good fits for model generation?

βœ… Correct!
❌ Not quite β€” the correct answer is .
Tests and docs have fixed shapes, full context, and many near-identical cases — exactly the profile models handle tirelessly. The risk isn't fit; it's trusting output that looks right without the review discipline this tutorial builds.

2. What is a test oracle, and why does it decide the value of generated tests?

βœ… Correct!
❌ Not quite β€” the correct answer is .
If the prompt supplies only code, the model derives 'expected' from what the code does — producing tests that prove code equals itself, bugs included. Supplying the behavior spec as the oracle is the single highest-leverage act in test generation.

3. Which NUnit attribute marks a class containing tests?

βœ… Correct!
❌ Not quite β€” the correct answer is .
NUnit uses [TestFixture] on the class and [Test] on methods. ([TestClass] is MSTest; [Fact] is xUnit.) State the framework in every prompt — the model will otherwise pick one for you.

4. What is the natural NUnit tool for implementing a boundary-value table?

βœ… Correct!
❌ Not quite β€” the correct answer is .
[TestCase(17, ...)], [TestCase(18, ...)] rows run one test body over the whole table — compact, readable, and each row reports independently. Boundary tables map onto parameterized tests one-to-one.

5. Why request a test-case list BEFORE any test code?

βœ… Correct!
❌ Not quite β€” the correct answer is .
The two-turn shape converts test generation into spec review: you approve scenarios before implementation exists, and every case whose expected outcome you can't answer is a requirements gap found before shipping.

6. For the rule 'quantity must be 1–999', which is the correct minimal boundary set?

βœ… Correct!
❌ Not quite β€” the correct answer is .
Boundary value analysis takes both sides of every limit: the last invalid and first valid at the bottom (0, 1), the last valid and first invalid at the top (999, 1000). Testing only inside values misses exactly where off-by-one bugs live.

7. What is an assertion-free test?

βœ… Correct!
❌ Not quite β€” the correct answer is .
It's the signature generated-test pathology: the code runs (coverage rises), but no spec-relevant outcome is checked — or only 'Is.Not.Null'. It passes forever, broken code and all. The sabotage check exposes it instantly.

8. What is a tautological test?

βœ… Correct!
❌ Not quite β€” the correct answer is .
When expectations derive from the code under test rather than the spec, the test can only confirm current behavior — right or wrong. It stays green through the exact bugs it should catch. Root cause: no behavior spec in the prompt.

9. What is the 'break the code' honesty check?

βœ… Correct!
❌ Not quite β€” the correct answer is .
A test earns trust the day you watch it fail. If sabotage leaves everything green, the tests verify nothing regardless of coverage. Mutation testing tools automate this; two manual sabotages catch most generated-suite dishonesty in minutes.

10. A generated suite reaches 95% code coverage. What do you now know?

βœ… Correct!
❌ Not quite β€” the correct answer is .
Coverage measures execution, not verification — an assertion-free suite can post 95% while catching zero bugs. Use coverage to find untested branches; use the honesty check to learn whether the executed tests mean anything.

11. When generating tests for legacy code with no spec, what are you actually creating?

βœ… Correct!
❌ Not quite β€” the correct answer is .
With only the implementation as oracle, the suite locks in what the code does today. That's genuinely valuable for safe refactoring — and dangerous the moment anyone reads its green as 'the code is correct'. Label it; ask the model to flag suspicious behaviors it pinned.

12. What's the key grounding difference between mediocre and useful generated docs?

βœ… Correct!
❌ Not quite β€” the correct answer is .
'Sets the flag to true' is what code-only docs look like. Supplying intent — what it's for, valid ranges, units — produces docs a consumer can act on. Same rule as all coding prompts: the model documents your context, not your mind.

13. The riskiest defect in generated API documentation is:

βœ… Correct!
❌ Not quite β€” the correct answer is .
Hallucinated specifics read identically to real ones and mislead every consumer of the API. Defense: the 'document only visible or stated behavior — do not invent' prompt clause, plus fact-checking every claimed default, range, exception, and status code.

14. What is the best defense against doc drift?

βœ… Correct!
❌ Not quite β€” the correct answer is .
Drift is the default fate of docs maintained on a separate cadence from code. Binding regeneration to the change PR keeps the delta tiny and reviewable, and the docs never describe a previous version of the code.

15. You fixed a bug. What's the correct generated-artifact sequence?

βœ… Correct!
❌ Not quite β€” the correct answer is .
The regression test must fail pre-fix — that failure proves it detects the bug. Green after the fix then means something, and the doc regeneration keeps the trio (code, tests, docs) telling one story. Each artifact verifies the previous one.

16 Exam Questions

Try answering each question yourself before expanding the model answer.

1. Explain why test generation is simultaneously one of the best and one of the riskiest uses of code-generation models.
Best: tests are structured (fixed AAA shape), fully grounded (code in the prompt), and voluminous (many near-identical cases) — the exact profile where models outperform human patience, turning chronic under-testing into reviewable suites. Riskiest: the model optimizes for producing something shaped like a test suite, and the pathologies — assertion-free tests, tautological expectations derived from the implementation, bug-enshrining characterization — all present as green checkmarks and rising coverage. The value therefore depends entirely on two disciplines: supplying a behavior spec as the oracle, and the honesty review (assertion reading plus sabotage) that proves green can turn red.
2. Define 'test oracle' and trace how oracle choice determines whether a generated suite catches or reproduces bugs, with an example.
The oracle is the source of truth expectations derive from. Spec-derived: prompt includes 'discount applies only at or above 100' — the model writes Calculate(99.99) expecting 0; if the code's comparison is >= 99, the test fails and catches the bug. Implementation-derived: prompt includes only the code — the model reads '>= 99', writes Calculate(99.50) expecting a discount, and the test passes, permanently certifying the off-by-one as intended. Identical generation machinery, opposite outcomes, decided entirely by what the prompt offered as truth. This is why 'paste the method, ask for tests' is the most common serious mistake in the whole practice.
3. Specify the complete context pack for an NUnit test-generation prompt and justify each element.
(1) Code under test plus the types it touches — real signatures prevent invented members. (2) Behavior specification — rules and expected outcomes in plain language; the oracle, without which expectations mirror the implementation. (3) One exemplar test class — transmits fixture setup, naming, and assertion style so output drops into the suite without style debt. (4) Framework and convention constraints — NUnit explicitly (else the model picks), MethodName_Scenario_ExpectedResult naming (test names become the coverage review), arrange-act-assert with one behavior per test, [TestCase] for boundary tables, Assert.That syntax. Delivered in two turns: case list first for human review, then implementation of the approved list.
4. Describe the two-turn (case list → implementation) protocol and argue why the intermediate review is 'the highest-value minute in the workflow'.
Turn one requests only a scenario table — name, input, expected outcome — across mandated categories (happy, boundaries, empty/null, format, failure paths). The human edits: adds missed cases, cuts redundant ones, and — crucially — hits cases whose expected outcome the spec doesn't answer. Turn two implements exactly the approved list. The intermediate minute is highest-value because it operates at maximum leverage: a defect fixed in the case list costs one table row; the same defect discovered in implemented tests costs debugging mysteriously wrong assertions; discovered in production, far more. And the 'spec decides?' moments are requirements discovery — holes in the specification surfaced before the code ships, which no amount of post-hoc test review provides.
5. Apply boundary value analysis to 'password length must be 8–64 characters' — derive the full case set and show how it becomes NUnit code.
Boundaries: 7 (last invalid low), 8 (first valid), 64 (last valid), 65 (first invalid high). Extremes and structural cases: 0/empty string, null, whitespace-only at valid length, exactly-8 multibyte/unicode characters (length semantics!), and a very long string (e.g., 10,000) for robustness. The unicode case typically exposes a spec hole: is 'length' chars, bytes, or graphemes? Spec decides. As NUnit: one parameterized method — [TestCase("1234567", false)], [TestCase("12345678", true)], [TestCase(<64 chars>, true)], [TestCase(<65 chars>, false)] — with separate [Test] methods for null (Assert.Throws<ArgumentNullException>) and the unicode decision once the spec answers it.
6. Contrast the three generated-test pathologies — assertion-free, tautological, and bug-enshrining characterization — including detection for each.
Assertion-free: code executes, nothing spec-relevant is asserted (or only Is.Not.Null); coverage rises, verification doesn't. Detect: read assertions; sabotage stays green. Tautological: expectations derived from the implementation; test proves code == code, so present bugs are certified. Detect: compare assertions to the spec rather than the code; sabotage of the mirrored logic stays green because the test agrees with the sabotage's parent logic — spec review is the reliable catch. Bug-enshrining characterization: legacy behavior pinned as expected, including defects; legitimate for refactoring protection, toxic when read as correctness. Detect: label the suite's purpose explicitly and ask the model to flag pinned behaviors that look like bugs rather than intent. All three present as green — which is why green must be earned via the honesty check.
7. Explain manual mutation testing (the sabotage check): protocol, what each outcome means, and its relation to tooling.
Protocol: pick the riskiest behaviors; introduce one deliberate defect at a time in the code under test — flip a comparison, off-by-one a boundary constant, delete a validation line; run the suite; demand that the corresponding tests go red; restore and repeat once or twice elsewhere. Red = those tests genuinely verify that behavior — they've earned their green. All-green under sabotage = the covering tests are decoration (assertion-free or tautological); fix the tests, not the code. This is mutation testing performed by hand; tools (e.g., Stryker.NET) automate it by generating many mutants and scoring the kill rate — worth adopting once suites grow, but two manual sabotages catch most generated-suite dishonesty in minutes and cost nothing.
8. Argue precisely what code coverage does and does not tell you about a generated suite, and derive the correct workflow use of it.
Coverage counts executed lines/branches. It does tell you: where no test reaches at all — unexecuted branches are guaranteed verification gaps, and for generated suites it catches implementation slips (a case implemented against the wrong input leaves its branch cold, as in the walkthrough). It does not tell you: whether executed code was verified — assertions are invisible to it, so an assertion-free suite posts high numbers while catching nothing; the pathology is common in generated suites precisely because volume is cheap. Correct use: coverage as the map of missing tests (investigate every cold branch), the honesty check as the measure of existing tests, and never as a quality score or a CI vanity target on its own.
9. Design the documentation-generation approach for a public NuGet library: inputs, prompt constraints, verification, and pipeline integration.
Inputs: the public surface (signatures with existing partial docs), intent notes per area (units, invariants, valid ranges, why-parameters), and two runnable usage snippets for the quick-start. Constraints: XML documentation comments per member — summary as purpose not mechanism, param with meaning/units/range, returns, exception per condition, example where non-obvious; and the anti-hallucination clause: 'document only behavior visible in code or stated in notes; do not invent defaults, limits, or thread-safety claims'. Verification: fact-check every specific (defaults, ranges, exceptions) against code or a run; compile the snippets. Pipeline: /// comments flow to IntelliSense, DocFX site, and package docs from one source; README quick-start built from the verified snippets; regeneration bound to the PR that changes each member so drift never accumulates.
10. Why is 'docs from code + tests' stronger than 'docs from code alone'? Explain the mechanism.
A well-named test suite is a machine-checked catalog of intended behaviors with concrete examples: Parse_NegativeInput_Throws tells the documenter (human or model) the exception contract; the arrange blocks are realistic usage snippets; the boundary rows enumerate valid ranges exactly. Pasting tests alongside code gives the model spec-grade grounding that the implementation alone cannot provide — the docs inherit the tests' precision about edge behavior instead of hedging or inventing. It also creates consistency pressure: docs, tests, and code drawn from one session tell one story, and a documented claim with no corresponding test is visible as a gap in either artifact. The prerequisite, of course, is that the tests passed the honesty review first.
11. A generated suite of 40 tests all pass on first run. Give a complete 15-minute review plan with the rationale for each step.
(1) Two minutes: scan test names against the case-list categories — happy, boundaries both sides, empty/null, format, failure paths. Missing scenarios are visible instantly when names follow MethodName_Scenario_ExpectedResult. (2) Five minutes: read assertions on the five riskiest tests (money, boundaries, exception contracts) asking 'does this check what the SPEC requires?' — catches Is.Not.Null padding and implementation-mirroring. (3) Five minutes: two sabotage rounds — flip the core comparison, delete a guard — demand red, restore. All-green sabotage fails the suite regardless of its size. (4) Three minutes: run coverage; investigate any cold branch (usually a mis-implemented case). Only after all four does the suite's green mean anything; note the plan never includes 'read all 40' — review targets risk, not volume.
12. How should async code, time, and randomness be handled in generated tests? What must the prompt demand?
These are the flakiness sources, and generated tests inherit them unless the prompt forbids it. Demand determinism explicitly: time via an injected clock abstraction (IClock/TimeProvider) with fixed values in arrange — never DateTime.Now in assertions; randomness seeded or injected; async tests as async Task with awaited results and Assert.ThrowsAsync for exception paths — never .Result/.Wait() (deadlock risk) and never fire-and-forget assertions; no Thread.Sleep for synchronization — use TaskCompletionSource or polling with timeout. If the code under test doesn't expose injection seams for time/randomness, the generated tests will reveal that as untestability — which is design feedback to act on, not to mock around. A flaky generated suite erodes trust in green faster than no suite at all.
13. Explain the regression-test workflow for a bug fix and why the pre-fix failure is non-negotiable.
Sequence: reproduce the bug from the report; generate the regression test from the report's scenario (spec-derived oracle: what SHOULD happen), named after the bug; run it against the unfixed code and require failure; fix the code; the test goes green; regenerate the affected member's docs; commit all three together. The pre-fix failure is the test's only proof of detection power — it demonstrates the test observes the defective behavior and asserts the correct one. A regression test written (or generated) after the fix that never failed might pass for unrelated reasons — wrong input, wrong layer, assertion-free — and its green is unearned. The failure is to a regression test what sabotage is to a general suite: the moment trust is created.
14. Your team adopted AI test generation; coverage rose to 90% but production bugs didn't fall. Diagnose systematically and prescribe.
Diagnosis: the signature symptom of volume-without-oracle generation. Likely findings on audit: prompts supplied implementation only (tautological suites reproducing bugs), assertion-free padding (coverage without verification), and characterization of legacy defects read as validation. Confirm cheaply: sample ten tests, read assertions against specs; run three sabotage rounds — if green survives, verified. Prescribe: (1) mandate the two-turn protocol with a written behavior spec as oracle — even five lines; (2) institute the honesty check in review: a suite ships only after demonstrated red; (3) reframe coverage as gap-map, remove it as a target; (4) adopt mutation testing tooling for a real strength metric; (5) label characterization suites explicitly. Re-measure by escaped-bug rate, not coverage — the metric that was supposed to move.
15. Scenario: an untested OrderTotalCalculator (money, discounts, tax rounding) must be test-covered and documented this sprint using AI assistance. Prescribe the complete workflow.
Day one — spec: write the behavior rules (discount threshold and rate, tax rounding mode — banker's or away-from-zero, currency precision, negative/zero handling); every rule someone can't answer goes to the product owner now. Turn 1: case list across categories — money demands boundary tables at thresholds, rounding cases at half-cents, and culture-safety cases; review, extend, resolve 'spec decides' rows. Turn 2: NUnit implementation with exemplar test pasted; [TestCase] rows for boundaries; injected rounding/culture settings for determinism. Run: investigate failures both ways — walkthrough experience says some will be real code bugs. Honesty: sabotage the threshold comparison and a rounding constant; demand red; restore. Coverage: investigate cold branches. Docs: XML documentation comments from the verified spec plus tests (units, rounding mode, exception contracts — no invented claims), regenerated in this same PR. Exit criteria: sabotage-proven suite, no cold branches unexplained, docs fact-checked, and the spec document committed beside the code as the durable oracle for next time.

17 Flashcards

Click a card to reveal the back.

Why tests/docs suit model generation
Structured output, complete input (code in prompt), high repetitive volume — the exact profile where human patience fails and models don't.
Test oracle
The truth expectations derive from. Spec-derived → catches bugs. Implementation-derived → certifies bugs. Decided entirely by what your prompt supplies.
Test-gen context pack
Code under test + behavior spec (oracle) + exemplar test class + framework/naming constraints (NUnit, MethodName_Scenario_ExpectedResult, AAA).
Two-turn protocol
Turn 1: case list as a table, no code. Human reviews (adds, cuts, resolves spec holes). Turn 2: implement exactly the approved list.
Boundary value analysis
Both sides of every limit: for 1–999 → 0, 1, 999, 1000 (+ null/empty/extremes). Maps 1:1 onto [TestCase] parameterized rows.
'Spec decides!' moments
Cases where you can't state the expected outcome = specification holes found before shipping. Edge-case prompting is requirements discovery.
Assertion-free test
Runs code, asserts nothing meaningful (or Is.Not.Null only). Coverage up, verification zero. Exposed instantly by the sabotage check.
Tautological test
Expectations mirror the implementation → proves code == code, bugs included. Root cause: no behavior spec in the prompt.
Sabotage (honesty) check
Break the code deliberately (flip comparison, remove guard) → the right tests MUST go red → restore. A test earns green the day you watch it fail.
Code coverage — honest reading
Measures execution, not verification. Use as the map of missing tests (cold branches); never as a quality score for existing ones.
Characterization tests
Pin current behavior of legacy code — bugs included. Gold for refactoring safety; poison when read as proof of correctness. Label them.
NUnit essentials
[TestFixture] class, [Test] method, [TestCase(...)] parameterized rows, Assert.That / Assert.Throws. Name the framework in every prompt.
Docs grounding rule
Code alone → mechanism ('sets flag'). Code + intent notes → purpose, units, ranges. Plus the clause: 'do not invent defaults/limits/thread-safety'.
Doc drift defense
Regenerate docs for changed members in the SAME PR — diff-sized review in seconds. Separate doc cadences always rot.
Regression test rule
Generated from the bug report, it must FAIL on pre-fix code — that failure is its proof of detection power. Then fix → green → docs updated together.
15-minute suite review
Scan names vs case list → read assertions on 5 riskiest → sabotage twice, demand red → coverage for cold branches. Target risk, not volume.

18 Interview Questions & Answers

1. How do you use AI for unit testing without ending up with a suite that verifies nothing?
Two disciplines. First, the oracle: I always supply a behavior spec — even five lines of rules — so expectations derive from intent; a prompt with only the implementation produces tests that prove the code equals itself, bugs included. Second, earned green: before trusting a generated suite I read the assertions on the riskiest tests and run the sabotage check — break the code deliberately and demand red. Coverage numbers don't enter into trust at all; they just tell me where tests are missing entirely. With those two habits, generated suites are a massive accelerant; without them, they're green-colored decoration.
2. Walk me through your actual workflow for getting tests generated for a method.
Two turns, never one. Turn one asks for a case list only — a table of scenario, input, expected outcome across mandated categories: happy paths, both sides of every boundary, empty/null/whitespace, format oddities, failure paths per dependency. I review that table: add what's missed, cut duplicates, and — the best part — resolve the cases where I realize the spec doesn't say what should happen. Turn two implements exactly the approved list, with an exemplar test class pasted so style matches our suite, NUnit and naming conventions stated explicitly. Then dotnet test, investigate failures in both directions — sometimes the test is wrong, sometimes it just found a real bug — and the sabotage check before the suite earns its green.
3. What's the most common mistake teams make with AI test generation?
Pasting the method and saying 'write tests for this' — implementation-only prompting. The model has no truth source except the code, so it derives expectations from what the code does: every current bug gets certified as intended behavior, and the suite becomes a mirror that stays green through exactly the defects it should catch. The fix costs five minutes: write the behavior rules down first and put them in the prompt as the source of truth for expectations. Closely behind it: accepting a 40-test suite because it's long and green — volume is the model's cheapest trick, and review must target risk, not count.
4. How do you review a generated test suite efficiently?
Fifteen minutes, four passes, targeting risk over volume. Pass one: scan the test names against my case-list categories — with MethodName_Scenario_ExpectedResult naming, missing scenarios are visible in a scan. Pass two: read the actual assertions on the five riskiest tests, asking whether each checks something the spec requires — this catches Is.Not.Null padding and implementation mirroring. Pass three: two rounds of sabotage — flip the core comparison, delete a guard — run, demand red, restore; if green survives sabotage, the suite fails review no matter how it looks. Pass four: coverage, investigating any cold branch, which for generated suites usually means a case was implemented against the wrong input.
5. Explain boundary value analysis and how you'd get a model to do it properly.
For every rule with a limit, bugs concentrate at the edges, so the cases are both sides of each edge: an 18–65 age rule needs 17, 18, 65, 66 — plus the structural extremes: zero, negative, null, max values. The catch with models is that unprompted they sample a couple of boundaries and move on, so I demand it mechanically: 'produce the boundary table for every numeric, size, and length rule — last invalid, first valid, last valid, first invalid.' The table then maps one-to-one onto NUnit [TestCase] rows — one test body, one row per boundary — so completeness doesn't bloat the suite. And half the value is when the table hits a case the spec doesn't answer: that's a requirements hole found on schedule.
6. What is mutation testing and do you actually use it?
It's the practice of deliberately breaking the code to verify tests notice — the only honest measure of whether a suite verifies anything. I use the manual version constantly: after generating a suite, I flip a comparison or delete a validation line, run the tests, and require red; restore and sabotage somewhere else once more. Two minutes, and it exposes the two classic generated-test pathologies — assertion-free tests and tautological expectations — that coverage numbers are structurally blind to. Tooling like Stryker.NET automates it at scale by scoring the kill rate across many mutants; worth adopting once suites matter, but the manual check is free and catches most of it.
7. How much do you trust code coverage as a metric?
As a map, fully; as a score, not at all. Coverage tells me which branches no test executes — those are guaranteed gaps, and with generated suites a cold branch often reveals a case implemented against the wrong input. What it cannot see is assertions, so an assertion-free suite posts 95% while catching zero bugs — and generated suites hit exactly that pathology because volume is cheap. So my workflow: coverage locates missing tests, the sabotage check measures whether existing tests mean anything, and coverage never appears as a standalone quality target — teams that target the number get the number, via tests that verify nothing.
8. You're asked to generate tests for a legacy class with no documentation. What do you produce and how do you label it?
Characterization tests — and the label matters as much as the tests. With no spec, the only available oracle is current behavior, so the suite pins what the code does today, quirks and all. That's genuinely valuable: it makes refactoring safe by detecting any behavioral change. But it proves stability, not correctness — if there's a lurking bug, the suite now defends it. So I name and document the suite as characterization, ask the model during generation to flag any pinned behavior that looks like a bug rather than intent (that list goes to the product owner), and when a flagged behavior is confirmed wrong, the fix comes with a spec-derived regression test that supersedes the characterization row.
9. How do you keep generated tests from being flaky?
Flakiness comes from time, randomness, and async handled lazily, and generated tests inherit whatever the prompt tolerates — so I forbid it in the prompt: injected clock (TimeProvider/IClock) with fixed values, never DateTime.Now in assertions; seeded or injected randomness; async tests as async Task with awaited assertions and Assert.ThrowsAsync — no .Result, no .Wait(), no Thread.Sleep synchronization. When the generated tests can't comply because the code under test has no seams for time or randomness, that's design feedback — the class is untestable — and I fix the seam rather than mock around it. A flaky suite is worse than a missing one: it trains the team to ignore red.
10. What makes AI-generated documentation good instead of mechanical?
Intent in the prompt. Code-only docs describe mechanism — 'sets the flag to true' — because that's all the code says. I add intent notes the code can't carry: what the member is for, units, valid ranges, invariants, why odd parameters exist. Then constraints: summaries state purpose not mechanism, params get meaning and units, every exception condition documented, examples for anything non-obvious — and the critical clause, 'document only behavior visible in code or stated in these notes; do not invent defaults, limits, or thread-safety claims.' Verification is fact-checking, not proofreading: every specific claim gets confirmed against code or a run, because hallucinated defaults read exactly like real ones and carry your API's authority.
11. How do you stop documentation going stale?
Bind regeneration to the change, not the calendar. Doc drift is the default fate of any docs maintained on a separate cadence — 'update the docs someday' never survives contact with a sprint. So the rule is: the PR that changes a public member regenerates that member's docs in the same diff. The update is then tiny — seconds to review — and the docs never describe a previous version. Same rule powers the pipeline: /// comments feed IntelliSense, DocFX, and package docs from one source, so one regeneration point serves every surface. For endpoint docs, generated descriptions live in the OpenAPI annotations next to the action, same reasoning.
12. A bug was just fixed. What's your artifact protocol?
Regression test first, and it must fail before the fix. I generate the test from the bug report — the spec of what should have happened — named after the bug, run it against the unfixed code, and require red: that failure is the only proof the test actually detects the defect. Then the fix lands, the test goes green, and that green now means something. Same PR regenerates the affected member's docs so code, tests, and documentation tell one story. A regression test that never demonstrably failed is unearned green — it might pass for reasons unrelated to the bug — which is the same principle as the sabotage check applied to a single test.
13. Tests from code, or docs from code — which benefits more from including the other artifact?
Docs benefit enormously from tests. A well-named, honesty-checked test suite is a machine-verified behavior catalog: exception contracts in the Throws tests, valid ranges in the boundary rows, realistic usage in the arrange blocks. Pasting tests alongside code gives the documentation model spec-grade grounding, so edge behavior gets stated precisely instead of hedged or invented. The reverse helps too but weaker — docs can seed a case list — with the caveat that docs are unverified prose while passing tests are checked, so tests feed docs, not docs feed tests, when both exist. And a documented claim with no corresponding test becomes visible as a gap in one artifact or the other, which is its own review value.
14. Your team's coverage went from 40% to 90% with AI generation, but escaped bugs didn't drop. What happened?
The metric moved without the property it proxies — the signature of volume-without-oracle generation. I'd audit a sample: read assertions against specs and run sabotage rounds. Expected findings: implementation-only prompts producing tautological suites that certify existing bugs; assertion-free padding inflating execution counts; legacy characterization read as validation. The fix is process, not effort: mandate a written behavior spec in every generation prompt, require demonstrated red (sabotage) before a suite merges, demote coverage from target to gap-map, and adopt mutation scoring as the real strength metric. Then measure what was supposed to move — escaped bugs — because coverage was never the goal, just the easiest number to inflate.
15. Where do generated tests and docs fit in your definition of done?
Same bar as handwritten, with generation-specific checks added. For code to be done: behavior spec exists (it's the oracle and it outlives the sprint); suite generated via the two-turn protocol and reviewed — names against case list, assertions against spec, sabotage demonstrated red; no unexplained cold branches; flakiness constraints (clock, randomness, async) satisfied. For public surface: XML docs regenerated in the same PR, every specific claim fact-checked, examples runnable. What's deliberately absent: a coverage percentage as a gate — I gate on the honesty checks instead. The model does the typing; the definition of done ensures a human still owns every claim the artifacts make.

19 Glossary

Unit test
An automated test verifying one unit of behavior in isolation; the primary target of test-generation prompts.
NUnit
A mainstream .NET test framework: [TestFixture] classes, [Test] methods, [TestCase] parameterized rows, Assert.That assertions.
Test fixture
The class grouping related tests and shared setup; NUnit's [TestFixture].
Arrange-Act-Assert
The canonical test structure — setup, invocation, verification — demanded explicitly in generation prompts.
Assertion
The verifying line of a test; assertion quality, invisible to coverage, is what separates verification from decoration.
Test case
One input/expected-outcome pair; listed and human-reviewed as a table before implementation in the two-turn protocol.
Parameterized test
One test body run over many [TestCase] rows — the natural implementation of a boundary table.
Edge case
Boundary-of-validity input (empty, null, zero, max, duplicates, unicode) where bugs concentrate; elicited systematically by category.
Boundary value analysis
Deriving cases from both sides of every limit (17/18, 65/66 for 18–65) plus structural extremes.
Test oracle
The truth source expectations derive from; spec-derived oracles catch bugs, implementation-derived oracles certify them.
Behavior specification
Plain-language rules of what code should do — the prompt ingredient that turns generated tests from mirror into judge.
Assertion-free test
A generated-test pathology: executes code, verifies nothing; inflates coverage; exposed by the sabotage check.
Tautological test
Expectations restate the implementation, proving code equals itself — bugs included; caused by implementation-only prompting.
Characterization test
Pins current behavior of legacy code as expected — refactoring protection that must never be mislabeled as correctness proof.
Mutation testing
Verifying tests by breaking code and demanding red — manually (the sabotage check) or via tools that score mutant kill rates.
Code coverage
Executed-line/branch percentage; the map of where tests are missing, structurally blind to whether executed tests verify anything.
Regression test
A test locking in a bug fix; must demonstrably fail on pre-fix code to prove its detection power.
XML documentation comments
C# /// comments feeding IntelliSense, DocFX, and package docs; generated per-member with purpose-not-mechanism summaries.
API documentation
Consumer-facing reference for public surface; generated from code plus intent notes, with every specific claim fact-checked.
OpenAPI
The standard machine-readable HTTP API description; generated endpoint docs belong in its annotations so docs live with code.
Doc drift
Documentation rotting as code evolves; prevented by regenerating changed members' docs in the same PR.
Flaky test
A test that passes or fails nondeterministically (time, randomness, async); forbidden via injected clocks, seeds, and awaited assertions.

πŸ—’ My Notes