formatter

Formatters

Behave's runner is decoupled from its output: every line it prints is emitted through a formatter. The default selection (when --format is not given) is progress (compact dot-stream output). You can swap in any class that composes the BDD::Behave::Formatter role to render runs differently: indented tree, hierarchical documentation, JSON, JUnit XML, and so on.

Selecting a formatter

Pass --format NAME to behave:

$ behave --format tree specs/users-spec.raku

NAME must be a registered formatter. The built-in formatters are documentation, html, json, json-events, junit, progress, tap, and tree. (json-events is the line-delimited event stream parallel workers use to report back to the parent. It is registered and selectable but rarely useful on its own.) An unknown name fails fast:

$ behave --format nope
Error: unknown --format 'nope' (available: documentation, html, json, json-events, junit, progress, tap, tree)

When --format is omitted, progress is selected. The currently registered names are also listed in behave --help.

Built-in formatters

progress

The default. Compact single-line output, one character per example. See the dedicated entry below.

tree

Behave's classic indented tree output (formerly called default): one line per describe/context/it, a green SUCCESS / red FAILURE marker per example, and an inline SLOW annotation when --slow-threshold fires. Useful for debugging: you can see exactly which example is running when something hangs or hits a long step.

documentation

A clean, document-style rendering of the spec tree. Each describe/context is printed as a heading (no quotes, no arrow markers) and each example is printed as an indented line beneath its enclosing group. Non-passing outcomes get a parenthetical suffix:

OutcomeSuffix
Pass(none: description prints on its own)
Fail(FAILED)
Pending(PENDING)
Skipped(SKIPPED)
Around-skipped(SKIPPED: around-each did not invoke continuation)

The Failures: block, counts line, profile/benchmark sections, and the multi-file Overall: block all print exactly as they do under tree, except in multi-file mode the per-file Failures: + counts line is deferred. Every spec file's tree streams uninterrupted under its own filename heading, and a single Failures: block plus the Overall: counts print once at the end.

$ behave --format documentation specs/calc-spec.raku
Calculator
  addition
    adds two positive numbers
    overflows on MAX_INT (FAILED)
    handles negative-zero (PENDING)
  subtraction
    subtracts a positive from a positive

Failures:

  [ āœ— ] specs/calc-spec.raku:24
      Expected: 2147483648
      to be: -2147483648

5 examples, 1 failed, 1 pending, 3 passed

html

A self-contained HTML5 report with collapsible describe/context nesting (native <details>/<summary>, no JavaScript required) and color-coded examples. Designed to be redirected to a file and opened in a browser:

$ behave --format html specs/ > report.html
$ open report.html

Structure:

ElementRole
<p class="summary"> / <p class="summary has-failures">Counts line at the top, with has-failures styling when any example failed.
<details open class="group"> / <summary class="group-summary">One per describe/context. Open by default. Click to collapse.
<div class="example pass"> / .fail / .pending / .skippedPer-example row with a marker (āœ“/āœ—/āø/⊘), the description, duration, and source location.
<pre class="failure-detail">Failure body for failing examples (also used for pending reason and around-each skip reason).
<div class="load-error">One per spec file that failed to compile.

Inline <style> provides the default theme. Colors are hard-coded (no external CSS). All user descriptions and file paths are HTML-escaped (<, >, &, "). In multi-file mode every spec file gets an <h2 class="suite-file"> heading and a single HTML document covers the entire run.

json

Emits a single JSON document on stdout after the run finishes, designed for CI dashboards and tool integration. No per-example or per-group output is printed, so the document is the only thing on stdout (load errors and CLI flag errors are still emitted to stderr as usual).

Top-level shape:

{
  "version": 1,
  "order": "random",
  "seed": 42,
  "aborted": false,
  "examples": [...],
  "load_errors": [...],
  "summary": {
    "total": 5,
    "passed": 2,
    "failed": 1,
    "pending": 1,
    "skipped": 1,
    "duration": 0.045
  },
  "summary_line": "5 examples, 1 failed, 1 pending, 1 skipped, 2 passed"
}

Each entry in examples carries:

FieldDescription
descriptionThe it/pending/xit description (or the derived label for the it { ... } form).
full_descriptionThe description prefixed with the enclosing describe/context chain.
status"passed", "failed", "pending", or "skipped".
file, lineSource location.
durationSeconds (or null for pending/skipped).
tagsEffective tag list (inherits from enclosing groups).
failurePresent only for status: "failed". Carries type: "exception" with message when the body threw, and/or an expectations array with file, line, given, expected, negated, and an optional message/aggregation_label.
pending_reasonPresent only for status: "pending". The reason string passed to pending.
skip_reasonPresent only when around-each returned without invoking its continuation.

In multi-file mode the document is emitted once, after every spec file has run, with summary reflecting the merged counts. Per-suite intermediate emission is suppressed.

junit

JUnit XML format consumable by Jenkins, GitLab CI, CircleCI, and other CI dashboards that ingest the testsuites/testsuite/testcase schema.

<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="behave" tests="5" failures="1" errors="0" skipped="2" time="0.045000">
  <testsuite name="calc-spec.raku" tests="5" failures="1" errors="0" skipped="2" time="0.045000" timestamp="..." file="specs/calc-spec.raku">
    <testcase classname="Calculator" name="adds positive numbers" file="specs/calc-spec.raku" line="4" time="0.001000"/>
    <testcase classname="Calculator &gt; addition" name="overflows" file="specs/calc-spec.raku" line="9" time="0.001000">
      <failure type="Expectation" message="Expected 2147483648 to be -2147483648"><![CDATA[
specs/calc-spec.raku:9
  Expected: 2147483648
  to be:    -2147483648
      ]]></failure>
    </testcase>
    <testcase classname="Calculator" name="todo" file="specs/calc-spec.raku" line="12" time="0.000000">
      <skipped message="pending: not yet implemented"/>
    </testcase>
  </testsuite>
</testsuites>

Conventions:

OutcomeChild elementNotes
Pass(self-closing testcase)
Failure (expectation)<failure type="Expectation" message="..."><![CDATA[...]]></failure>Message summarizes the first failed expectation. CDATA body contains all expectations for the example (file:line + given/expected).
Failure (exception)<error type="Exception" message="..."><![CDATA[...]]></error>Used when the example body threw. Message comes from Exception.message.
Pending<skipped message="pending: REASON"/>
Skipped<skipped message="skipped"/>
Around-skipped<skipped message="around-each did not invoke continuation"/>

The classname attribute joins the enclosing describe/context chain with > so CI dashboards that group by class produce a sensible hierarchy. Special characters (<, >, &, ", ') in attribute values are XML-escaped. CDATA bodies have ]]> re-encoded as ]]]]><![CDATA[> so they survive arbitrary payloads. In multi-file mode every spec file produces its own <testsuite> element under a single <testsuites> root.

tap

Test Anything Protocol version 13 output for compatibility with TAP consumers (prove, tappy, etc.):

TAP version 13
1..5
ok 1 - Calculator addition adds positive numbers
not ok 2 - Calculator addition overflows on MAX_INT
  ---
  severity: 'fail'
  file: 'specs/calc-spec.raku'
  line: 24
  message: 'Expected 2147483648 to be -2147483648'
  got: '2147483648'
  expected: '-2147483648'
  ...
ok 3 - Calculator addition handles negative-zero # TODO not yet implemented
ok 4 - Calculator addition obsoleted # SKIP skipped
ok 5 - Calculator subtraction subtracts a positive from a positive

Conventions:

OutcomeLineNotes
Passok N - DESCRIPTION
Failure (expectation)not ok N - DESCRIPTION + YAML diagnostic blockYAML carries severity: 'fail', file, line, message, got, expected.
Failure (exception)not ok N - DESCRIPTION + YAML diagnostic blockYAML carries severity: 'error', message (the exception message).
Pendingok N - DESCRIPTION # TODO REASONTAP TODO directive (TAP consumers treat these as expected-fail, not failures).
Skippedok N - DESCRIPTION # SKIP skipped
Around-skippedok N - DESCRIPTION # SKIP around-each did not invoke continuation

The full describe/context chain is prefixed onto each description (joined by spaces). # characters embedded in user descriptions are escaped as \# so a TAP parser cannot mistake them for the start of a directive. In multi-file mode a single TAP document covers all spec files (one plan line, one numbering sequence).

progress

Compact single-line output, one character per example:

CharacterMeaning
.Pass
FFailure
*Pending
SSkipped (xit/xdescribe/xcontext or :skipped, and around-each/around-all that returned without invoking their continuation)

Group descriptions, it descriptions, and inline slow markers are suppressed. The failures detail block, counts line, profile/benchmark sections, and the multi-file Overall: block still print exactly as they do under tree, except in multi-file mode the per-file Failures: + counts line is deferred. Dots stream continuously across every spec file, then a single Failures: block plus the Overall: counts print once at the end.

$ behave --format progress specs/
.....FF*.S....

Failures:

  [ āœ— ] specs/calc-spec.raku:24
      Expected: 1
      to be: 2
  [ āœ— ] specs/db-spec.raku:11
      exception in Database connect refuses without credentials: connection refused

14 examples, 2 failed, 1 pending, 1 skipped, 10 passed

Both expectation mismatches and exception-based failures (an example body that dies or throws) appear in the Failures: block with their source file:line, so a progress run is enough to diagnose what to fix without re-running under tree.

The BDD::Behave::Formatter role

BDD::Behave::Formatter is a Raku role that declares a hook for every interesting event the runner emits. All hooks have no-op default bodies, so custom formatters only need to override the events they care about.

Lifecycle hooks

HookWhen it fires
suite-loading(:$file)Before a spec file is loaded (verbose mode only).
suite-start($suite, :$multi-file)Once per spec file, before its suite runs. :multi-file is true when more than one file was selected.
suite-end($suite)After a suite completes.
group-start($group) / group-end($group)Around every describe / context group.
group-around-skipped($group)When an around-all hook returned without invoking its continuation.
example-start($example, :$auto)Before an example body executes. :auto is true when the example will have its description derived from a matcher (it { ... }).
example-auto-description($example, :$description)After an auto-described example finishes, with the derived description.
example-pass($example) / example-fail($example, :$failure-info) / example-pending($example) / example-skipped($example)Per-example outcomes.
example-around-skipped($example)When an around-each hook returned without invoking its continuation.
example-slow($example, :$threshold)When an example's duration meets or exceeds --slow-threshold.

Summary hooks

HookWhen it fires
run-summary($result, :$aborted, :$fail-fast, :$order, :$seed, :$show-seed)Per-suite summary: failures, counts, aborted line, seed announcement. The seed line is shown only when $show-seed is set or the run had failures.
profile-summary(@records, :$limit)"Top N slowest" section when --profile is enabled.
benchmark-summary-section(@summaries, @regressions, :$threshold, :$format, :$output, :$runner)Per-suite benchmark section when --benchmark is enabled.
multi-file-overall($result, :$order, :$seed, :$show-seed)The Overall: block printed when running more than one spec file. Same seed-line gating as run-summary.
multi-file-profile, multi-file-benchmarkMulti-file counterparts of the per-suite summary hooks.
load-errors(@errors)Reports spec files that failed to compile.

Writing a custom formatter

use BDD::Behave::Formatter;
use BDD::Behave::Formatter::Registry;

class DotsFormatter does BDD::Behave::Formatter {
  method name(--> Str) { 'dots' }

  method example-pass($example)                   { print '.' }
  method example-fail($example, :$failure-info)   { print 'F' }
  method example-pending($example)                { print '*' }
  method example-skipped($example)                { print 'S' }
  method run-summary($result, *%) {
    say "";
    say "{$result.total} examples, {$result.failed} failed, {$result.passed} passed";
  }
}

BDD::Behave::Formatter::Registry.register('dots', DotsFormatter);

Once registered, the formatter is available to --format:

$ behave --format dots

The registry

BDD::Behave::Formatter::Registry is a name-to-class lookup the CLI consults when resolving --format.

use BDD::Behave::Formatter::Registry;

BDD::Behave::Formatter::Registry.names;
# → (documentation html json json-events junit progress tap tree)

BDD::Behave::Formatter::Registry.register('dots', DotsFormatter);
BDD::Behave::Formatter::Registry.registered('dots');   # True
BDD::Behave::Formatter::Registry.lookup('dots');       # DotsFormatter
BDD::Behave::Formatter::Registry.create('dots');       # DotsFormatter instance

BDD::Behave::Formatter::Registry.reset;                # back to the built-ins

Duplicate registrations and classes that do not compose the role are rejected with a clear error.

Passing a formatter directly to the runner

The runner accepts a :formatter argument for embedded use cases (custom CLIs, IDE integrations, etc.):

use BDD::Behave::Runner;
use BDD::Behave::Formatter::Tree;

my $f = BDD::Behave::Formatter::Tree.new;
my $runner = BDD::Behave::Runner::Runner.new(:formatter($f));
$runner.run($suite);

When :formatter is omitted, a BDD::Behave::Formatter::Tree instance is constructed automatically. (Note that the behave CLI selects progress by default when --format is not supplied. The runner constructor's default is Tree because tree output is the most useful for embedded/IDE consumers programmatically inspecting events.)

BDD::Behave v0.9.4

Behavior driven development framework

Authors

  • Greg Donald

License

Artistic-2.0

Dependencies

Provides

  • BDD::Behave
  • BDD::Behave::Benchmark
  • BDD::Behave::Benchmark::Baseline
  • BDD::Behave::Benchmark::Format
  • BDD::Behave::Bisect
  • BDD::Behave::Colors
  • BDD::Behave::Configuration
  • BDD::Behave::Coverage
  • BDD::Behave::DSL
  • BDD::Behave::Diff
  • BDD::Behave::DocExtractor
  • BDD::Behave::DryRun
  • BDD::Behave::Expectation
  • BDD::Behave::Failure
  • BDD::Behave::FailureStore
  • BDD::Behave::Failures
  • BDD::Behave::Files
  • BDD::Behave::Formatter
  • BDD::Behave::Formatter::Documentation
  • BDD::Behave::Formatter::HTML
  • BDD::Behave::Formatter::JSON
  • BDD::Behave::Formatter::JUnit
  • BDD::Behave::Formatter::JsonEvents
  • BDD::Behave::Formatter::Progress
  • BDD::Behave::Formatter::Registry
  • BDD::Behave::Formatter::TAP
  • BDD::Behave::Formatter::Tree
  • BDD::Behave::LetRuntime
  • BDD::Behave::Matcher
  • BDD::Behave::Matcher::Async
  • BDD::Behave::Matcher::Boolean
  • BDD::Behave::Matcher::Change
  • BDD::Behave::Matcher::Collection
  • BDD::Behave::Matcher::Core
  • BDD::Behave::Matcher::Custom
  • BDD::Behave::Matcher::Exception
  • BDD::Behave::Matcher::Numeric
  • BDD::Behave::Matcher::String
  • BDD::Behave::Matcher::Type
  • BDD::Behave::Mock::Allow
  • BDD::Behave::Mock::ArgMatcher
  • BDD::Behave::Mock::Double
  • BDD::Behave::Mock::HaveReceived
  • BDD::Behave::Mock::Spy
  • BDD::Behave::Mock::Stub
  • BDD::Behave::Parallel
  • BDD::Behave::Parallel::Distribution
  • BDD::Behave::Parallel::EventStream
  • BDD::Behave::Parallel::Manifest
  • BDD::Behave::Parallel::Queue
  • BDD::Behave::Parallel::WorkerPool
  • BDD::Behave::Runner
  • BDD::Behave::SharedContexts
  • BDD::Behave::SharedExamples
  • BDD::Behave::Slang
  • BDD::Behave::SpecLoader
  • BDD::Behave::SpecRegistry
  • BDD::Behave::SpecTree
  • BDD::Behave::SpecTree::Core
  • BDD::Behave::SpecTree::Example
  • BDD::Behave::SpecTree::ExampleGroup
  • BDD::Behave::SpecTree::Suite
  • BDD::Behave::Time
  • BDD::Behave::TypeName
  • BDD::Behave::Version
  • BDD::Behave::Watch
  • BDD::Behave::Watch::Session
  • BDD::Behave::Watch::SmartSelector
  • BDD::Behave::Watch::UI
  • BDD::Behave::Watch::Watcher
  • BDD::Behave::Worker

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.