Language-agnostic testing principles including TDD, test quality, coverage standards, and test design patterns. Use when writing tests, designing test strategies, or reviewing test quality.
Treat coverage as a diagnostic signal for finding untested areas, not a target — a target gets gamed into trivial tests (Goodhart's Law)
Concentrate tests on critical paths, business logic, and behavior whose regression would matter
Prioritize meaningful assertions over the coverage number; any CI threshold is the project's config, not a quality goal in itself
Test Characteristics
All tests must be:
Independent: No dependencies between tests (see Test Independence Verification for detailed criteria)
Reproducible: Same input always produces same output
Fast: Unit tests < 100ms each, integration tests < 1s each, full suite < 10 minutes
Self-checking: Clear pass/fail without manual verification
Timely: Written close to the code they test
Test Types
Unit Tests
Purpose: Test individual components in isolation
Characteristics:
Test single function, method, or class
Fast execution (milliseconds)
No external dependencies
Mock external services
Majority of your test suite
Integration Tests
Purpose: Test interactions between components
Characteristics:
Test multiple components together
May include database, file system, or APIs
Slower than unit tests
Verify contracts between modules
Smaller portion of test suite
End-to-End (E2E) Tests
Purpose: Test complete workflows from user perspective
Characteristics:
Test entire application stack
Simulate real user interactions
Slowest test type
Fewest in number
Highest confidence level
Test Design Principles
AAA Pattern (Arrange-Act-Assert)
Structure every test in three clear phases:
// Arrange: Setup test data and conditions
user = createTestUser()
validator = createValidator()
// Act: Execute the code under test
result = validator.validate(user)
// Assert: Verify expected outcome
assert(result.isValid == true)
Adaptation: Apply this structure using your language's idioms (methods, functions, procedures)
One Assertion Per Concept
Test one behavior per test case
Multiple assertions OK if testing single concept
Split unrelated assertions into separate tests
Example: prefer returns error when email is invalid over validates user.
Descriptive Test Names
Test names should clearly describe:
What is being tested
Under what conditions
What the expected outcome is
Recommended format: "should [expected behavior] when [condition]"
Examples:
test("should return error when email is invalid")
test("should calculate discount when user is premium")
test("should throw exception when file not found")
Adaptation: Follow your project's naming convention (camelCase, snake_case, describe/it blocks)
Test Independence
Setup and Teardown
Use setup hooks to prepare test environment
Use teardown hooks to clean up resources
Keep setup minimal and focused
Ensure teardown runs even if test fails
Mocking and Test Doubles
When to Use Mocks
Mock external dependencies: APIs, databases, file systems
Mock slow operations: Network calls, heavy computations
Mock unpredictable behavior: Random values, current time
Mock unavailable services: Third-party services
Mocking Principles
Mock at boundaries, not internally
Keep mocks simple and focused
Verify mock expectations when relevant
Wrap external libraries/frameworks behind adapters and mock the adapter
Data Layer Testing
Mock Limitations for Data Layer
Mocks validate call patterns but cannot verify data layer correctness. The following pass through undetected with mock-only testing:
Schema mismatches (table names, column names, data types)
Options for verifying data layer correctness against a real database engine:
Containerized databases for CI environments
In-memory databases for fast feedback (note: dialect differences may mask issues)
Dedicated test databases with seed data
The appropriate approach depends on project environment and CI/CD capabilities.
AI-Generated Code and Schema Awareness
AI-generated data access code has heightened schema hallucination risk
Generated queries may use correct syntax but reference nonexistent schema elements
Mock-based tests pass regardless of schema accuracy
Mitigation: Design Docs should include explicit schema references so that documented schemas can be cross-checked against data access code during review
Test Quality Practices
Keep Tests Active
Fix or delete failing tests: Resolve failures immediately
Remove commented-out tests: Fix them or delete entirely
Keep tests running: Broken tests lose value quickly
Maintain test suite: Refactor tests as needed
Test Helpers and Utilities
Create reusable test data builders
Extract common setup into helper functions
Build test utilities for complex scenarios
Share helpers across test files appropriately
What to Test
Focus on Behavior
Test observable behavior, not implementation:
✓ Good: Test that function returns expected output
✓ Good: Test that correct API endpoint is called
✗ Bad: Test that internal variable was set
✗ Bad: Test order of private method calls