Design Testing Strategy
A reference manual for designing a fit-for-purpose, fit-for-criticality testing strategy.
This skill is decision-oriented, not philosophical: every gate is deterministic (ON when X / OFF when Y), every schema is enforced (field ordering matters), every example is worked end-to-end.
How To Use This Skill
- Read Decision Gates in order (Gate 0 -> Gate 6). Each gate is independent — you may finish with any subset of test types ON.
- Apply Strategic Skip Heuristics to remove ON gates that would yield low ROI for this artifact.
- For each ON gate, fill the Test Matrix Schema (
selected_typesentry) — the field order is load-bearing. - List rejected types in
rejected_typesand deliberate skips indeliberately_skipped. - Produce a Test Cases to Cover markdown bullet list using ISTQB techniques from Case Design Techniques.
- Cross-check against the matching Worked Example (A pure function / B HTTP+DB endpoint / C UI component).
Decision Gates
Apply gates in numeric order. Each gate produces an independent boolean (applies: true|false). Gates do NOT veto each other — a single artifact may have unit + integration + contract + property-based all ON.
Gate Application Algorithm
Criticality Scale (used by Gates 3 and 6):
Test Type Reference
Google Test Size Mapping
Google Test Sizes (Bland) and SWE at Google Ch.11 classify tests by resources (size), independent of scope (paths covered):
A test's type (unit/integration/e2e) and size (small/medium/large) are orthogonal: a small integration test (Testcontainers Postgres in same process via JDBC) is legitimate.
Playwright vs Cypress (UI e2e)
Case Design Techniques
Use ISTQB Foundation Level black-box techniques to derive what to test inside each chosen test type. References: ISTQB BVA white paper, ASTQB black-box techniques.
1. Equivalence Partitioning (EP)
Divide input domain into partitions where the system is expected to behave the same way; ONE test per partition is sufficient.
Worked example — discount(orderTotal: number) -> number:
Four tests cover all partitions. EP alone misses boundaries — combine with BVA.
2. Boundary Value Analysis (BVA)
Bugs cluster at boundaries. For every boundary value B, test B-1, B, B+1 (or for floats, the smallest representable step).
Worked example — same discount function, boundary at 100:
Repeat for boundary at 500: test 499, 500, 501. Total: 6 boundary tests + 4 EP tests = 10 cases.
The B-1 / B / B+1 triplet has the same shape across boundaries (vary input, vary expected output, identical assertion); this is a natural fit for a table-driven test (see sub-section 5 below).
3. Decision Tables
When output depends on combinations of conditions. Each column is a rule.
Worked example — canCheckout(cartHasItems, paymentValid, addressOnFile):
Four tests, one per rule (* = don't care, dropped via merging).
4. State Transition
When behavior depends on history. Identify states, events, and forbidden transitions.
Worked example — Order state machine with states {draft, submitted, paid, shipped, cancelled}:
Cover one test per legal transition + one per forbidden transition (negative path).
5. Table-Driven Tests
When EP, BVA, or decision-table analysis yields 3+ cases with the same shape (same setup, same assertion, only inputs and expected outputs differ — e.g., parsing valid/invalid date formats; computing tax across brackets; routing rules) collapse them into a single table-driven test. The cases become rows in a data table; the test body iterates the rows and runs one assertion per row. References: Dave Cheney, Prefer table-driven tests; Go wiki: TableDrivenTests.
Do NOT force a table when setup, framework calls, or the assertion shape varies substantially across cases. Forced uniformity hides real differences behind a single name and produces obscure failure messages — keep those as separate, individually named tests.
Worked example — six EP+BVA cases for discount(orderTotal) (boundary at 100) collapsed into one table-driven unit test (TS / vitest syntax; the same pattern applies to Go t.Run, JUnit @ParameterizedTest, pytest parametrize):
The name column is mandatory: each row must produce an individually addressable test so failures point to the specific case, not "row 3 of 6". Rows that need a different assertion (e.g., the negative-input case throws) stay as separate tests outside the table.
Dependency Decision
For Gate 2 (Integration) and Gate 3 (Component/E2E), choose dependencies deliberately. The goal is maximum realism that still runs deterministically in CI.
Tradeoff summary: Testcontainers > in-memory fake > mock, but cost goes the same direction. Pick the cheapest level that doesn't lie about the boundary's behavior.
Strategic Skip Heuristics
Explicit "don't bother" rules. Skipping these is not laziness — it is risk-adjusted ROI per ISO/IEC/IEEE 29119 risk-based testing and Risk-Based Testing.
Test Matrix Schema
Every test strategy MUST be expressed as the YAML block below. Field ordering inside each list entry is load-bearing — judges and downstream tools parse the first key as the critical one (rationale / reason / why), and the second key as the categorical one (type / what).
Schema
Worked YAML Example
Field ordering checklist (judges check this verbatim):
test_strategy:artifactBEFORErationaleBEFOREcriticality.selected_types[*]:rationaleBEFOREtypeBEFOREsizeBEFOREframeworkBEFOREdependenciesBEFOREgate.rejected_types[*]:reasonBEFOREtype.deliberately_skipped[*]:whyBEFOREwhat.
Case Listing Schema
After the matrix, produce a flat markdown bullet list of test cases to be implemented. This is separate from the YAML matrix because:
- a. it lists what to test, not how
- b. it links back to acceptance criteria
Format
Where:
typematches one ofselected_types[*].typefrom the matrixdescriptionfollows AAA / Given-When-Then (Dan North BDD) shape — see Bill Wake AAA (2001)AC-Nreferences the acceptance criterion the case verifies (omit if non-AC-bound, e.g., infrastructure smoke)
Worked Example
Sources & Further Reading
These 14 sources back every gate and rule above. When in doubt, consult the source linked at that gate.
- Test Pyramid — Mike Cohn (2009, Succeeding with Agile) + Ham Vocke, The Practical Test Pyramid, martinfowler.com.
- Testing Trophy — Kent C. Dodds (2018), The Testing Trophy and Testing Classifications and Write Tests.
- Google Test Sizes — Mike Bland (2011), Small / Medium / Large; Software Engineering at Google Ch.11; Test Sizes (Google Testing Blog).
- Google Testing on the Toilet — What Makes a Good End-to-End Test, Testing UI Logic - Follow the User, Origins (Mike Bland).
- ISTQB Foundation Level — Black-box techniques: Boundary Value Analysis white paper; ASTQB Black-Box Techniques.
- ISO/IEC/IEEE 29119 — Risk-based test process standard. Wikipedia overview.
- Kent Beck — Test Driven Development: By Example (Addison-Wesley, 2002). Publisher page. ISBN 978-0321146533.
- The Pragmatic Programmer (20th Anniversary Edition) — Hunt & Thomas (2019). pragprog.com.
- AAA pattern — Bill Wake (2001), 3A — Arrange, Act, Assert. Given-When-Then — Dan North, Introducing BDD.
- Property-based testing — Hypothesis: What is property-based testing?; QuickCheck (Haskell), fast-check (TS).
- Contract testing / Consumer-Driven Contracts — Pact docs; Pactflow CDC explainer.
- Testcontainers — testcontainers.com.
- Table-driven tests — Dave Cheney, Prefer table-driven tests; Go wiki: TableDrivenTests.
- Risk-based testing — Risk Management During Test Planning (softwaretestinghelp.com).
Worked Examples
Each example shows:
- a. the artifact and acceptance criteria
- b. gate-by-gate walkthrough
- c.
test_strategyYAML following the schema - d.
Test Cases to Coverlist - e. commentary on rejected types
Example A — Pure Helper Function: formatCurrency(amount: number, code: string): string
Artifact
Acceptance criteria:
- AC-1: USD output uses
$prefix, comma thousands, period decimal, two decimal places. - AC-2: EUR output uses
€prefix, period thousands, comma decimal, two decimal places. - AC-3: Throws
Error("Unknown currency code")for unsupported codes. - AC-4:
amount = 0formats as"$0.00"/"€0,00".
Criticality: LOW (helper used in display only, no money movement here).
Gate Walkthrough
test_strategy YAML
Test Cases to Cover
Why types were rejected: Helper has no boundaries (no integration), no UI (no component/e2e), is internal and library-style (no contract/smoke), and at LOW criticality the cost of additional test types far exceeds the benefit.
Example B — HTTP POST Endpoint with DB and Multi-Consumer: POST /users
Artifact
A user-registration endpoint that:
- Validates request body (email format, password complexity, age >= 13).
- Checks email uniqueness against Postgres.
- Inserts user record (transactional).
- Emits
user.createdevent to Kafka. - Returns
201with{id, email, createdAt}. - Returns
400for invalid input,409for duplicate email.
Consumed by: mobile app (iOS/Android) and web app on independent deploy cadences.
Acceptance criteria:
- AC-1: Valid request returns
201and persists user. - AC-2: Invalid email format returns
400with field-level error. - AC-3: Password not meeting policy returns
400. - AC-4: Duplicate email returns
409. - AC-5: Successful registration emits exactly one
user.createdevent. - AC-6: Response schema is stable for mobile + web consumers.
Criticality: MEDIUM-HIGH (auth surface, identity domain, multi-consumer public API).
Gate Walkthrough
test_strategy YAML
Test Cases to Cover
Why types were rejected: No UI surface (component/e2e belong to consumer apps), bounded input space (property-based ROI low), out-of-scope concerns (load, multi-region) deliberately skipped with rationale.
Example C — UI Form Component: <RegistrationForm /> (web)
Artifact
A React form component:
- Fields: email, password, confirmPassword, age.
- Client-side validation: email format, password >= 8 chars with mixed case + digit, passwords match, age >= 13.
- Submits to
POST /users. - Shows inline field errors and submit-level errors (network, 409 duplicate).
- Disables submit button while pending; re-enables on response.
- WCAG 2.1 AA: labels bound to inputs, errors announced via
aria-live, focus moves to first error on validation failure.
Acceptance criteria:
- AC-1: User can submit a valid form and is navigated to
/welcome. - AC-2: Invalid email shows inline
"Enter a valid email". - AC-3: Mismatched passwords show inline
"Passwords must match". - AC-4: Submit is disabled while request is in flight.
- AC-5: 409 response from server shows
"This email is already registered"at form level. - AC-6: Form is keyboard navigable; focus moves to first error on validation failure.
- AC-7: All inputs have programmatic labels; errors are announced via
aria-live="polite".
Criticality: MEDIUM-HIGH (registration is a critical user-facing path; accessibility is regulated in many jurisdictions).
Gate Walkthrough
test_strategy YAML
Test Cases to Cover
Why types were rejected: This artifact is a UI consumer — its real boundary is the API, which is tested as integration in Example B (provider side). Property-based testing is not justified for bounded UI input handling. Cross-browser legacy and visual-regression are out of scope and explicitly skipped with rationale.
Skill Self-Check
Before declaring a strategy complete, the loading verify:
- All 7 gates evaluated explicitly (ON/OFF + reason).
-
selected_types[*]order isrationale -> type -> size -> framework -> dependencies -> gate. -
rejected_types[*]order isreason -> type. -
deliberately_skipped[*]order iswhy -> what. - Each AC is referenced by at least one test case.
- BVA cases enumerate
B-1,B,B+1for each numeric boundary. - Test sizes (small/medium/large) are assigned per Google Test Sizes.
- Test names contain no "and" (per Skip Heuristic).
- At least one Strategic Skip Heuristic was applied or explicitly considered and overridden with rationale.
If any check fails, revise the strategy before delivering.
Coverage Analysis
Mutation testing and other coverage-analysis methods (used after tests are written to assess test-suite quality) are documented in the companion test-coverage skill.


