TDD Rules

Three Laws

  1. No production code without a failing test
  2. Write only enough test to fail
  3. Write only enough code to pass

Tidy-Red-Green-Refactor-Commit

TIDY     → Small structural improvement  → COMMIT (no behavior change)
RED      → Write failing test
GREEN    → Minimal code to pass          → COMMIT (tests green)
REFACTOR → Improve design                → COMMIT (tests still green)

Inspired by Kent Beck's Tidy First?: make the change easy, then make the easy change.

TIDY is an optional pre-step before starting a Red-Green cycle. A tidying is a small, structural-only change that makes the upcoming work easier — guard clauses, extract helper, rename for clarity, reorder for readability, delete dead code. Tidyings never change behavior and always get their own commit (tidy: type).

When to tidy:

TimingGuidance
Tidy FirstDefault. The code you're about to change is hard to read or extend
Tidy AfterYou understand the shape of the change better after implementing
Tidy LaterBatch structural cleanup into a separate branch/MR
NeverCode works, won't change again, and is readable enough

Commit early, commit often:

  • Commit tidyings separately before the RED step (tidy: commit)
  • Commit immediately after GREEN (tests pass for the first time)
  • Commit after each REFACTOR cycle (if tests still pass)
  • Small, atomic commits make rollback and review easier
  • Never commit with failing tests

Tidying Catalog

Small, safe, structural-only changes (from Tidy First?):

TidyingWhat it does
Guard ClausesReplace nested if/match with early returns
Dead CodeDelete unreachable or unused code
Normalize SymmetriesMake similar code use consistent patterns
New Interface, Old ImplementationWrap before replacing internals
Reading OrderReorder declarations top-down
Cohesion OrderGroup related items together
Move Declaration and Initialization TogetherClose the gap between let and first use
Explaining VariablesName intermediate results
Explaining ConstantsReplace magic numbers with named constants
Explicit ParametersPass values instead of relying on ambient state
Chunk StatementsAdd blank lines between logical blocks
Extract HelperPull reusable logic into a function
One PileInline before re-extracting with better structure
Explaining CommentsAdd "why" comments where intent isn't obvious
Delete Redundant CommentsRemove comments that repeat the code

Test Types

TypeScopeSpeedCoverage
UnitSingle function< 100ms90% min
IntegrationMultiple components< 5sCritical paths
E2EFull system< 60sAll Gherkin scenarios

Naming

test_<function>_<scenario>_<expected>

Examples:
- test_encrypt_valid_key_returns_ciphertext
- test_decrypt_wrong_key_fails

Critical Rules

Crypto - Never mock. Test with real crypto:

  • Roundtrip (encrypt/decrypt, sign/verify)
  • Wrong key rejection
  • Tampered data rejection

Gherkin - Every scenario in features/ must have a test.

Coverage - 90% minimum for vauchi-core.

Forbidden

  • Writing code before tests
  • Mocking crypto operations
  • #[ignore] without tracking issue
  • Flaky/non-deterministic tests
  • Hardcoded test secrets

Mocking Strategy

ComponentApproach
CryptoReal (never mock)
NetworkMock transport
StorageIn-memory DB
TimeMockable clock

PR Checklist

  • Structural tidyings in separate tidy: commits (if any)
  • Tests written before code
  • All Gherkin scenarios covered
  • No ignored tests
  • Coverage ≥ 90%
  • Crypto has security tests