# Ears Tests

> Generate parameterized (table-driven) tests from EARS requirement statements — the deterministic transform that turns an SBCE capability spec's `## Requirements` into one parameterized test per requirement group and one labeled row per statement id. Stack-neutral; owns only the EARS→table mapping and the spec↔test trace, and delegates the test syntax to the composed stack skill (JUnit 5, zunit, Playwright). Use whenever turning EARS requirements, acceptance criteria, or an SBCE/`/sbce` spec into tests; whenever you see "When/While/If…then, the … shall …" statements that need covering; or when asked for table-driven, data-driven, or parameterized tests from a requirement group. Triggers on "EARS", "parameterized tests", "table-driven tests", "data-driven tests", "tests from requirements", "tests from the spec", "cover requirement Rn", "generate tests for this capability", "acceptance criteria to tests".

- Skill: `adambien/ears-tests` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add adambien/ears-tests`
- Raw SKILL.md: https://api.skillmd.com/api/skills/adambien/ears-tests/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: AdamBien (https://skillmd.com/u/adambien)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/adambien/ears-tests

---


Turn EARS requirement statements into parameterized tests. The leverage is structural: every
EARS pattern is a `(condition → response)` tuple, and an SBCE requirement **group** already
collects statements that share **one boundary operation** — same arrange/act skeleton, only the
data differs. That is the textbook precondition for parameterization, so the mapping is mechanical.

Own only the **transform and the trace**. The concrete test syntax is the composed stack skill's
call — never name a runner or framework verb here. When the generated tests exercise the running
system, the system-tests skill owns the contract (black box, coordinates as configuration, total
verdict reporting).

## The mapping

- One requirement **group `Rn` → one parameterized test method** (the shared boundary op is the body).
- One **statement `Rn.m` → one row** in the parameter source.
- The **statement id `Rn.m` is the row's display name** — so the spec↔test binding stays per-statement
  and grep-visible, exactly as SBCE requires (a removed id retires its row; a row with no statement is drift).
- The id MUST surface in the **runner-visible case label** — as a string literal or as a symbol whose
  display form is the literal id. An id that appears only in a comment is not a trace.
- The EARS pattern fixes the row's **role and shape** — you do not invent the columns, you read them off the pattern.

## EARS pattern → row shape

The system is always **the BC**. Each pattern decomposes into the arrange/act/assert columns of a row:

| Pattern | Statement shape | Arrange (state) | Act (trigger) | Assert (response) | Role |
|---|---|---|---|---|---|
| Event-driven | `When <trigger>, the BC shall <response>` | — | trigger | response | happy |
| Unwanted-behaviour | `If <trigger>, then the BC shall <response>` | — | bad/invalid trigger | rejection / error | unhappy |
| State-driven | `While <precondition>, the BC shall <response>` | precondition | (implicit) | response | stateful |
| Complex | `While <precondition>, when <trigger>, the BC shall <response>` | precondition | trigger | response | full tuple |
| Optional-feature | `Where <feature>, the BC shall <response>` | feature enabled | trigger | response | gated |
| Ubiquitous | `The BC shall <response>` | — | — | invariant | single case (least parameterizable — a plain test is fine) |

Mixing a happy `When` row and an unhappy `If…then` row in the same group's table is the norm — it is
how one parameterized method covers both the success and the rejection path of one boundary op.

## Deterministic vs. authored

EARS hands you the *enumeration*, not the *data*. Keep the two honestly separated so the generated
test never silently asserts an assumption:

- **Deterministic (generate without judgment):** the method-per-group, the row-per-statement, the
  `Rn.m` row labels (in Java, the generated per-BC trace symbols), which boundary op the body calls,
  the row role from the pattern, and the bijection check (every `Rn.m` ↔ exactly one row).
- **Authored (leave a clearly marked stub for human/LLM):** the concrete input fixture and the
  assertion that expresses `<response>`. EARS abstracts the response in prose ("create and confirm
  an order") and carries no values — do not fabricate them. Emit a failing/`TODO` stub a human or a
  later `/sbce apply` pass fills, so an unwritten assertion shows up red, not green.

## Realization is the stack skill's call

This skill is stack-neutral. The parameter-source mechanism and display-name form belong to the
composed stack skill — read `references/realizations.md` for the per-stack shape (JUnit 5
`@ParameterizedTest` + `@MethodSource`, zunit's table form, Playwright `test.describe` per group).
Java stacks materialize the `Rn.m` ids as a generated per-BC `Requirement` annotation with a nested
`Rn` enum — the label contract is unchanged because the enum's display form is the literal id; web
stacks and the black-box `-st` module keep literal strings. Pick the one the project already uses;
never introduce a second test idiom.

## Composition

- **`/sbce`** owns the spec and the stable `Rn.m` ids — this skill consumes them; it never edits the
  spec or coins ids. During `/sbce apply`, this is how the *spec→code* "a traceable test per `Rn.m`"
  step is realized when a group has ≥2 statements.
- **`/bce`** owns where tests and the BC live; the **stack skill** owns the test syntax and the
  "are you green?" oracle.
- Prefer a parameterized method only when a group has **≥2 statements**; a lone statement is a plain
  test — do not force a one-row table.

