Unit Testing
Unit testing validates the smallest individual component of code in isolation. The key constraint is isolation — the unit under test must not depend on external systems (databases, networks, file I/O) during execution.Purpose
Tests serve two roles simultaneously:- Bug detection — catch regressions automatically
- Living documentation — executable specification of intended behavior
CalculateShipping_InternationalOrderOverThreshold_GetsFreeShipping documents a business rule that no wiki page will keep current. Tests break when implementation changes; documentation doesn’t.
Test Doubles
Replace real dependencies with controlled substitutes:
Distinction matters: you’re not testing the email service — you’re testing whether your code calls it correctly.
AAA Pattern
Every test should have three clearly separated sections:Naming Convention
[Method]_[Scenario]_[ExpectedBehavior]
Examples:
ProcessPayment_InsufficientFunds_ThrowsPaymentExceptionWithdraw_ValidAmount_DecreasesBalanceCalculateShipping_InternationalOrderOverThreshold_GetsFreeShipping
Coverage Philosophy
Coverage percentage measures execution, not correctness.TestEverything() with no assertions can hit 80% coverage while catching zero bugs.
Better frame: risk-weighted testing
- Core business logic (payments, auth, calculations) → comprehensive coverage
- Infrastructure and error handling → error case verification
- Configuration and getters/setters → minimal or none
Key Practices
Single-purpose tests — one reason to fail. SplittingProcessOrder into separate tests for success result, order ID generation, and email sends means each failure points to exactly one thing.
Test builders — centralize object construction. OrderBuilder().WithStandardCustomer().WithItem(...).Build() hides irrelevant setup while making the essential conditions visible. One update when domain objects change.
Hermetic tests — no external dependencies. Replace file I/O with in-memory implementations, DBs with test doubles, network calls with mocks. Tests must run anywhere, anytime.
Red-green-refactor (TDD) — write the failing test first. If the test doesn’t fail before the implementation exists, it’s not testing the right thing.
Flaky test quarantine — a test that intermittently fails trains the team to ignore failures. Root causes: timing assumptions, shared state, order dependencies, external services. Quarantine while investigating; don’t let flaky tests block deployments indefinitely.
Synthetic test data — test data should mirror production patterns without containing real customer data. Never use production PII in test environments.
What Not to Test
- Implementation details — test behavior, not internals
- External libraries — trust them; test your usage of them
- Simple getters/setters with no logic
AI Impact
AI coding agents now generate test suites from code context. Key implications:- AI-generated tests need the same review rigor as AI-generated production code (AI Code Review)
- “Self-healing” tests that update when code changes exist but introduce risk — auto-updated tests may silently change what’s being asserted
- AI root cause analysis on test failures is emerging capability
Related Pages
- CI/CD Testing — where unit tests fit in the pipeline (foundation of the testing pyramid)
- Verification Pipeline — AI-specific quality ladder; complements but doesn’t replace unit testing
- AI Code Review — human + automated review layer that includes test quality