Skip to main content

testing-best-practices

Enforce testing best practices when writing, reviewing, or improving tests. Covers Red-Green TDD, property-based testing, real objects over mocks, test quality assessment (assertion density, not just coverage), E2E testing, contract tests, flaky test upgrades, and detection of sabotaged/skipped tests. Use when writing tests, reviewing test quality, fixing flaky tests, or when the user mentions TDD, testing, coverage, mocks, or test quality.

ソース情報

リポジトリ
adewale/testing-best-practices
ソースの最終更新活動
2026年5月21日 11:49
検出された SKILL.md の言語
英語
スター
5
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

ファイルエクスプローラー
7 ファイル

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
testing-best-practices
description
Enforce testing best practices when writing, reviewing, or improving tests. Covers Red-Green TDD, property-based testing, real objects over mocks, test quality assessment (assertion density, not just coverage), E2E testing, contract tests, flaky test upgrades, and detection of sabotaged/skipped tests. Use when writing tests, reviewing test quality, fixing flaky tests, or when the user mentions TDD, testing, coverage, mocks, or test quality.
compatibility
Works with any language. Language-specific guidance in references/.
metadata
{"author":"adewale","version":"0.1"}
# Testing Best Practices ## How to use this skill This skill operates in four modes depending on what's needed: 1. **Write** — Writing new tests using Red-Green TDD 2. **Assess** — Evaluating existing test quality (not just coverage) 3. **Upgrade** — Improving weak, flaky, or low-quality tests 4. **Detect** — Finding tests that are skipped, sabotaged, or faking coverage Before writing or modifying tests, determine the project's primary language and read the matching reference file for framework-specific guidance: - Python: read `references/python.md` - TypeScript/JavaScript: read `references/typescript.md` - Go: read `references/go.md` - Rust: read `references/rust.md` For antipattern detection and fixes, read `references/antipatterns.md`. For deciding which test types to use, read `references/test-types.md`. ## Core principles These apply to every language and every project. ### 1. Red-Green TDD Always follow the Red-Green-Refactor cycle: 1. **Red** — Write a failing test that describes the desired behavior 2. **Green** — Write the minimum code to make the test pass 3. **Refactor** — Clean up while keeping tests green For bug fixes: write the test that reproduces the bug BEFORE fixing it. This test becomes a permanent regression test. ### 2. Test quality over quantity A test suite's value is measured by what bugs it catches, not by coverage percentage or test count. **Assertion density**: Aim for 3+ meaningful assertions per test. Tests with only 1 assertion — especially "not empty" checks — are weak. **Both directions**: Every test should verify what SHOULD be present (positive) AND what SHOULD NOT be present (negative). A sanitizer test must check that safe content survives AND dangerous content is removed. **Coverage as informational, not blocking**: Use coverage to find untested code paths, not as a merge gate. Branch coverage is more valuable than line coverage. ### 3. Real objects over mocks Prefer this hierarchy (best to worst): 1. **Real objects** — in-memory databases, real filesystems with temp dirs 2. **Purpose-built fakes** — implementations of real interfaces with history tracking 3. **Deterministic stubs** — hash-based stubs that produce consistent output 4. **Framework mocks** — `unittest.mock`, `vi.mock()` as last resort When you must use mocks: write mock fidelity tests that verify mock behavior matches the real system. Add contract tests that validate mock assumptions in a real environment. ### 4. Property-based testing Use property-based tests for any function that processes arbitrary input. Every parser and normalizer needs at minimum a "never crashes on arbitrary input" property test. **Key invariant patterns**: | Pattern | When to use | Example | |---------|-------------|---------| | Never crashes | Every parser/normalizer | `parse(arbitrary_string)` doesn't throw | | Roundtrip | Serialization/encoding | `decode(encode(x)) == x` | | Idempotent | Normalization/formatting | `f(f(x)) == f(x)` | | Monotonic | Counting/measuring | `f(x + more) >= f(x)` | | Conservation | Stripping/filtering | Output characters are a subset of input | | Valid-or-absent | Parsing with fallback | Result is valid or `None`, never invalid | **Boundary-first**: Configure generators to yield min/max boundary values before random values. This catches edge cases in the first few iterations. ### 5. E2E testing Every project with user-facing endpoints needs E2E tests. These catch bugs that unit tests fundamentally cannot: wrong routes, broken integration contracts, platform-specific behavior. - Test the golden path first (most common user workflow) - Gate infrastructure-dependent tests behind environment variables - Limit to 5-15 E2E tests (supplement, don't replace unit tests) - For platform-specific runtimes (Cloudflare Workers, Pyodide): E2E against real infrastructure is non-optional ### 6. Documentation-code sync Test that documented features exist and working features are documented. Use the code itself as the source of truth — inspect registries, command lists, hook names — and verify docs match. ### 7. Test the sad path For every happy-path test, write at least one sad-path test: invalid input, missing data, permission denied, network failure. Use boundary values: empty, null, max, min, zero, one-past-max. Pre-build invalid input collections for validation testing: ``` INVALID_EMAILS = ['', 'notanemail', 'user@', '@domain.com'] CONTENT_LENGTHS = {EMPTY: '', MIN: 'a', MAX: 'a' * 280, OVERFLOW: 'a' * 281} ``` ## Assess mode: evaluating test quality When assessing existing tests, check these in order: ### Step 1: Detect sabotage Search for tests being silently disabled. Read `references/antipatterns.md` for the full detection signal list. Key signals: - `@skip`, `@pytest.mark.skip`, `test.skip`, `xit`, `xdescribe` without conditions - `t.Log` / `console.log` / `print` inside conditional blocks (logging not asserting) - Empty test bodies or tests with no assertions - Tests where the only assertion is `!= ""` / `toBeDefined()` / `toBeTruthy()` ### Step 2: Measure assertion density Count assertions per test function. Flag files where the ratio is below 3. Security-critical tests (XSS, auth, injection) with low assertion density are P0 issues. ### Step 3: Check for mock-reality drift Look for mocks that return hardcoded values. Ask: "If the real API changed, would this test notice?" If no, flag it. ### Step 4: Verify test tier integrity - Files in `tests/integration/` that mock all dependencies are unit tests in disguise — flag them - Files in `tests/unit/` that hit the network are integration tests — flag them - E2E tests that mock the system under test are not E2E tests — flag them ### Step 5: Check coverage configuration - Branch coverage (`branch = true`) should be enabled, not just line coverage - Coverage should measure production code only (exclude test files) - Coverage thresholds should be pragmatic (75-90%), not 100% ## Upgrade mode: improving weak tests When upgrading tests, prioritize by risk: 1. **P0**: Security tests with low assertion density or logging-not-asserting 2. **P1**: Tests with only "not empty" assertions — add specific content checks 3. **P2**: Flaky tests — replace `sleep()` with condition-based waiting, network tests behind markers, committed fixtures instead of live data 4. **P3**: Integration tests that mock everything — either rename to unit tests or add real integration tests alongside them ### Upgrading flaky tests | Flake cause | Fix | |-------------|-----| | Time-dependent | Inject a clock or freeze time | | Order-dependent | Reset shared state in setup/teardown | | Network-dependent | Use VCR cassettes or committed fixtures | | Race conditions | Add synchronization or use deterministic alternatives | | Visual/font rendering | Skip in CI, run locally with `animations: 'disabled'` | ## Write mode: generating new tests When writing tests for new code: 1. Determine which test types are needed (read `references/test-types.md`) 2. Read the language-specific reference for framework conventions 3. Follow Red-Green TDD: write the failing test first 4. Use test data builders/factories for setup — express intent, not structure 5. Use domain-specific assertion helpers for readability 6. Include regression test comments linking to the bug/issue being fixed ## Gotchas - `t.Logf` in Go is NOT an assertion — it never fails the test. Use `t.Errorf`. - `expect(x).toBeDefined()` passes for any non-undefined value including errors. Use specific value assertions. - Coverage of 100% with 1 assertion per test is worse than 80% with 5 assertions per test. Assertion density reveals test quality; coverage reveals test quantity. - A mock that returns `{status: 200}` will pass even if the real API returns `{statusCode: 200}`. Mock shape must match reality. - Visual regression tests that pass locally but fail in CI are usually font rendering differences, not real bugs. Skip in CI or use high pixel tolerance. - `@pytest.mark.skip("broken")` without a tracking issue is tech debt that grows silently. Require `skipif(condition)` or delete the test. - Integration tests that patch `http_fetch` are unit tests wearing a costume.
GitHubで見る