CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/test-step-design-patterns

Pure reference catalog of test-step design patterns at the architecture tier - step granularity (one logical action per step), abstraction layers (mechanical → page → business), step extraction rules (when to inline / when to extract to a helper / when to extract to a Page Object method), the declarative-vs-imperative phrasing rule, FIRST principles (Fast / Independent / Repeatable / Self-validating / Timely), and the AAA / Given-When-Then mapping. This is the cross-framework architecture-tier reference for what a step IS, when it should exist, and where it should live - not file-level AAA style rules and not Gherkin-specific translation. Use when designing or reviewing the step layer of a test framework - for example when writing or reviewing E2E or integration tests, when the step count per test is high, or when refactoring recorded or codegen test output into readable steps.

80

Quality

100%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

step-grouping-and-phrasing.mdreferences/

Step grouping and phrasing

Reference detail for test-step-design-patterns Patterns 5 and 6: how test steps are grouped into phases and how they are phrased. The SKILL.md spine summarizes and links here; this file holds the full tables, guidance, and code.

AAA / Given-When-Then mapping (Pattern 5)

Two equivalent step-grouping vocabularies. Same content, different name conventions.

AAA (xUnit tradition)Given-When-Then (BDD tradition)
ArrangeGiven
ActWhen
AssertThen
(Annotate)(no equivalent; goes in step comments)

Both express the same three phases. The team picks one and uses it consistently. Don't mix: if some tests use AAA comments and others use Given-When-Then helpers, the cognitive cost grows.

When AAA is the right choice

  • xUnit-family frameworks (Jest, pytest, JUnit, NUnit).
  • Tests that aren't user-facing scenarios (data-quality, security, perf).
  • The team prefers comment-anchored phase separation.

When Given-When-Then is the right choice

  • BDD frameworks (Cucumber, SpecFlow, Reqnroll, Behave) where the syntax is enforced.
  • Tests that are user-facing scenarios (E2E, integration).
  • The team wants product stakeholders to read the tests.

The phase-separation rule

Whatever vocabulary the team uses, the phases must be visually separable. The pattern from test-code-conventions §1:

test('places an order', async () => {
  // Arrange
  const customer = await aCustomer().withCartItem('sku-001').build();

  // Act
  const order = await customer.placesOrder();

  // Assert
  expect(order.status).toBe('confirmed');
});

Or, with blank-line separation (no comments needed):

test('places an order', async () => {
  const customer = await aCustomer().withCartItem('sku-001').build();

  const order = await customer.placesOrder();

  expect(order.status).toBe('confirmed');
});

Either works; lacking either fails the §1 AAA review.

Declarative vs imperative step phrasing (Pattern 6)

Even at the business layer, two phrasings exist:

ImperativeDeclarative
"The customer enters the email and password and clicks submit""The customer signs in"
"The customer adds 5 items to the cart and proceeds to checkout""The customer initiates checkout with a 5-item cart"
"The customer is created with role=admin and org_id=42""An admin customer in org 42"

The declarative form is preferred per Cucumber's better-Gherkin guidance (which applies broadly, not just to Gherkin): "scenarios should describe the intended behaviour of the system, not the implementation."

The test: would the wording need to change if the implementation changed? If yes, the step is imperative; rewrite declaratively.

When imperative is correct

  • Tests that test the imperative mechanic (e.g., "the form submits on Enter key press" - Enter is the mechanic under test).
  • Accessibility tests where the keyboard sequence IS the test.

Anti-patterns

Anti-patternWhy it fails
Declarative step that hides a critical mechanic (customer.signsIn() when the test is about SSO redirect)The test no longer tests what it claims to test
Imperative step that re-describes the system internalsBrittle to refactors; the test breaks when the implementation changes for unrelated reasons
Mixing imperative and declarative within one testReader can't tell what abstraction level they're operating at

SKILL.md

tile.json