---
name: "testing"
description: 'Apply testing strategies including test pyramid, unit/integration/e2e testing, and coverage requirements. Use when writing unit tests, designing integration test suites, implementing end-to-end tests, measuring test coverage, or setting up continuous testing pipelines.'
metadata:
 author: "Frontier"
 version: "1.0.0"
 created: "2025-01-15"
 updated: "2025-01-15"
---

# Testing

> **Purpose**: Language-agnostic testing strategies ensuring code quality and reliability. 
> **Goal**: 80%+ coverage with 70% unit, 20% integration, 10% e2e tests. 
> **Note**: For language-specific examples, see [C# Development](../../languages/csharp/SKILL.md) or [Python Development](../../languages/python/SKILL.md).

---

## When to Use This Skill

- Writing unit tests for new code
- Designing integration test suites
- Implementing end-to-end test automation
- Measuring and improving test coverage
- Setting up continuous testing in CI/CD pipelines

## Prerequisites

- Testing framework installed (pytest, Jest, xUnit, etc.)
- CI/CD pipeline for automated test execution

## Frontier Execution Boundary

Inside a Frontier quality loop or review, use this skill to design and author
tests, not execute suites. Test execution, coverage and adversarial test runs
wait until the loop completes and the owning agent explicitly asks the user.
No answer or a decline means not run. Use `.github/AGENT-PROTOCOL.md` section
1.4 for the consent procedure.

The execution recipes below apply to explicitly approved, separate testing
tasks and CI/release jobs. They do not override the in-loop/review prohibition.
Do not claim pass counts or coverage for tests that were only written or read.

## Rationalization Table

If you find yourself thinking one of these, push back against it. These are the common ways agents and humans skip the discipline.

| Rationalization | Reality |
|-----------------|---------|
| "The change is small, tests are overkill." | Small changes hide the highest-leverage regressions because reviewers under-scrutinize them. Add at least one regression test per behavioral change. |
| "I'll add tests after the feature works." | Tests written after code are shaped to pass current code, not to specify behavior. Write the failing test first; confirm it fails for the expected reason; then code. |
| "This code is too hard to test, so we'll skip it." | "Hard to test" almost always means "badly factored". Refactor for testability instead of waiving the requirement. |
| "Coverage is at 80%, we're fine." | Coverage measures lines, not behavior. Branches, error paths, and boundary inputs can all be untested at 80% line coverage. Inspect what is covered, not just the number. |
| "The test is flaky, just rerun it." | A flaky test is a bug report. Fix the timing, the shared state, or the assertion. Do not retry until green. |
| "This is a refactor, behavior is identical." | Then the existing tests must all stay green without modification. If you had to edit tests to keep them green, the refactor changed behavior. |

## Decision Tree

```
Writing or reviewing tests?
+- New feature/story?
| +- Has acceptance criteria? -> Write e2e test first, then unit tests
| - No criteria? -> Write unit tests for public API surface
+- Bug fix?
| - Write regression test FIRST (red), then fix (green)
+- Refactoring?
| - Ensure existing tests pass -> refactor -> verify green
+- What type of test?
| +- Pure logic, no I/O? -> Unit test (70% of total)
| +- Database/API/file I/O? -> Integration test (20%)
| - Full user workflow? -> E2E test (10%)
- Coverage measured and below 80%?
 - Run: scripts/check-coverage.ps1 -> add tests for uncovered paths
```

## Test Pyramid

```
 /\
 /E2E\ 10% - Few (expensive, slow, brittle)
 /------\
 / Intg \ 20% - More (moderate cost/speed)
 /----------\
 / Unit \ 70% - Many (cheap, fast, reliable)
 /--------------\
```

**Why**: Unit tests catch bugs early, run fast, provide precise feedback. E2E tests validate workflows but are slow and flaky.

---

## Test Coverage

### Coverage Metrics

```
Coverage Types:
 - Line Coverage: % of code lines executed
 - Branch Coverage: % of if/else branches taken
 - Function Coverage: % of functions called
 - Statement Coverage: % of statements executed

Target: 80%+ overall coverage
```

**Coverage Tools by Language:**
- **.NET**: Coverlet, dotCover
- **Python**: coverage.py, pytest-cov
- **Node.js**: Istanbul (nyc), Jest
- **Java**: JaCoCo, Cobertura
- **PHP**: PHPUnit --coverage

### What to Test

**[PASS] Always Test:**
- Business logic and algorithms
- Data transformations
- Validation rules
- Error handling paths
- Edge cases and boundary conditions
- Security-critical code

**[FAIL] Don't Test:**
- Third-party library internals
- Framework code
- Simple getters/setters (unless logic involved)
- Configuration files
- Auto-generated code

---

## Core Rules

### Write Testable Code

**Testable Code Characteristics:**
```
[PASS] Single Responsibility Principle
[PASS] Dependency Injection
[PASS] Pure Functions (no side effects)
[PASS] Small, focused methods
[PASS] Minimal global state
[PASS] Clear interfaces

[FAIL] Tightly coupled code
[FAIL] Hidden dependencies
[FAIL] God classes
[FAIL] Hard-coded dependencies
[FAIL] Static methods everywhere
```

### Test Fixtures

**Setup and Teardown:**
```
class UserServiceTests:
 # Run once before all tests
 beforeAll():
 testDatabase.connect()
 
 # Run before each test
 beforeEach():
 testDatabase.clear()
 seedTestData()
 
 # Run after each test
 afterEach():
 testDatabase.clear()
 
 # Run once after all tests
 afterAll():
 testDatabase.disconnect()
 
 test "getUser returns correct user":
 # Test uses clean database state
 user = service.getUser(1)
 assert user.name == "Test User"
```

### Parameterized Tests

**Data-Driven Testing:**
```
testCases = [
 {input: 0, expected: 0},
 {input: 1, expected: 1},
 {input: -1, expected: -1},
 {input: 100, expected: 100}
]

for each testCase in testCases:
 test "abs({testCase.input}) returns {testCase.expected}":
 result = abs(testCase.input)
 assert result == testCase.expected
```

---

## Anti-Patterns

- **Test After Ship**: Writing tests after code is merged and deployed -> Write tests before or alongside implementation (TDD or test-with)
- **Ice Cream Cone**: Mostly E2E tests with few unit tests (inverted pyramid) -> Follow the test pyramid: 70% unit, 20% integration, 10% E2E
- **Flaky Acceptance**: Tests that pass or fail randomly due to timing or shared state -> Remove timing dependencies, isolate test data, use deterministic fixtures
- **Testing Implementation**: Tests coupled to private methods or internal structure -> Test public behavior and contracts; refactoring should not break tests
- **Assertion-Free Tests**: Tests that execute code but never assert expected outcomes -> Every test MUST have at least one meaningful assertion
- **Shared Mutable State**: Tests that depend on or modify global/shared state -> Reset state in beforeEach/setUp; use isolated test databases or containers
- **Coverage Gaming**: Writing trivial tests to hit coverage numbers without testing real logic -> Focus coverage on business logic, error paths, and edge cases

---

## Testing Frameworks and Resources

Framework options by test level and further reading: [Testing frameworks and resources](references/frameworks-and-resources.md).

---

**See Also**: [Skills.md](../../../../Skills.md) - [AGENTS.md](../../../../AGENTS.md)

**Last Updated**: January 27, 2026

## Scripts

| Script | Purpose | Usage |
|--------|---------|-------|
| [`check-coverage.ps1`](scripts/check-coverage.ps1) | Check test coverage against threshold (80% default) | `./scripts/check-coverage.ps1 [-Threshold 90]` |
| [`check-coverage.sh`](scripts/check-coverage.sh) | Cross-platform coverage checker (bash) | `./scripts/check-coverage.sh --threshold 80` |
| [`check-test-pyramid.ps1`](scripts/check-test-pyramid.ps1) | Verify test distribution matches pyramid ratios | `./scripts/check-test-pyramid.ps1` |
| [`scaffold-playwright.py`](scripts/scaffold-playwright.py) | Generate Playwright e2e test scaffold (TS or Python) | `python scripts/scaffold-playwright.py --lang typescript --url http://localhost:3000` |

## Troubleshooting

| Issue | Solution |
|-------|----------|
| Flaky tests in CI | Remove timing dependencies, use deterministic test data, add retries for known flaky tests |
| Low coverage despite many tests | Focus on branch coverage not just line coverage, test edge cases |
| Integration tests too slow | Use test containers, parallelize test suites, mock external services |

## References

- [Test Type Examples](references/test-type-examples.md)
- [Test Org Ci Pitfalls](references/test-org-ci-pitfalls.md)