Test-driven development with red-green-refactor loop. Use when user wants to build features or fix bugs using TDD, mentions "red-green-refactor", wants integration tests, or asks for test-first development.
التثبيت
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
Test-driven development with red-green-refactor loop. Use when user wants to build features or fix bugs using TDD, mentions "red-green-refactor", wants integration tests, or asks for test-first development.
Test-Driven Development
Edict conventions apply at every step
This is a generic TDD loop. The Edict skills override the generic examples below. The SessionStart hook injects csharp, testing, blazor, and surface-config into every session — those skills are authoritative, not this one. Re-read them before each RED and each GREEN, not just at the start of the task.
The five rules most often blown past during TDD — check each on every cycle:
Naming. Never abbreviate. cancellationToken not ct, serviceProvider not sp, exception not ex, logger not log. Applies to test parameters, locals, lambda variables — everywhere. (csharp skill, also enforced by the block-style-violations.ps1 PostToolUse hook.)
Verify over Assert chains. When the GREEN outcome has more than one field, the assertion is a single Verify(...) snapshot — not a stack of Assert.Equal. Use targeted Asserts only for a single behavioural fact (e.g. exception type) or for a semantically load-bearing Guid alongside the Verify. Contract-surface round-trips are the ADR 0007 drift guard. (testing skill.)
Project placement (ADR 0016). Pick the test project deliberately before writing the test:
Mechanism logic, in-memory only → Edict.Core.Tests. No Testcontainers, no Azurite here.
Real-infra conformance, by axis (ADR-0054): streaming scenarios → Edict.Azure.Streaming.Tests / Edict.Kafka.Tests (real broker + reference persistence); persistence scenarios → Edict.Azure.Persistence.Tests / Edict.Postgres.Tests (real store + dumb MemoryStreams); cross-pairing smoke → Edict.Pairing.Tests. All via Testcontainers.
EDICT00x diagnostics → Edict.Analyzers.Tests (assert diagnostic line positions).
Internal framework tests never depend on Edict.Testing — that surface is proven by Sample app tests only.
Comments and AAA. No comments that restate what the code does. No XML doc on internal-only types. No ADR-number citations inline. The // Arrange / // Act / // Assert markers are allowed — they are the one permitted readability convention in test bodies. No section-divider comments inside test files; split into separate files instead.
Determinism — never Task.Delay to force a timing outcome. Scenarios assert, the waiter layer polls (ADR-0068). A conformance scenario asserts an order-independent outcome and drives timers/drains/deactivations through seams (AdvanceClock, ForceDrainViaReminderAsync, the deactivate-and-confirm probe); any polling lives in a *Waiters helper. Task.Delay in a *Scenarios.cs file is a red build — no allowlist. (testing skill states the full doctrine + the four-class wall-clock taxonomy.)
Also: FluentAssertions is banned (commercial licence). Moq is banned for infrastructure boundaries — use real Azurite/Postgres/Kafka via Testcontainers in the provider suites.
Philosophy
Core principle: Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.
Good tests are integration-style: they exercise real code paths through public APIs. They describe what the system does, not how it does it. A good test reads like a specification - "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure.
Bad tests are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior.
DO NOT write all tests first, then all implementation. This is "horizontal slicing" - treating RED as "write all tests" and GREEN as "write all code."
This produces crap tests:
Tests written in bulk test imagined behavior, not actual behavior
You end up testing the shape of things (data structures, function signatures) rather than user-facing behavior
Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine
You outrun your headlights, committing to test structure before understanding the implementation
Correct approach: Vertical slices via tracer bullets. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle. Because you just wrote the code, you know exactly what behavior matters and how to verify it.
When exploring the codebase, use the project's domain glossary so that test names and interface vocabulary match the project's language, and respect ADRs in the area you're touching.
Before writing any code:
Confirm with user what interface changes are needed
Confirm with user which behaviors to test (prioritize)
Identify opportunities for deep modules (small interface, deep implementation)
List the behaviors to test (not implementation steps)
Get user approval on the plan
Ask: "What should the public interface look like? Which behaviors are most important to test?"
You can't test everything. Confirm with the user exactly which behaviors matter most. Focus testing effort on critical paths and complex logic, not every possible edge case.
2. Tracer Bullet
Write ONE test that confirms ONE thing about the system:
RED: Write test for first behavior → test fails
GREEN: Write minimal code to pass → test passes
This is your tracer bullet - proves the path works end-to-end.
3. Incremental Loop
For each remaining behavior:
RED: Write next test → fails
GREEN: Minimal code to pass → passes
Rules:
One test at a time
Only enough code to pass current test
Don't anticipate future tests
Keep tests focused on observable behavior
Asserting GREEN in this repo: see the "Edict conventions apply" section
above — Verify snapshot over Assert chains, no abbreviated names in the
test, AAA markers allowed, ADR 0016 project placement chosen deliberately.
For the command/event contract surface, the round-trip/shape test is
the ADR 0007 Verify drift guard — a renamed or removed property must fail
CI on the snapshot diff. Use fixed, deterministic inputs so the snapshot is
stable and the literal values are themselves the assertion.
Consider what new code reveals about existing code
Run tests after each refactor step
Never refactor while RED. Get to GREEN first.
5. Convention Pass
Apply Edict conventions on:
CSharp
Blazor (if applicable)
Testing
Code comments
File/Folder/Project structure and placement
6. Commit
Once all tests are GREEN and any refactoring is done:
Run the full test suite one final time
Commit directly to main — no feature branches
Commit with a descriptive message referencing the issue, choosing the Conventional-Commit prefix that matches the work: feat: for a consumer-facing capability, fix: for a consumer-facing bug fix, and docs: / chore: / ci: / refactor: / test: for non-shipping work. The prefix is load-bearing: git-cliff builds the GitHub Release notes from it (feat: becomes Features, fix: becomes Fixes, breaking becomes Breaking Changes; docs:, chore:, and every other prefix are dropped). A fix mislabelled feat: lands in the wrong section; a chore mislabelled feat: invents a phantom Feature in the notes. The subject line is printed verbatim, so write it as the release-note line it will become (e.g. fix: stop the dead-letter promoter throwing on an unknown effect kind (#N)).
If the change is breaking, mark it: use feat!:/fix!: and add a BREAKING CHANGE: <what broke> footer, so the release notes surface it in the Breaking section. A change is breaking when it removes, renames, or alters the signature of a public Edict* member — concretely, when the public-surface allow-list in Edict.Architecture.Tests changed by anything other than a pure addition. Adding-only is a normal feat:. If the issue body carries a Breaking: line, honor it rather than re-deriving.
Always include the Co-authored-by trailer: Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7. Close the Issue
Add a comment to the issue summarising what was implemented and the tests added
Close the issue with gh issue close N --repo owner/repo
Checklist Per Cycle
[ ] Test describes behavior, not implementation
[ ] Test uses public interface only
[ ] Test would survive internal refactor
[ ] Code is minimal for this test
[ ] No speculative features added
Edict conventions (every cycle, not just the first):
[ ] Test parameters, locals, lambdas use full words — no ct/sp/ex/log
[ ] GREEN asserted via a Verify snapshot, not an Assert chain (targeted Asserts only for a single behavioural fact, or a load-bearing Guid alongside Verify)
[ ] Contract-surface tests double as the ADR 0007 Verify drift guard
[ ] Test landed in the right project per ADR 0016 (no Azurite in Core.Tests, no mechanism logic in provider suites, no Edict.Testing dependency from framework tests)
[ ] No comments that restate what the code does; no XML doc on internal-only types; no ADR-number citations inline; AAA markers are fine
[ ] No FluentAssertions; no Moq at infrastructure boundaries
[ ] No Task.Delay to force a timing outcome — scenarios assert, the *Waiters layer polls; conformance timers/drains/deactivations driven through seams (ADR-0068)
[ ] If the implementation introduced a tunable (TimeSpan, int, magic string) on the Edict.Core / Edict.Contracts / provider surface, the surface-config skill's five-step ADR-0028 checklist was followed — it is an options property, not a literal in mechanism code