Testing Strategy (Platform & Tooling Agnostic)
Overview
Establish a pragmatic, layered testing strategy that maximises signal, supports incremental
adoption, and ensures failures are diagnosable with minimal noise. Extend functional testing
with architecture testing and public API/contract governance so solutions remain
maintainable over time.
Testing Skills Decision Tree
Use this decision tree to select the appropriate testing skill:
Start Here
│
├── Defining overall testing strategy?
│ └── YES → testing-strategy-agnostic (this skill)
│
├── Using .NET?
│ │
│ ├── YES → testing-strategy-dotnet (for .NET conventions)
│ │ │
│ │ ├── Using .NET Aspire distributed apps?
│ │ │ └── YES → aspire-integration-testing
│ │ │
│ │ └── Need real database/queue/cache in tests?
│ │ └── YES → testcontainers-integration-tests
│ │
│ └── NO → Use this skill with language-specific tooling
│
└── Need architecture boundary testing?
└── YES → architecture-testing
Testing Skills Comparison
| Skill |
Scope |
When to Use |
| testing-strategy-agnostic |
Any stack |
Defining overall strategy, principles |
| testing-strategy-dotnet |
.NET only |
.NET-specific conventions and tooling |
| aspire-integration-testing |
.NET Aspire |
Distributed apps with multiple services |
| testcontainers-integration-tests |
Any stack with containers |
Tests needing real infrastructure |
| architecture-testing |
Any stack |
Enforcing architectural boundaries |
Invocation Flow
For a complete .NET testing implementation:
- testing-strategy-agnostic - Understand principles (if new to testing strategy)
- testing-strategy-dotnet - Apply .NET-specific conventions
- testcontainers-integration-tests OR aspire-integration-testing - For integration tests
- architecture-testing - For boundary enforcement
Do NOT Use This Skill When
- You need .NET-specific tooling guidance (use testing-strategy-dotnet)
- You're implementing Aspire-specific tests (use aspire-integration-testing)
- You need container-based infrastructure for tests (use testcontainers-integration-tests)
When to Use
- Defining or modernising a test strategy.
- Designing CI quality gates (tiers, coverage, architecture constraints, compatibility checks).
- Reviewing changes that impact solution structure, public interfaces, or integration contracts.
- Introducing or tightening E2E/system testing and their operational criteria.
Core Workflow
- Select appropriate testing skill using the decision tree (this skill for strategy, others for implementation)
- Define test tiers: unit tests (isolated, fast), system tests (real wiring, stubbed externals), E2E tests (minimal, high-value)
- Establish architecture testing rules for layering, dependencies, and conventions
- Define contract versioning and public API governance policies
- Configure observability requirements with payload logging constraints
- Create quality gates with appropriate thresholds for each tier
- Document acceptance criteria using provided templates
Core Principles
- Layered test pyramid (Unit → System → E2E).
- Incremental enforcement focused on changed code (and changed contracts/APIs).
- Repeatability and isolation (test-owned state only).
- Strict data safety (never mutate non-test-owned data).
- Observability is testable (diagnosable failures, controlled noise).
- Architecture is enforceable (structure and dependency rules are verified continuously).
- Contracts and public APIs are governed (versioning discipline and compatibility checks).
Test Tiers
Unit Tests
- Validate class- and method-level behaviour.
- Fully isolated from external I/O.
- Deterministic and fast.
System Tests
- Validate component behaviour with real internal wiring.
- Stub/mock external dependencies only.
- Validate functional outcomes and operational diagnosability.
E2E Tests
- Validate end-to-end user journeys.
- Minimal, high-value scenarios.
- Strong isolation and cleanup guarantees.
Architecture Testing (Hard Requirements)
Architecture tests enforce solution structure and prevent architectural drift.
What to enforce
- Layering rules (e.g., UI depends on Application; Application depends on Domain; Domain depends on nothing).
- Allowed dependencies at package/module boundaries.
- No cyclic dependencies between modules.
- Namespace/folder conventions (e.g., vertical slices, bounded contexts, feature folders).
- Forbidden frameworks in the wrong layer (e.g., no persistence types in Domain).
- Test project conventions (naming, colocation, allowed references).
Where to place architecture tests
- Prefer a dedicated test suite (or a dedicated project) that runs as part of PR gates.
- Keep rules small, explicit, and business-aligned (avoid overly abstract purity constraints).
Incremental enforcement
- Apply strict architecture rules to new modules and modified boundaries first.
- Fail fast on new violations, optionally tolerate legacy violations with a tracked baseline until touched.
Contract Versioning & Public Interfaces (Hard Requirements)
Concepts
- Integration contracts: APIs/events/messages shared between components.
- Published library public surface: exported types/members that downstream consumers compile against.
Rules
- Contracts must be versioned explicitly and follow a clear compatibility policy.
- Breaking changes require:
- a new major version (or a new contract version),
- clear migration guidance,
- and a compatibility window where applicable.
- PRs must include:
- contract change notes (what changed, why),
- and evidence of compatibility checks (automated where feasible).
Recommended automated checks
- API compatibility checks comparing current output to a baseline.
- Public API surface snapshots (generated lists) to detect accidental exposure.
- Consumer-driven contract tests where multiple consumers exist (especially for events).
- Schema linting and backward-compat validation for messages (where applicable).
Observability Requirements (System & E2E)
Minimum Telemetry Bar ("Just Enough")
- Correlation / trace identifiers
- Structured logs with operation name and error classification
- Dependency visibility (name, outcome, failure classification)
- Actionable diagnostics without payload noise
Payload Logging Constraints (Hard Rule)
- Full request/response payloads MUST be logged only at
Debug or Trace levels.
Info/Warn/Error/Critical logs MUST NOT include full payloads.
- Payload logging must be redacted and gated behind environment-specific log level configuration.
Noise Controls
- Avoid repeated identical error logs across retries; prefer one summary error with context.
- Successful scenarios should not emit unexpected
Error/Critical logs.
Acceptance Criteria Templates
System Tests
- Functional behaviour validated.
- Failures produce structured logs with correlation IDs and error classification.
- Successful scenarios emit no unexpected
Error/Critical logs (unless explicitly expected).
- Full payloads are absent from
Info+ logs; payload detail appears only with Debug/Trace enabled.
- Where relevant, verifies the component's contract behaviour and version handling.
E2E Tests
- End-to-end traceability (correlation/trace) across the journey.
- Diagnosable failures via logs/traces with controlled noise.
- Never mutates data not created by the test; cleanup is reliable/idempotent.
- Full payloads are absent from
Info+ logs; payload detail appears only with Debug/Trace enabled.
- Where relevant, validates cross-component contract compatibility and version negotiation/fallback.
Review Heuristics
- Lowest-cost tier used for the behaviour being proven.
- Architecture rules: does the change preserve layering and allowed dependencies?
- Contract discipline: is the contract/public interface change intentional, versioned, and compatible?
- Observability: are failures diagnosable without payload dumps?
- Payload discipline: full payloads restricted to
Debug/Trace only.
Minimal Baseline Strategy Template
For small repositories or MVP projects, use this streamlined testing strategy:
Small Repo Testing Strategy
# Testing Strategy: [Project Name]
## Scope
[1-2 sentence project description]
## Test Tiers
### Unit Tests (Required)
- **Coverage target**: 70% on business logic
- **Focus**: Domain models, validators, pure functions
- **Exclusions**: Controllers, database access, external integrations
### Integration Tests (Required for APIs)
- **Coverage**: All public API endpoints
- **Approach**: In-memory database or TestContainers
- **Focus**: Request/response contracts, error handling
### E2E Tests (Optional for MVP)
- **Scope**: Critical user journey only (e.g., signup → core action)
- **Frequency**: Run on merge to main, not on every PR
## Quality Gates
| Gate | Threshold | Enforcement |
| ------------------------ | --------- | ------------------- |
| Unit test pass | 100% | Block merge |
| Coverage (changed files) | 70% | Block merge |
| Integration tests | 100% pass | Block merge |
| E2E tests | 100% pass | Block merge to main |
## Execution
```bash
# Unit tests (fast, run on every commit)
npm test -- --coverage
# Integration tests (run on PR)
npm run test:integration
# E2E tests (run on merge to main)
npm run test:e2e
```
Evidence Template
When documenting test coverage:
## Test Evidence
- Unit tests: [X/Y passing] ([coverage report link])
- Integration tests: [X/Y passing]
- Changed files coverage: [X]%
When to Upgrade from Minimal
Upgrade to full strategy when ANY of these occur:
- Team size exceeds 3 developers
- More than 2 integration points (external APIs, databases)
- Production incidents related to untested scenarios
- Code complexity metrics indicate high cyclomatic complexity
- Contract versioning becomes necessary
Red Flags - STOP
These statements indicate testing strategy anti-patterns:
| Thought |
Reality |
| "We'll add tests later" |
Untested code becomes untestable; test-first is non-negotiable |
| "E2E tests cover everything" |
E2E tests are slow and brittle; use the test pyramid |
| "100% coverage means bug-free" |
Coverage measures execution, not correctness; focus on meaningful assertions |
| "Mocking everything is fine" |
Over-mocking hides integration failures; stub only external dependencies |
| "Architecture tests are overkill" |
Architectural drift is expensive to fix; enforce boundaries continuously |
| "Contract changes don't need tests" |
Breaking changes break consumers; version explicitly and verify compatibility |
1---2name: testing-strategy-agnostic3description: Use when defining, reviewing, or improving a testing strategy in any stack; focuses on layered testing, incremental enforcement, data safety, architecture enforcement, contract versioning, and observability.4---56# Testing Strategy (Platform & Tooling Agnostic)78## Overview910Establish a pragmatic, layered testing strategy that maximises signal, supports incremental11adoption, and ensures failures are diagnosable with minimal noise. Extend functional testing12with **architecture testing** and **public API/contract governance** so solutions remain13maintainable over time.1415## Testing Skills Decision Tree1617Use this decision tree to select the appropriate testing skill:1819```text20Start Here21 │22 ├── Defining overall testing strategy?23 │ └── YES → testing-strategy-agnostic (this skill)24 │25 ├── Using .NET?26 │ │27 │ ├── YES → testing-strategy-dotnet (for .NET conventions)28 │ │ │29 │ │ ├── Using .NET Aspire distributed apps?30 │ │ │ └── YES → aspire-integration-testing31 │ │ │32 │ │ └── Need real database/queue/cache in tests?33 │ │ └── YES → testcontainers-integration-tests34 │ │35 │ └── NO → Use this skill with language-specific tooling36 │37 └── Need architecture boundary testing?38 └── YES → architecture-testing39```4041### Testing Skills Comparison4243| Skill | Scope | When to Use |44| -------------------------------- | ------------------------- | --------------------------------------- |45| **testing-strategy-agnostic** | Any stack | Defining overall strategy, principles |46| testing-strategy-dotnet | .NET only | .NET-specific conventions and tooling |47| aspire-integration-testing | .NET Aspire | Distributed apps with multiple services |48| testcontainers-integration-tests | Any stack with containers | Tests needing real infrastructure |49| architecture-testing | Any stack | Enforcing architectural boundaries |5051### Invocation Flow5253For a complete .NET testing implementation:54551. **testing-strategy-agnostic** - Understand principles (if new to testing strategy)562. **testing-strategy-dotnet** - Apply .NET-specific conventions573. **testcontainers-integration-tests** OR **aspire-integration-testing** - For integration tests584. **architecture-testing** - For boundary enforcement5960### Do NOT Use This Skill When6162- You need .NET-specific tooling guidance (use testing-strategy-dotnet)63- You're implementing Aspire-specific tests (use aspire-integration-testing)64- You need container-based infrastructure for tests (use testcontainers-integration-tests)6566## When to Use6768- Defining or modernising a test strategy.69- Designing CI quality gates (tiers, coverage, architecture constraints, compatibility checks).70- Reviewing changes that impact solution structure, public interfaces, or integration contracts.71- Introducing or tightening E2E/system testing and their operational criteria.7273## Core Workflow74751. Select appropriate testing skill using the decision tree (this skill for strategy, others for implementation)762. Define test tiers: unit tests (isolated, fast), system tests (real wiring, stubbed externals), E2E tests (minimal, high-value)773. Establish architecture testing rules for layering, dependencies, and conventions784. Define contract versioning and public API governance policies795. Configure observability requirements with payload logging constraints806. Create quality gates with appropriate thresholds for each tier817. Document acceptance criteria using provided templates8283## Core Principles8485- **Layered test pyramid** (Unit → System → E2E).86- **Incremental enforcement** focused on changed code (and changed contracts/APIs).87- **Repeatability and isolation** (test-owned state only).88- **Strict data safety** (never mutate non-test-owned data).89- **Observability is testable** (diagnosable failures, controlled noise).90- **Architecture is enforceable** (structure and dependency rules are verified continuously).91- **Contracts and public APIs are governed** (versioning discipline and compatibility checks).9293---9495## Test Tiers9697### Unit Tests9899- Validate class- and method-level behaviour.100- Fully isolated from external I/O.101- Deterministic and fast.102103### System Tests104105- Validate component behaviour with real internal wiring.106- Stub/mock **external dependencies only**.107- Validate functional outcomes and **operational diagnosability**.108109### E2E Tests110111- Validate end-to-end user journeys.112- Minimal, high-value scenarios.113- Strong isolation and cleanup guarantees.114115---116117## Architecture Testing (Hard Requirements)118119Architecture tests enforce solution structure and prevent architectural drift.120121### What to enforce122123- **Layering rules** (e.g., UI depends on Application; Application depends on Domain; Domain depends on nothing).124- **Allowed dependencies** at package/module boundaries.125- **No cyclic dependencies** between modules.126- **Namespace/folder conventions** (e.g., vertical slices, bounded contexts, feature folders).127- **Forbidden frameworks** in the wrong layer (e.g., no persistence types in Domain).128- **Test project conventions** (naming, colocation, allowed references).129130### Where to place architecture tests131132- Prefer a dedicated test suite (or a dedicated project) that runs as part of PR gates.133- Keep rules **small, explicit, and business-aligned** (avoid overly abstract purity constraints).134135### Incremental enforcement136137- Apply strict architecture rules to new modules and modified boundaries first.138- Fail fast on **new violations**, optionally tolerate legacy violations with a tracked baseline until touched.139140---141142## Contract Versioning & Public Interfaces (Hard Requirements)143144### Concepts145146- **Integration contracts**: APIs/events/messages shared between components.147- **Published library public surface**: exported types/members that downstream consumers compile against.148149### Rules150151- Contracts must be **versioned explicitly** and follow a clear compatibility policy.152- Breaking changes require:153 - a new major version (or a new contract version),154 - clear migration guidance,155 - and a compatibility window where applicable.156- PRs must include:157 - contract change notes (what changed, why),158 - and evidence of compatibility checks (automated where feasible).159160### Recommended automated checks161162- **API compatibility checks** comparing current output to a baseline.163- **Public API surface snapshots** (generated lists) to detect accidental exposure.164- **Consumer-driven contract tests** where multiple consumers exist (especially for events).165- **Schema linting** and backward-compat validation for messages (where applicable).166167---168169## Observability Requirements (System & E2E)170171### Minimum Telemetry Bar ("Just Enough")172173- Correlation / trace identifiers174- Structured logs with operation name and error classification175- Dependency visibility (name, outcome, failure classification)176- Actionable diagnostics without payload noise177178### Payload Logging Constraints (Hard Rule)179180- Full request/response payloads MUST be logged **only** at `Debug` or `Trace` levels.181- `Info`/`Warn`/`Error`/`Critical` logs MUST NOT include full payloads.182- Payload logging must be redacted and gated behind environment-specific log level configuration.183184### Noise Controls185186- Avoid repeated identical error logs across retries; prefer one summary error with context.187- Successful scenarios should not emit unexpected `Error`/`Critical` logs.188189---190191## Acceptance Criteria Templates192193### System Tests194195- Functional behaviour validated.196- Failures produce structured logs with correlation IDs and error classification.197- Successful scenarios emit no unexpected `Error`/`Critical` logs (unless explicitly expected).198- Full payloads are absent from `Info`+ logs; payload detail appears only with `Debug`/`Trace` enabled.199- Where relevant, verifies the component's **contract behaviour** and version handling.200201### E2E Tests202203- End-to-end traceability (correlation/trace) across the journey.204- Diagnosable failures via logs/traces with controlled noise.205- Never mutates data not created by the test; cleanup is reliable/idempotent.206- Full payloads are absent from `Info`+ logs; payload detail appears only with `Debug`/`Trace` enabled.207- Where relevant, validates cross-component **contract compatibility** and version negotiation/fallback.208209---210211## Review Heuristics212213- Lowest-cost tier used for the behaviour being proven.214- Architecture rules: does the change preserve layering and allowed dependencies?215- Contract discipline: is the contract/public interface change intentional, versioned, and compatible?216- Observability: are failures diagnosable without payload dumps?217- Payload discipline: full payloads restricted to `Debug`/`Trace` only.218219---220221## Minimal Baseline Strategy Template222223For small repositories or MVP projects, use this streamlined testing strategy:224225### Small Repo Testing Strategy226227````markdown228# Testing Strategy: [Project Name]229230## Scope231232[1-2 sentence project description]233234## Test Tiers235236### Unit Tests (Required)237238- **Coverage target**: 70% on business logic239- **Focus**: Domain models, validators, pure functions240- **Exclusions**: Controllers, database access, external integrations241242### Integration Tests (Required for APIs)243244- **Coverage**: All public API endpoints245- **Approach**: In-memory database or TestContainers246- **Focus**: Request/response contracts, error handling247248### E2E Tests (Optional for MVP)249250- **Scope**: Critical user journey only (e.g., signup → core action)251- **Frequency**: Run on merge to main, not on every PR252253## Quality Gates254255| Gate | Threshold | Enforcement |256| ------------------------ | --------- | ------------------- |257| Unit test pass | 100% | Block merge |258| Coverage (changed files) | 70% | Block merge |259| Integration tests | 100% pass | Block merge |260| E2E tests | 100% pass | Block merge to main |261262## Execution263264```bash265# Unit tests (fast, run on every commit)266npm test -- --coverage267268# Integration tests (run on PR)269npm run test:integration270271# E2E tests (run on merge to main)272npm run test:e2e273```274````275276## Evidence Template277278When documenting test coverage:279280```markdown281## Test Evidence282283- Unit tests: [X/Y passing] ([coverage report link])284- Integration tests: [X/Y passing]285- Changed files coverage: [X]%286```287288### When to Upgrade from Minimal289290Upgrade to full strategy when ANY of these occur:291292- Team size exceeds 3 developers293- More than 2 integration points (external APIs, databases)294- Production incidents related to untested scenarios295- Code complexity metrics indicate high cyclomatic complexity296- Contract versioning becomes necessary297298## Red Flags - STOP299300These statements indicate testing strategy anti-patterns:301302| Thought | Reality |303| ----------------------------------- | ----------------------------------------------------------------------------- |304| "We'll add tests later" | Untested code becomes untestable; test-first is non-negotiable |305| "E2E tests cover everything" | E2E tests are slow and brittle; use the test pyramid |306| "100% coverage means bug-free" | Coverage measures execution, not correctness; focus on meaningful assertions |307| "Mocking everything is fine" | Over-mocking hides integration failures; stub only external dependencies |308| "Architecture tests are overkill" | Architectural drift is expensive to fix; enforce boundaries continuously |309| "Contract changes don't need tests" | Breaking changes break consumers; version explicitly and verify compatibility |