Visitors Guide¶
The visitor pattern lets you traverse the entire domain model tree without coupling traversal logic to the model classes.
How it works¶
Every visitable node (Project, Feature, Rule, Background, Scenario, ScenarioOutline, Step, Tag, Table, DocString, Examples) implements accept(visitor), which calls the appropriate visit_* method on the visitor.
graph TD
A[project.accept visitor] --> B[visitor.visit_project]
B --> C[feature.accept visitor]
C --> D[visitor.visit_feature]
D --> E[rule.accept visitor]
E --> F[visitor.visit_rule]
D --> G[scenario.accept visitor]
G --> H[visitor.visit_scenario]
H --> I[step.accept visitor]
I --> J[visitor.visit_step]
Base Visitor class¶
Subclass Visitor and override only the visit_* methods you need:
from behave_model import Visitor
class MyVisitor(Visitor):
def visit_project(self, project):
print(f"Project with {len(project.features)} features")
def visit_feature(self, feature):
print(f" Feature: {feature.name}")
def visit_scenario(self, scenario):
print(f" Scenario: {scenario.name}")
def visit_step(self, step):
print(f" {step.full_text}")
Available visit methods¶
| Method | Called for |
|---|---|
visit_project(project) |
Project |
visit_feature(feature) |
Feature |
visit_rule(rule) |
Rule (Gherkin v6) |
visit_background(background) |
Background |
visit_scenario(scenario) |
Scenario |
visit_scenario_outline(outline) |
ScenarioOutline |
visit_examples(examples) |
Examples block |
visit_step(step) |
Step |
visit_table(table) |
Table |
visit_table_row(row) |
TableRow |
visit_doc_string(doc_string) |
DocString |
visit_tag(tag) |
Tag |
Built-in visitors¶
CountingVisitor¶
Counts nodes by type:
from behave_model import CountingVisitor
counter = CountingVisitor()
project.accept(counter)
print(counter.counts)
# {'project': 1, 'feature': 4, 'rule': 2, 'scenario': 12, 'step': 45, ...}
CollectingVisitor¶
Collects nodes by type into lists:
from behave_model import CollectingVisitor
collector = CollectingVisitor()
project.accept(collector)
print(f"Features: {len(collector.features)}")
print(f"Scenarios: {len(collector.scenarios)}")
print(f"Steps: {len(collector.steps)}")
print(f"Rules: {len(collector.rules)}")
Custom visitor examples¶
Step keyword counter¶
from behave_model import Visitor
class KeywordCounter(Visitor):
def __init__(self):
self.given = 0
self.when = 0
self.then = 0
self.and_ = 0
self.but = 0
def visit_step(self, step):
kw = step.keyword.lower().strip()
if kw == "given":
self.given += 1
elif kw == "when":
self.when += 1
elif kw == "then":
self.then += 1
elif kw == "and":
self.and_ += 1
elif kw == "but":
self.but += 1
counter = KeywordCounter()
project.accept(counter)
print(f"Given: {counter.given}, When: {counter.when}, Then: {counter.then}")
Tag usage analyzer¶
from behave_model import Visitor
from collections import Counter
class TagAnalyzer(Visitor):
def __init__(self):
self.tag_counts = Counter()
def visit_tag(self, tag):
self.tag_counts[tag.name] += 1
analyzer = TagAnalyzer()
project.accept(analyzer)
for tag, count in analyzer.tag_counts.most_common():
print(f" {tag}: {count}")
Feature file printer¶
from behave_model import Visitor
class SimplePrinter(Visitor):
def __init__(self):
self.indent = 0
self.lines = []
def _p(self, text):
self.lines.append(" " * self.indent + text)
def visit_feature(self, feature):
for tag in feature.tags:
self._p(tag.name)
self._p(f"Feature: {feature.name}")
self.indent += 1
def visit_rule(self, rule):
self._p(f"Rule: {rule.name}")
self.indent += 1
def visit_scenario(self, scenario):
self._p(f"Scenario: {scenario.name}")
self.indent += 1
def visit_step(self, step):
self._p(f"{step.keyword} {step.name}")
def visit_rule_end(self, rule):
self.indent -= 1
def visit_feature_end(self, feature):
self.indent -= 1
printer = SimplePrinter()
project.accept(printer)
print("\n".join(printer.lines))
Tree walking (alternative to visitors)¶
For simple traversal without a visitor class, use walk():
# Depth-first search (default)
for node in project.walk(strategy="dfs"):
print(type(node).__name__)
# Breadth-first search
for node in project.walk(strategy="bfs"):
print(type(node).__name__)
Next steps¶
- Query API — High-level filtering without visitors
- API Reference — Visitors — Complete visitor API