FabricFabricExperiments
Migration

Move from Behave

Keep readable Gherkin while moving Behave suites to Fabric's TypeScript runner and first-class Databricks workload steps.

Fabric keeps the part your stakeholders see—standard Gherkin feature text—and replaces Behave's Python runner layer with cucumber-js, TypeScript support code, and Databricks-aware execution contexts. Python notebooks and pipelines remain valid workloads under test; Python is simply not required to orchestrate them.

Start with the ten-minute BDD quickstart if you want to evaluate the runner before migrating an existing suite.

Decide whether Fabric fits

Fabric is a strong fit when:

  • Databricks SQL, Delta, Unity Catalog, Jobs, Lakeflow, Volumes, Lakebase, notebooks, dbt, or Databricks Apps are important test surfaces;
  • analysts and product owners should keep authoring readable .feature files;
  • the platform team is comfortable maintaining reusable steps in TypeScript;
  • local DuckDB feedback and live Databricks execution should share scenarios;
  • CI evidence, automatic cleanup, least privilege, and resource lifecycle are part of the framework rather than custom glue.

Retain Behave only when Python source compatibility itself is a requirement—for example a third-party plugin imports Behave internals or your organization must continue executing existing Python step modules unchanged. Fabric provides TypeScript equivalents for the user-visible runner capabilities, but it does not pretend that TypeScript classes are Python ABI-compatible.

Capability mapping

BehaveFabric
features/*.featureSame standard Gherkin files.
environment.pyCucumber hooks in TypeScript support files; core hooks ship with Fabric.
contextScenario-isolated DatabricksWorld.
context.add_cleanup()this.addCleanup() in reverse registration order.
before_all / after_allCucumber BeforeAll / AfterAll.
Run-scoped fixturesharedFixtures.runFixture(key, setup); reverse-order AfterAll cleanup.
Feature-scoped fixturethis.featureFixture(key, setup); memoized by feature URI and cleaned in AfterAll.
before_scenario / after_scenarioCucumber Before / After.
before_step / after_stepCucumber BeforeStep / AfterStep; Fabric attaches failure evidence.
-D name=value userdataWorld parameters or DBX_TEST_USERDATA_NAME.
Layered context attributessetState(scope, key, value) with scenario → feature → run lookup.
context.execute_steps()Typed defineAction() helpers and this.runAction().
parse/cfparse typesCucumber Expressions, custom parameter types, and parseCardinalityField() for ?, *, +.
@fixture / active tagsFixture helpers plus @requires.*, @excludes.*, @use.with_*, and @not.with_*.
--junitJUnit formatter.
rerun formatterRerun formatter and manifest.
captured stdout/stderr/loggingSecret-redacted, per-step process-output capture.
--steps-catalogGenerated Fabric steps catalog.
-w@wip, fail-fast, summary profile.
multiprocessing add-onsParallel local scenarios; shared live resources remain serial.
--stage / configuration variantsCucumber profiles plus DBX_TEST_STAGE and worldParameters.
custom runner / formatterrunFeatures(), Cucumber support-code APIs, and custom formatters.

The exhaustive behavior-by-behavior status is in the compatibility matrix.

Migrate incrementally

1. Run the existing feature text unchanged

Create a small TypeScript runner project using the BDD quickstart. Copy one feature that uses only Given/When/Then, Background, Scenario Outline, Examples, Rule, and tags.

Import Fabric's standard steps:

import '@fabricorg/databricks-bdd/steps'

Run it locally first. Undefined steps are a migration inventory, not a reason to rewrite the Gherkin.

2. Replace generic database glue with shipped steps

Before porting Python implementations, check the step catalog. Fabric already supplies fixture tables, SQL queries, row/golden/schema assertions, permission failures, Jobs, pipelines, Volumes, Lakebase, notebooks, and dbt steps.

For example, replace a custom Python table loader with:

Given a table "orders" with:
  | order_id | amount | ordered_at          |
  | o-1      | 49.0   | 2026-07-01T10:00:00Z |
When I query table "orders"
Then the result has 1 rows

3. Port domain steps, not infrastructure steps

Move business language such as “the churn guardrail fires” into TypeScript steps over DatabricksWorld. Keep SQL emitters and orchestration helpers in ordinary TypeScript modules so they can also be unit tested. See Advanced Databricks BDD.

4. Map hooks and cleanup

Fabric already owns context creation, output capture, failure attachment, and context disposal. Port only product-specific setup. Register temporary resources with this.addCleanup() instead of relying on a later scenario to remove them.

For a Behave fixture shared by every scenario in one feature, port the setup body to this.featureFixture('stable-key', setup). For a run-wide service use sharedFixtures.runFixture(). Both accept either a value or { value, cleanup }, deduplicate concurrent setup, retry failed setup, and clean up in reverse order. Feature cleanup happens in AfterAll, rather than Behave's after_feature, because cucumber-js does not expose a safe after-feature lifecycle event under filtering and parallel execution.

5. Replace Python context composition with typed TypeScript

Port mutable context attributes to explicit layered state and port nested execute_steps() calls to ordinary typed actions:

import { defineAction, type DatabricksWorld } from '@fabricorg/databricks-bdd'

const createCustomer = defineAction<[string], void>(async (world, customerId) => {
  const ctx = await world.context()
  await ctx.exec('INSERT INTO customers VALUES (?)', [customerId])
  world.setState('scenario', 'customerId', customerId)
})

When('customer {string} signs up', async function (
  this: DatabricksWorld,
  customerId: string,
) {
  await this.runAction(createCustomer, customerId)
})

Use run state for suite configuration, feature state for values shared by scenarios in one feature file, and scenario state for mutable test data. Scenario values override feature values, which override run values. Pair shared state with the fixture of the same scope when the value owns a resource.

6. Separate local and live behavior

Keep engine-neutral logic untagged so it runs on DuckDB. Add @live, @jobs, @dlt, @pipeline, @autoloader, @lakebase, or @slow only when the scenario genuinely requires that managed surface. The local profile skips those tags; the live profile runs serially after an eager workspace preflight.

For the equivalent of Behave active tags, describe runtime requirements in Gherkin and provide capabilities through environment variables:

@requires.cloud=azure @requires.compute=serverless
Scenario: serverless contract
  Given a Databricks workspace

@use.with_cloud=azure and @not.with_cloud=azure are accepted for suites that want to retain Behave's active-tag spelling.

7. Compare evidence before switching CI

Run Behave and Fabric against the same fixtures for a transition window. Compare scenario counts, expected rows, JUnit results, failure messages, and cleanup. Add Fabric's HTML, rerun, and JSON evidence as CI artifacts. Retire the Python implementation after the same feature behavior is green locally and on the intended Databricks target.

Language boundary, not capability boundary

Fabric does not execute Python step modules or expose Behave's Python object model. It supplies the corresponding TypeScript behaviors: typed actions for step composition, explicit layered state, cardinality parsing, active tags, fixtures, hooks, profiles, formatters, userdata, output capture, and evidence. This keeps compiler checking and warehouse isolation while avoiding two runtime implementations.

featureFixture() cleanup occurs in AfterAll, not immediately after the last scenario in a feature, because cucumber-js can interleave feature scenarios under filters, retries, and parallel execution. The resource remains isolated by feature URI. This lifecycle timing is the remaining observable difference; it does not require Python or prevent once-per-feature setup.

There is no shipped Python step-definition adapter. It is unnecessary for feature parity in the TypeScript runner and would make Python a production dependency. Tests can still submit Python notebooks, run Python-backed Lakeflow pipelines, and validate PySpark-produced tables.

Evaluation checklist

Before choosing a framework, prove these outcomes with your own workload:

  • A representative feature runs locally without Databricks credentials.
  • The same business assertion runs on the intended SQL warehouse.
  • A failed SQL step includes useful, redacted evidence.
  • A temporary table is removed after success and failure.
  • A Job or pipeline reaches a terminal state within an explicit timeout.
  • A restricted identity is denied access outside the test schema.
  • JUnit, HTML, rerun, and JSON evidence are retained by CI.
  • Your team can add one domain step in TypeScript.
  • The target cloud and compute mode are listed as certified rather than merely API-compatible.

The public published-package consumer is a minimal, cloneable local example. For production design, continue with the advanced playbook.

On this page