How does parameterizing a step definition stop the glue layer growing one definition per sentence?
answer
- Grow with behaviors, not with sentences
- The generated skeleton is the trap
- Replace the literal that varies
- Register the conversion once, use it everywhere
- Count definitions against scenarios
basics
~20 sWrite one definition per behavior and capture the parts that vary as parameters. Sentences differing only by a value then reuse the same function, so the library grows with the number of behaviors, not sentences.
solid answer
~50 sThe sprawl has a mechanical cause: a new sentence arrives, the runner reports it undefined and prints a skeleton, and pasting that skeleton is easier than looking for the definition that already covers the behavior. The discipline is to search first and, when the only difference is a value, replace the literal with a typed placeholder rather than register a second pattern. Custom parameter types go further: register one transform that turns matched text into a domain object, and every step using that placeholder receives a typed argument for free. Optional and alternate text markers absorb wording variations such as singular versus plural. Keep the definitions thin and push shared work into a helper layer beneath them, so duplication collects in helpers, which are cheap to refactor, instead of in the step library, which is a shared namespace. Watch the definitions-per-scenario ratio as the signal.
code
pseudocode · 12 lines# before: one definition per sentence
step("a teacher schedules Year 9 Maths in Tuesday period 3", ...)
step("a teacher schedules Year 11 Physics in Thursday period 1", ...)
step("a teacher schedules Year 8 Art in Monday period 5", ...)
# after: one definition per behavior
parameterType("course", "[A-Za-z0-9 ]+", function(text) { return courses.byName(text) })
step("a teacher schedules {course} in {word} period {int}",
function(course, day, period) {
planner.schedule(course, day, period)
})go deeper
Be ready to say that one definition should serve many sentences that differ only by a value, and to show a literal replaced by a placeholder. Knowing that the generated skeleton is a starting point, not the finished design, is the main thing.
Explain the mechanics: typed placeholders, a custom parameter type registered once to convert text into a domain object, and optional or alternate text absorbing wording variation. Be able to name the smells of over-parameterizing.
Demonstrate judgement about where duplication should live — thin definitions over a helper layer — and use a concrete signal such as the definitions-per-scenario ratio when arguing for consolidation work.
Own the shared-library question: when a phrase namespace crosses teams it is an interface, so talk about ownership, the blast radius of generalising a pattern, and whether team-local duplication is the cheaper failure.
The glue layer's characteristic failure is growth in the wrong dimension: a library that grows with the number of *sentences written* instead of the number of *behaviors the product has*. **Parameterization** is the main tool against it. ## Why sprawl happens It is not laziness so much as a path of least resistance built into the tooling: 1. Someone writes a new scenario, runs it, and the runner reports the new line as undefined and helpfully prints a skeleton definition. 2. Pasting the skeleton takes seconds; opening the existing library, finding the near-identical definition, and generalising it takes minutes and touches code other scenarios depend on. 3. Repeat that a few hundred times and the arithmetic gets ugly. A timetable-planner suite that had grown this way carried 418 step definitions for 96 scenarios — more than four definitions per scenario, which means almost every line ever written got its own function. After consolidation it held 63, and the suite exercised exactly the same behavior. **The ratio of definitions to scenarios is the cheapest health signal you have**; a mature library trends toward far fewer definitions than scenarios, because scenarios reuse. ## One definition per behavior The rule to state in an interview is that a definition corresponds to a *behavior of the product*, not to a *string of text*. "A course is scheduled into a period" is one behavior. `"Year 9 Maths" into Tuesday period 3` and `"Year 11 Physics" into Thursday period 1` are two instances of it. So the pattern captures course, day and period, and one function serves both — and serves every future row of examples anyone adds without a line of new glue. ## Placeholders and typed parameters - **Basic placeholders** capture a number, a quoted string, or a single word and hand them over already converted, so the function signature reads like a domain operation rather than a parse. - **Custom parameter types** are the step up: you register once that the text `Year 9 Maths` resolves to a `Course` object, and from then on every definition using that placeholder receives a `Course`. The conversion, the lookup, and the "no such course" error message live in one place instead of being re-implemented in twenty definitions. This is also what keeps definitions thin — a definition that begins with five lines of turning text into objects has swallowed a job that belongs in a parameter type. ## Wording variation without extra definitions Alternation and optional-text markers in a step expression let one pattern absorb "a teacher"/"the teacher", singular/plural, and a trailing clause that some sentences carry and others do not. Used in moderation this removes a whole class of duplicate definitions that exist purely because two authors punctuated differently. Used immoderately it produces patterns nobody can read, which is the next hazard. ## Over-parameterizing is a real failure too A pattern of the shape "the user does <anything>" matches everything, which makes it a magnet for ambiguous matches and makes the library impossible to reason about — you can no longer tell from a sentence which function will run. Two smells mark the line: - First, a **catch-all wildcard** where a typed placeholder would do. - Second, **parameter count**: a definition taking five or six arguments has stopped being a sentence and become a function call written in prose, and it usually wants splitting or a table argument instead. A useful reflex is to parameterize the *values* a behavior varies over and to leave the *behavior itself* in the literal text of the pattern. ## Where duplication should go instead Definitions should be one to three lines: convert arguments into a call on a helper or driver, or assert on what a helper returns. When two definitions genuinely need the same twelve lines of setup, that setup moves into the helper layer. - **Duplication in helpers** is ordinary code duplication and can be refactored freely; - **duplication in the step library** is duplication in a shared, text-addressed namespace where any change risks breaking scenarios nobody in the room has read. ## Ownership when the library is shared Once several teams load the same glue, **a phrase becomes an interface**. Adding a placeholder to an existing pattern is a compatible change if the old sentences still match, and a breaking one if they do not — the failure surfaces as an undefined step in someone else's suite. Teams that share a library therefore need a named owner for the phrase set and a habit of grepping the whole corpus before generalising a pattern, exactly as they would before changing a published signature.
- When is registering a second definition better than generalising the existing one?When the two sentences describe different behaviors that happen to look alike. Generalising then produces a pattern with a branch hidden inside it, which is worse than two honest definitions. The test is whether the difference is a value the behavior varies over, or a different behavior wearing similar words.
- What would make you suspect a step definition is over-parameterized?A wildcard that matches any text, a parameter list of five or six arguments, or a pattern you cannot read aloud and predict from. All three mean the definition has stopped representing one behavior; the first also invites ambiguous matches, since a catch-all will collide with any narrower pattern registered later.
- How do you generalise a pattern that several teams' scenarios already match?Treat it as a published interface. Check that every existing sentence still matches the new pattern — adding a placeholder where a literal stood usually breaks them unless the literal is kept as an alternative — and run the whole corpus, not just your own scenarios. A break shows up as an undefined step in a suite you do not own.
saying these in an interview costs you the question
- Pastes the generated skeleton without searching the library
- Rewrites the scenario sentence to fit an existing literal
- Thinks one definition per sentence is the intended design
- Uses a catch-all wildcard as the standard parameter
- Puts text-to-object conversion inside every definition
- Measures glue health by lines of code rather than reuse