Skip to main content
Back to Blog
executable specificationsBDDliving documentationCI testingspec-driven development

Executable Specifications for Small Product Teams

Greg Ceccarelli
Greg Ceccarelli
·18 min read

A product decision can be perfectly clear at 10:00 and surprisingly ambiguous by the time someone opens the editor. The team agrees that an invite link should expire, sends a few notes in the PRD, and moves on. Two weeks later, the code rejects an old link but doesn't send the warning email, support doesn't know what users should see, and everyone remembers a slightly different version of the agreement.

That gap between intent and the first commit creates more rework than most small teams acknowledge. Conversations, Slack threads, designs, and tickets preserve fragments of the decision, but they rarely give engineers an artifact they can run, review, and keep synchronized with production behavior.

Executable specifications close that gap. They turn a product decision into a readable behavioral contract that can execute against the system, fail when behavior drifts, and remain visible after the meeting disappears from everyone's memory.

Table of Contents

When the Spec Disappears Between the Meeting and the Commit

A seed-stage team usually doesn't lose requirements because people are careless. The team loses them because context moves through too many shapes. A founder explains the customer problem in a call, a product manager summarizes it in a ticket, a designer adds an exception in Figma, and an engineer infers the rest while implementing the happy path.

The invite-link example exposes the problem. “Links expire after a short period” sounds specific until the team asks practical questions. Does the user see an error or a recovery path? Does the owner receive a warning? Does an expired link remain visible in the audit log? What happens when someone opens the same link twice? Prose can contain these answers, but under deadline pressure, people scan for the main action and fill in the edge cases from memory.

Practical rule: If a decision matters enough to change production behavior, capture it in an artifact that can fail.

An executable specification gives the team that artifact. It expresses behavior in terms a product person can challenge, binds those examples to real application code, and produces a result in CI. The document isn't merely a record of what the team hoped to build. It becomes a check on what the system does.

This approach sits close to conversation-driven development, where the conversation remains part of the path from intent to implementation instead of becoming disposable meeting history. The useful distinction is not between “documentation” and “tests.” The useful distinction is between an artifact that can expose disagreement and one that permits it.

A modern office desk workspace with a laptop displaying code, surrounded by coffee and sticky notes.

Written requirements still matter. They explain why a capability exists, define constraints that may not belong in a scenario, and give stakeholders room to reason about the product. They just shouldn't carry the entire burden of precision. A prose requirement can describe intent, while an executable example makes the intended behavior inspectable.

The 1986 IEEE Transactions on Software Engineering paper that described executable specifications as a distinct software specification approach shows that the idea predates modern agile tooling by decades, as summarized in the historical executable-specification reference. Later literature found that the term remained broad and unsettled, which matches what teams experience in practice. The label matters less than the discipline, preserve intent in a form people can review and the build can verify.

What Executable Specifications Actually Are

An executable specification is a human-readable description of expected behavior that runs against software. In behavior-driven development, teams commonly express that behavior through examples using Given, When, Then:

  • Given establishes the relevant state.
  • When describes the user or system action.
  • Then states the observable outcome.

The format can also use examples tables or a domain-specific language. The syntax isn't the defining feature. The defining feature is the connection between shared language, executable steps, and a pass/fail result.

A diagram explaining executable specifications as human-readable, automated tests, and living documentation for software development teams.

The three parts that make a spec executable

A useful implementation has three coupled elements:

  1. A prose requirement that states the product intent and context.
  2. A functional model that makes the rules precise enough to inspect.
  3. Test cases that execute examples against the implementation.

Literature on model-based development describes how this structure supports earlier and more thorough testing, automatic document generation, and even code generation, as discussed in this model-based executable specification research. The practical value comes from using the same rule at multiple points. The team can examine it for completeness and well-definedness before implementation, then run it against the delivered behavior.

“Executable” adds pressure that prose alone can't provide. A malformed step fails to parse. An undefined step exposes missing implementation work. A changed behavior breaks the scenario instead of leaving an outdated paragraph in a document. The spec survives the meeting because the build keeps asking whether the code still satisfies it.

What it isn't

An executable specification isn't a replacement for every other testing layer.

  • Unit tests isolate functions and classes, making local logic fast to validate.
  • Contract tests check agreements between services or consumers and providers.
  • Static type checks catch invalid shapes and certain classes of misuse before runtime.
  • Executable specifications describe behavior at a level that product and engineering can review together.

A scenario might call an HTTP endpoint, create a user through a test fixture, or exercise a domain service. It should verify an outcome that matters to the product, not merely prove that a private method was called. The best suite gives the team a readable map of behavior while leaving lower-level tests to cover implementation detail.

In practice, the specification often acts as both acceptance test and regression test. The same artifact checks whether a feature is complete and whether a later change has broken it, avoiding a separate translation from requirement to test. That dual role is why teams should treat scenario wording as production-quality interface design, not as disposable QA text.

The Three Patterns Teams Use Most

Small teams usually encounter executable specifications through three overlapping patterns. Behavior-driven development emphasizes collaboration and a shared vocabulary, often using Given-When-Then scenarios. Specification by example starts with concrete examples and tables that expose business rules. Living documentation publishes runnable behavior in a form that remains accessible beyond the engineering repository.

They overlap because each turns intent into examples that can run. They diverge in audience, ceremony, and how much structure the team wants around discovery and publication.

DimensionBDDSpecification by ExampleLiving Documentation
Primary focusShared behavior and ubiquitous languageRules made concrete through examples and dataAccessible, continuously updated product behavior
Typical formatGiven-When-Then scenariosExamples tables, rules, and focused casesGenerated pages, scenarios, and capability views
Best audienceProduct, design, engineering, QADomain experts and engineersEngineers, product, support, and other stakeholders
Main strengthSurfaces misunderstandings before codingHandles edge cases and decision logic clearlyKeeps system behavior discoverable after delivery
Main riskScenarios become procedural scriptsTables become exhaustive and difficult to maintainPublishing becomes separate from actual execution

BDD for shared decisions

BDD works well when a product manager can read a scenario and object to its meaning before implementation begins. A scenario such as “Given an invite link has expired, When the recipient opens it, Then the page offers a request for a new link” gives the team a sentence to debate.

The failure mode appears when engineers encode every click. “When I click the blue button in the top-right corner” describes a current interface, not durable behavior. UI-coupled steps make refactoring expensive and force product language to follow implementation details.

Specification by example for rules

Specification by example is particularly effective for pricing, permissions, eligibility, dates, and other domain logic where a small change in input should produce a clearly different result. Tables let a product owner add a boundary case without writing a long narrative.

The team still needs judgment. Examples don't automatically reveal every rule, and an exhaustive table can become harder to understand than the business policy it represents.

Living documentation for shared visibility

Living documentation earns its name when the published content comes from scenarios that run. It helps support and product teams answer “what does the system do?” without relying on an engineer to interpret a stale ticket.

Most small teams blend the patterns. They might discover behavior through BDD conversations, formalize calculations with example tables, and publish the resulting capabilities as living documentation. Choosing one label too early matters less than keeping the artifact readable, executable, and owned by the people who understand the product.

Writing Your First Executable Spec

Start with a one-line requirement, then refuse to implement it until the behavior has concrete examples. “Users can reset a password within five minutes” leaves open what counts as valid, what happens after expiry, and whether repeated requests trigger protection.

Extract the cases first:

  • A valid email produces a reset message and a usable token.
  • An expired token sends the user to a recovery path.
  • Repeated requests trigger the product's defined rate-limit behavior.

Use acceptance criteria guidance to separate observable outcomes from implementation choices. The scenario should say what the user or system can observe, not which controller, database table, or CSS selector must produce it.

A small feature file

Feature: Password reset

  Scenario: A user resets a password with a valid token
    Given a registered user has requested a password reset
    And the reset token is still valid
    When the user submits a new password
    Then the password is changed
    And the user can sign in with the new password

  Scenario: A user opens an expired reset token
    Given a registered user has an expired reset token
    When the user opens the reset link
    Then the user sees that the link has expired
    And the user can request a new reset link

The step definitions should be thin. They translate domain language into fixtures and application calls, rather than embedding the whole test in the scenario. A Python team might connect the steps to pytest-bdd, while a Node team could use Cucumber.js or a Jest integration harness.

@given("the reset token is still valid")
def valid_token(reset_token):
    reset_token.expires_at = clock.now() + timedelta(minutes=5)

@when("the user submits a new password")
def submit_password(client, reset_token):
    client.post("/reset-password", json={
        "token": reset_token.value,
        "password": "new-secret"
    })

@then("the password is changed")
def password_changed(user):
    assert user.password_matches("new-secret")

Keep one scenario focused on one behavior. Name the file after the capability, such as password_reset.feature, rather than after a component like reset_controller.feature. Steps should be imperative, observable, and reusable without knowing whether the product uses a browser, API, or background worker.

Your first local run

  1. Create one capability file with a success case and a meaningful failure case.
  2. Run the parser before writing all step definitions.
  3. Implement the thinnest fixtures that exercise real application paths.
  4. Run the scenario locally and inspect the failure output.
  5. Invite a product teammate to review the wording before merging.

The first spec doesn't need to model the whole product. It needs to prove that the team can carry one decision from intent to a repeatable result.

Wiring Specs Into CI and Team Workflows

A spec suite becomes useful when it runs where the team already makes decisions. Put it beside the product code, not in a separate documentation project that engineers remember only during release preparation.

A practical repository layout might look like this:

src/
specs/
  features/
  steps/
  fixtures/
docs/

The exact directories depend on the runner, but the principle is stable. A reviewer should be able to follow a pull request from the changed capability to its scenario file, step definitions, and implementation.

Build a fast feedback path

Run a focused smoke subset on each commit or pull request, then run the broader suite on a scheduled job. Tag scenarios by capability, risk, and speed so the pipeline can make an explicit choice instead of guessing which tests are safe to skip.

TagPurposeWhen to runExample
@smokeProtect the shortest critical pathEvery pull requestSign-in succeeds
@criticalGuard revenue or access behaviorEvery pull request and releasePermission denial works
@slowIsolate expensive integration behaviorScheduled or targeted runsExternal-service workflow
@wipMark incomplete work temporarilyLocal development onlyNew billing rule

@wip should have an owner and an expiration decision. A permanent work-in-progress tag is a hidden backlog, not a workflow.

Block merges when required specs fail. A red build that permits merging teaches the team that the gate is decorative. Flaky scenarios need the same public triage path as product bugs, with an owner, a clear failure reason, and a decision to fix, quarantine, or remove the scenario.

Make the social loop explicit

Require the PR description to link to the scenario file and explain any behavior that isn't covered. Assign step-definition ownership to the engineers who own the domain area, but review wording with product partners. Publish generated documentation after a successful merge so support and product see behavior that passed against the current code.

Don't create a second slow pipeline by duplicating setup. Reuse application fixtures, test databases, and service containers from the existing integration tests where possible. The specification runner should add a readable collaboration layer, not force the team to maintain an entirely separate test platform.

For teams connecting delivery work to issue tracking, a documented Jira integration workflow can help keep the scenario, ticket, and pull request traceable. The important outcome is not the brand of tracker. It's that a reviewer can locate the decision and verify that the implementation still represents it.

Tooling and Where Collaborative AI Workspaces Fit

Choose the tool that fits the repository and the people maintaining it. Start with three questions:

  1. Which language already owns the product?
  2. Who will write and review the steps?
  3. Can the team maintain the runner, fixtures, and reporting without creating a second engineering project?

Cucumber JVM fits Java and other JVM repositories, while Cucumber.js serves Node-focused teams. SpecFlow remains a familiar option for .NET teams. Gauge uses Markdown-style specifications, which suits teams that want readable files without committing to one programming language.

Python teams commonly consider pytest-bdd or behave. A JavaScript team that does not need a dedicated Gherkin workflow can use Jest for plain-language integration tests, particularly when engineers and product partners already review behavior in code.

ToolSpec FormatBest ForStack FitAI Assist
Cucumber JVMGherkinShared behavior in JVM teamsJava and JVM ecosystemsDraft scenarios, never assume step names
Cucumber.jsGherkinBehavior suites in Node repositoriesJavaScript and TypeScriptSuggest examples and mappings
SpecFlowGherkin.NET acceptance workflowsC# and .NETGenerate first-cut scenarios
GaugeMarkdown specsLanguage-flexible teamsMultiple language runnersTurn prose into structured cases
pytest-bddGherkinPython teams using pytestPythonPropose fixtures and scenarios
behaveGherkinLightweight Python BDDPythonDraft behavior language
JestTest files and descriptionsPlain-language integration testsJavaScript and TypeScriptDraft test outlines

Where collaborative workspaces help

A collaborative AI workspace can support the work before a scenario reaches the runner. Product managers, designers, and engineers can shape examples together, connect them to PRDs and architecture decisions, and turn a paragraph of intent into an initial Given-When-Then outline. Engineers still verify every step, fixture, domain rule, and expected result.

SpecStory, Inc.'s Stoa workspace keeps decisions and working context alongside drafts, helping shorten the path from discussion to a first scenario while leaving verification with the engineering team.

AI is most useful for transformation and coverage prompts. It can extract candidate examples from a discussion, identify unanswered questions, and convert a known rule into a consistent format. It can also invent step names, assume an endpoint exists, or produce plausible scenarios that never execute against real fixtures. Treat generated output as a starting point, not as evidence that the behavior works.

Review license terms, repository access, export formats, and lock-in before adopting a workspace. Seed-stage teams should be able to keep scenarios as plain files and run them without depending on an assistant's memory. The practical test is simple: can another engineer inspect the generated spec, trace it to the decision that prompted it, and execute it through the same CI path as the application? If not, the workspace has added a drafting surface rather than a reliable bridge to the first commit.

Best Practices and Anti-Patterns for Small Teams

Executable specifications stay alive when the team treats them as part of product design and code review. They rot when someone writes them after delivery, hides implementation detail behind vague language, or lets a passing build substitute for product judgment.

A useful test is simple: could a product owner argue with the scenario? If the answer is no, the scenario probably describes code rather than behavior.

An infographic titled Keeping Executable Specs Alive contrasting best practices with common anti-patterns for software testing.

Habits that preserve value

  • One behavior per spec: Keep the password-reset example focused on valid and expired-token behavior instead of combining account creation, notifications, billing, and login into one feature file.
  • Business language: Say “the customer can download an invoice” rather than “the invoice service returns a 200 response,” unless the HTTP contract itself is the behavior under review.
  • Independent scenarios: Create their own state, clean up after execution, and avoid relying on whichever scenario ran before. Shared state makes failures misleading.
  • Refactored step definitions: Treat steps like production code. Remove duplicates, improve fixture boundaries, and preserve domain vocabulary as the implementation changes.
  • PR review: Review changes to scenario wording with the same care as changes to application code. A small wording change can alter the product contract.

Teams also need a culture that supports these habits. The broader practice of learning how to build a product culture from scratch is relevant because executable specs depend on shared ownership. If product treats scenarios as engineering paperwork and engineering treats them as QA scripts, the artifact will become stale no matter which runner you choose.

Anti-patterns that cause decay

The 80-scenario mega-feature file is a warning sign. It usually means the team grouped work by release or screen rather than capability. Split it into smaller behaviors that can be read and changed without navigating unrelated rules.

Procedural steps cause a different kind of damage. “Click the third button after scrolling twice” fails as soon as the interface changes, while “When the member requests a new invite” remains useful across UI and API implementations.

Don't leave @wip in the suite indefinitely. Don't automate scenarios only after QA finds the feature in production. And don't treat green CI as proof that the product works. A passing scenario can still encode the wrong requirement, omit a meaningful edge case, or use fixtures that don't resemble reality.

Green means the implementation matches the scenarios you wrote. It doesn't prove you wrote the right scenarios.

Audit your suite by asking who can explain each behavior, whether the examples still match current product decisions, and whether failures point to a real discrepancy. Remove dead scenarios. Strengthen vague ones. Keep the executable layer small enough that a seed-stage team can understand every failure and act on it.


SpecStory, Inc. offers Stoa, a collaborative AI workspace that preserves product conversations, decisions, and artifacts as reusable context for the path from intent to executable specification. Visit SpecStory, Inc. to explore a workflow for turning shared decisions into traceable scenarios and first commits.

Newsletter

Get new posts in your inbox

Bring your team together to build better products. Fresh takes on remote collaboration and AI-driven development.