Behavior Driven Development
Behavior Driven Development (BDD) is an agile software development technique. BDD enables non-technical participants to participate in writing tests.
The HILSTER Testing Framework enables users to write features in a gherkin-like language with add-ons that can be run as tests with seamless integration into the existing ecosystem.
Tests are written in form of Features, Rules, Scenarios and Steps, which are either Given, When, or Then steps. It is also possible to create Data Driven Feature Tests.
Thus every step is implemented in Python based on HILSTER Testing Framework.
HILSTER Testing Framework also offers requirements coverage in combination with the Dashboard.
Features
A feature file describes a feature in natural language with scenarios describing actions and the expected outcome.
The filename ends with .feature
with UTF-8 encoding
.
Every feature file must contain one single feature.
Every feature can contain one or multiple scenarios or scenario outlines.
A feature may contain further keywords to describe how tests may run.
Lines starting with #
are comments.
Scenarios
Scenarios are “tests” which consist of Given
, When
, and Then
statements.
They describe preconditions, actions as well as the expected outcomes.
Steps
Given
steps specify preconditions, which describe the state of the environment.
They are used to put the tested system into an initial state.
When
steps specify conditions, for example when car turns right
is a condition.
They are used to take actions.
Then
steps specify assertions and serve for checking if conditions are met.
They are used to observe outcomes.
Each statement can be repeated with And
as well as But
.
HILSTER Testing Framework finds the correct implementation of these steps so that they can be executed as a test.
Writing Features
A feature is written in a file named <feature-name>.feature
.
The feature description is written with the Feature:
keyword followed by a feature name and an optional
indented documentation string in the next lines.
Feature: Example features without info string
Feature: Example features with info string
This documentation string is optional.
Writing Scenarios
A scenario is described inside a Scenario:
keyword.
It is indented compared to the feature.
Feature: Heating
Scenario: Heat up
Given the refrigeration machine controller is running at 100 rpm
And the environmental temperature is 21 °C
When the initial temperature is -100 °C
Then the refrigeration machine controller does not cool down
Example Feature
The following example contains a very simple feature with a single scenario.
Feature: This is an example feature
This features is only used as an example
Scenario: Example
Given we write an example
When the interested reader reads it
Then he will understand how BDD works
Complex Scenarios
As an extension to BDD, scenarios support repeated keywords which allow you to create more complex scenarios.
Feature: This is an example feature
This features is only used as an example
Scenario: Complex Example
Given we write an example
When the interested reader reads it
Then he will understand how BDD works
Given we write another example
When the interested reader reads it, too
Then he will understand how BDD works for sure
Implementing Steps
Steps are implemented in Python as methods that are decorated with
htf.given
, htf.when
and htf.then
decorators.
The following code shows the implementation of the corresponding steps so that this feature can be run as a test.
@htf.given("we write an example")
def we_write_an_example():
pass
@htf.when("the interested reader reads it")
def the_interested_reader_reads_it():
pass
@htf.then("he will understand how BDD works")
def he_will_understand_how_bdd_works():
pass
The corresponding steps are found via pattern matching. Details are described later.
Steps can be async, too.
@htf.given("we write an example")
async def we_write_an_example():
pass
@htf.when("the interested reader reads it")
async def the_interested_reader_reads_it():
pass
@htf.then("he will understand how BDD works")
async def he_will_understand_how_bdd_works():
pass
- htf.given(precondition: str, parser: str | None = None, extra_types: Dict[Any, Any] | None = None, converters: Dict[str, Any] | None = None) Callable[[Callable[[...], Any]], Any]
Decorates a method to make it an implementation of a given step in a behavior driven test. See also https://cucumber.io/docs/gherkin/reference/#given .
- Parameters:
precondition – gherkin feature precondition
parser=None – the parser to parse the parameters from the precondition
extra_types=None – extra types for parsing
converters=None – optional converters that are applied to parameters after parsing
- htf.when(condition: str, parser: str | None = None, extra_types: Dict[Any, Any] | None = None, converters: Dict[str, Any] | None = None) Callable[[Callable[[...], Any]], Any]
Decorates a method to make it an implementation of a when step in a behavior driven test. See also https://cucumber.io/docs/gherkin/reference/#when .
- Parameters:
condition – gherkin feature condition
parser=None – the parser to parse the parameters from the condition
extra_types=None – extra types for parsing
converters=None – optional converters that are applied to parameters after parsing
- htf.then(assertion: str, parser: str | None = None, extra_types: Dict[Any, Any] | None = None, converters: Dict[str, Any] | None = None) Callable[[Callable[[...], Any]], Any]
Decorates a method to make it an implementation of a then step in a behavior driven test. See also https://cucumber.io/docs/gherkin/reference/#then .
- Parameters:
assertion – gherkin feature assertion
parser=None – the parser to parse the parameters from the assertion
extra_types=None – extra types for conversion
converters=None – optional converters that are applied to parameters after parsing
Running Features
Run htf
by giving the specifier for feature
file, and also the <step-implementation>.py
file.
Features and normal tests can also be mixed up.
In Python you can run:
htf.main(tests=["steps.py", "example.feature"])
And on the command-line you can run:
htf steps.py example.feature
Steps with Parameters
You can implement steps with parameters, too. This allows to use one step implementation for more than one step in a feature file.
Feature: Feature with parameters in steps
Scenario: Monkey eats many Bananas
Given the monkey is awake
When the monkey eats 10 bananas
Then he is not hungry anymore
Scenario: Monkey eats two Bananas
Given the monkey is awake
When the monkey eats 2 bananas
Then he is still hungry
The When
step with the parameter to read the number of bananas
is implemented in the following example.
@htf.when("the monkey eats {number_of_bananas} bananas")
def monkey_eats_bananas(number_of_bananas):
print(f"Monkey eats {number_of_bananas} bananas")
This also works for the other steps.
There are also other parsers for more complex situations.
Long Lines
Long lines can be split up to multiple lines by adding a \
at the end.
Feature: Feature with long lines
Scenario: Monkey eats many Bananas
Given the monkey is awake
When the monkey eats \
10 bananas
Then he is not \
hungry \
anymore
Multi-Language Support
Features can be written in natural languages and do not need to use the English language.
The first line of a feature file controls the language. The default language is English (en
).
The following example uses German as the language.
# language: de
Funktion: Heizen
Szenario: Aufheizen
Angenommen die Maschine läuft mit 100 Umdrehungen pro Minute
Wenn die initiale Temperatur 100 °C beträgt
Dann erreicht die Temperatur der Maschine nicht 120 °C
Context Fixture
If you need to pass data between steps you can use the htf.fixtures.context
fixture.
Other fixtures can be used like expected. Step parameters are supplied on the left side.
@htf.when("the monkey eats {number_of_bananas} bananas")
def monkey_eats_bananas(number_of_bananas, context):
print(f"Monkey eats {number_of_bananas} bananas")
context.number_of_bananas = int(number_of_bananas)
@htf.then("he is not hungry anymore")
def monkey_is_not_hungry_anymore(context, assertions):
assertion.assert_greater(context.number_of_bananas, 5, "The monkey did not eat enough bananas!")
@htf.then("he is still hungry")
def monkey_is_still_hungry(context, assertions):
assertion.assert_less_equal(context.number_of_bananas, 5,
"The monkey ate too much bananas! The monkey is dead now.")
To set the actual result of a step, the fixture htf.fixtures.context
contains current_step
, which is
the htf.fixtures.step
instance in the current context.
@htf.then("the current number of bananas is {number_of_bananas}")
def check_current_number_of_bananas(number_of_bananas, context):
current_number_of_bananas = 10
context.current_step.set_actual_result(current_number_of_bananas)
htf.assert_almost_equal(current_number_of_bananas, number_of_bananas)
Attach Data to Steps
You can attach more complex data to steps compared to parameters by adding a data table or text in the feature.
The data is passed via the htf.fixtures.context
fixture as table
or text
attribute.
Feature: Feature with Data
Scenario: Monkey feeding
When the monkey eats bananas
| i | name |
| 1 | first |
| 2 | second |
And the monkey eats bananas and says
"""
This text is said while eating bananas
"""
| i | name |
| 3 | third |
| 4 | fourth |
Then the monkey says
"""
Baarrrb.
"""
The implementation of the steps looks like the following
@htf.when("the monkey eats bananas")
def monkey_eats_bananas(context):
for item in context.table:
print(f"Eat the {item['i']}th banana called {item['name']}")
@htf.when("the monkey eats bananas and says")
def monkey_eats_bananas(context):
for item in context.table:
print(f"Eat the {item['i']}th banana called {item['name']}")
print(f"Monkey: {context.text}")
@htf.then("the monkey says")
def monkey_says(context):
print(f"Monkey: {context.text}")
Tagging
HILSTER Testing Framework offers the possibility to add textual tags to features and scenarios as well as scenario outlines.
To do this use the Tags:
keyword followed by a list of comma-separated strings.
Feature: Feature with Tags
Tags: Tag-1, Tag-2, Tag-3
Scenario: Scenario with Tags
Tags: Tag-4, Tag-5, Tag-6
# ...
Tags are combined and put into the test result and thus can be used to filter text by tags as mentioned in Tagging.
Requirements
HILSTER Testing Framework offers the possibility to add textual requirements to features and scenarios as well as scenario outlines.
To do this use the Requirements:
keyword followed by a list of comma-separated strings.
Feature: Feature with Requirements
Requirements: Req-1, Req-2, Req-3
Scenario: Scenario with Requirements
Requirements: Req-4, Req-5, Req-6
# ...
The requirements are combined and put into the test result and thus can be used to perform requirements coverage in combination with the Dashboard.
Implemented Tests
HILSTER Testing Framework offers the possibility to add links to implemented tests to features and scenarios.
To do this use the Implements:
keyword followed by a list of comma-separated strings.
Each entry can be a url or a simple id.
Feature: Feature with implemented tests
Implements: Test-1, https://host:port/path/to/Test-2
Scenario: Scenario with implemented tests
Implements: Test-3
# ...
The implemented tests are added to the test report and used to send test results back to ALM/QMS systems.
Background Steps
The keyword Background:
can be used when scenarios inside a feature have intersecting preconditions.
It contains only Given
preconditions that are automatically run in every scenario before running
the other steps.
It can be used to realize common preconditions.
Feature: Refrigeration machine can preserve internal heat correctly
Background:
Given the refrigeration machine controller is running at 100 rpm
Scenario: Heat up
Given the environmental temperature is 21 °C
# ...
Scenario: Cool down
Given the environmental temperature is 21 °C
# ...
Preconditions for Background:
are implemented like normal given steps with htf.given
.
Rules
The Rule:
keyword is optionally used for grouping together related scenarios when they belong to the same feature.
Feature: Monkey movements are correct
Rule: Monkeys can run if they are healthy
Scenario: Baboon can run
Given baboon is healthy and adult
Then baboon can run
Scenario: Chimp
Given chimp is healthy and adult
Then chimp can run
Rule: Another rule
# ...
Scenario Outlines
A scenario outline is a scenario template that dynamically generates scenarios.
It is the behavior driven way to realize Data Driven Testing.
It consists of steps like a normal scenario that has template variables in it and one
data source called Examples
.
When the scenarios are generated the template variables are replaced automatically.
Feature: Feature with Scenario Outline
Scenario Outline: Machine does not cool down
Given the refrigeration machine controller is running at <rpm> rpm
When the initial temperature is <temperature> °C
Then the refrigeration machine controller does not cool down
Examples:
| rpm | temperature |
| 78 | -10 |
| 80 | 21 |
| 90 | 25 |
| 100 | 38 |
Running the examples leads to multiple dynamically generated scenarios with parameters:
Scenario: Machine does not cool down [rpm=78, temperature=-10] (Feature: ...)
Scenario: Machine does not cool down [rpm=80, temperature=21] (Feature: ...)
Scenario: Machine does not cool down [rpm=90, temperature=25] (Feature: ...)
Scenario: Machine does not cool down [rpm=100, temperature=38] (Feature: ...)
The implementation can be done with normal steps with parameters for example.
htf.given("the refrigeration machine controller is running at <rpm> rpm")
def machine_running_at(rpm):
# this is called once for every line in the data table (78, 80, 90 and 100)
htf.when("the initial temperature is {temperature} °C)
def initial_temperature(temperature):
# this is called once for every line in the data table (-10, 21, 25 and 38)
Scenario Outline Data Sources
Scenario outlines support multiple different data sources which can be data-tables, external CSV-, JSON, YAML-files and also Python generators. Every data source can have an optional title.
Data-Tables Data Sources
Data-tables are textual tables supplied directly in the feature file. The first line is a header. All other lines are data lines. A data table is parsed into a list of dictionaries with the keys from the header line.
Feature: Feature with Data-Table
Scenario Outline: Data-Table Scenario
Given there is a data-table with <key> and <value>
Examples: Data-Table
| key | value |
| foo | bar |
| ding | dong |
Running the examples leads to multiple dynamically generated scenarios with parameters:
Scenario: Data-Table Scenario [key=foo, value=bar] (Feature: ...)
Scenario: Data-Table Scenario [key=ding, value=dong] (Feature: ...)
CSV Data Sources
Instead of using data-tables you can also supply external data via a CSV file.
To do this you can supply an example with the CSV:
keyword followed by a CSV filename.
The filename can be an absolute path or a relative path.
Relative paths are relative to the feature file where they appear.
The following example show the contents of data.csv
key,value
foo,bar
ding,dong
The following feature shows how to use CSV data.
Feature: External CSV Data
Scenario Outline: CSV Scenario Outline
Given there is a csv-data-file with <key> and <value>
Examples: CSV Data-Source
CSV: data.csv
Running the examples leads to multiple dynamically generated scenarios with parameters:
Scenario: CSV Scenario Outline [key=foo, value=bar] (Feature: ...)
Scenario: CSV Scenario Outline [key=ding, value=dong] (Feature: ...)
JSON Data Sources
JSON data sources are also supported via the JSON:
keyword followed by a JSON filename.
The filename can be an absolute path or a relative path. Relative paths are relative to the feature file where they appear.
The following example show the contents of data.json
[
{"key": "foo", "value": "bar"},
{"key": "ding", "value": "dong"}
]
The contents have to be a list of dictionaries.
The following feature shows how to use CSV data.
Feature: External JSON Data
Scenario Outline: JSON Scenario Outline
Given there is a json-data-file with <key> and <value>
Examples: JSON Data-Source
JSON: data.json
Running the examples leads to multiple dynamically generated scenarios with parameters:
Scenario: JSON Scenario Outline [key=foo, value=bar] (Feature: ...)
Scenario: JSON Scenario Outline [key=ding, value=dong] (Feature: ...)
YAML Data Sources
YAML data sources are also supported via the YAML:
keyword followed by a YAML filename.
The filename can be an absolute path or a relative path. Relative paths are relative to the feature file where they appear.
The following example show the contents of data.yml
- key: foo
value: bar
- key: ding
value: dong
The contents have to be a list of dictionaries.
The following feature shows how to use CSV data.
Feature: External YAML Data
Scenario Outline: YAML Scenario Outline
Given there is a yaml-data-file with <key> and <value>
Examples: YAML Data-Source
YAML: data.yml
Running the examples leads to multiple dynamically generated scenarios with parameters:
Scenario: YAML Scenario Outline [key=foo, value=bar] (Feature: ...)
Scenario: YAML Scenario Outline [key=ding, value=dong] (Feature: ...)
Python Generators Data Sources
Python generators can also be used as data sources via the Generator:
keyword followed by a generator title.
Every data generator needs to be decorated with htf.data_generator
.
The following Python code can be used as data sources.
@htf.data_generator("Python-based Generator")
def data_generator() -> Generator[Dict[str, str], None, None]:
yield dict(key="foo", value="bar") # this is the most efficient way
yield dict(key="ding", value="dong")
# ...
@htf.data_generator("Python-based Data Source with Lists")
def data_source_with_list() -> List[Dict[str, str]]:
return [ # this also works
dict(key="foo", value="bar"),
dict(key="ding", value="dong")
# ...
]
Async Python generators are supported, too.
@htf.data_generator("Python-based Generator")
async def data_generator() -> AsyncGenerator[Dict[str, str], None]:
yield dict(key="foo", value="bar") # this is the most efficient way
yield dict(key="ding", value="dong")
# ...
The data generator must return an iterable so you can make it a Python generator or let it return an iterable object like a list for example.
The following feature shows how to use a Python generator as a data source.
Feature: Python Generator Data Source
Scenario Outline: Data Source Scenario Outline
Given there is a data-source yielding <key> and <value>
Examples: Python Generator Data-Source
Generator: Python-based Generator
Running the examples leads to multiple dynamically generated scenarios with parameters:
Scenario: Data Source Scenario Outline [key=foo, value=bar] (Feature: ...)
Scenario: Data Source Scenario Outline [key=ding, value=dong] (Feature: ...)
- htf.data_generator(pattern: str, parser: str | None = None) Callable[[Callable[[...], Any]], Any]
Decorates a method to make it an implementation of a data generator used for examples in a behavior driven test.
- Parameters:
pattern – the pattern to match the title
parser=None – the parser to match the name of the example
Multiple and Combined Data Sources
Every scenario outline can have multiple data sources that can also be mixed.
Feature: Mixed External Data
Scenario Outline: Scenario Outline with Mixed Data
Given there is a data-table with <key> and <value>
Examples:
| key | value |
| foo | bar |
# CSV: data.csv
# JSON: data.json
# YAML: data.yml
# Generator: Python-based Generator
# etc.
Transforming Data Sources
Sometimes you need to precalculate values based on data-sources. To do this HILSTER Testing Framework supports data-transformation.
A data transformation is created by decorating a method with the htf.transformation
decorator.
The method is called for every line of the data source.
To use a data-transformation the data source (Example:
) need to have a name supplied.
In the following example the Addition functionality for a calculator is tested. The data-source only contains to addends but not the required result. To check if the calculator is working properly the result needs to be precalculated.
Feature: Addition
Scenario Outline: Addition
Given the calculator is cleared
When <a> is added to <b>
And the equals button is pressed
Then the result is <result>
Examples: Addition
| a | b |
| 1 | 1 |
| 2 | 12 |
To implement a data-transformation hook you could use the following code:
@htf.transformation("Addition")
def expected_addition_result(data):
data["result"] = int(data["a"]) + int(data["b"])
return data
Running the examples leads to multiple dynamically generated scenarios with parameters where result
is generated on the fly.
Transformations can be async, too.
@htf.transformation("Addition")
async def expected_addition_result(data):
data["result"] = int(data["a"]) + int(data["b"])
return data
Scenario: Addition [a=1, b=2, result=3] (Feature: ...)
Scenario: Addition [a=2, b=12, result=14] (Feature: ...)
- htf.transformation(pattern: str, parser: str | None = None) Callable[[Callable[[...], Any]], Any]
Decorates a method to make it an implementation of a data transformation hook used for examples in a behavior driven test.
- Parameters:
pattern – gherkin examples pattern
parser=None – the parser to match the name of the example
Skipping Features, Scenarios or Scenario Outlines
You can skip features, scenarios or scenario outlines by adding a line Skip: <reason>
to your feature.
To skip a whole feature you can use:
Feature: Skipped Feature
Skip: this feature is skipped
Scenario: Skipped Feature Scenario
Given there is a skipped feature
To skip a scenario you can use:
Feature: Skipped Feature
Scenario: Skipped Feature Scenario
Skip: this scenario is skipped
Given there is a skipped feature
To skip a scenario outline you can use:
Feature: Skipped Feature
Scenario Outline: Skipped Feature Scenario Outline
Skip: this scenario is skipped
Given there is a skipped feature
Examples:
| key | value |
| foo | bar |
Setup and Tear Down Code
It is possible to create setup and tear down code for features and scenarios, too.
To do this you can use htf.feature
and htf.scenario
.
The decorated methods can be returning methods to only implement setup code or can be implemented as generators yielding one element to implement setup and tear down code.
Fixtures and parameters are added like for the other step implementations.
Feature: Feature with Setup and Tear Down code
Scenario: Setup and Tear Down
Given the setup was run
when actions are performed
then an outcome will be available
In the following example only setup code for a feature and a scenario is implemented.
@htf.feature("Feature with Setup and Tear Down code")
def feature_set_up(context):
# your feature setup code comes here
@htf.scenario("Setup and Tear Down")
def scenario_set_up(context):
# your scenario setup code comes here
And in the following example setup and tear down code is implemented.
@htf.feature("Feature with Setup and Tear Down code")
def feature_set_up_and_tear_down(context):
# your feature setup code comes here
yield # now the scenario is run
# your feature tear down code comes here
@htf.scenario("Setup and Tear Down")
def scenario_set_up(context):
# your scenario setup code comes here
yield # now the scenario is run
# your scenario tear down code comes here
Setup and tear down code can be async, too.
- htf.feature(pattern: str, parser: str | None = None, extra_types: Dict[Any, Any] | None = None, converters: Dict[str, Any] | None = None) Callable[[Callable[[...], Any]], Any]
Decorates a method to make it an implementation of a given feature in a behavior driven test.
It can be implemented as a normal method with a return value to implement set up code or as a generator to realize set up and tear down code for a feature.
- Parameters:
pattern – gherkin feature pattern
parser=None – the parser to parse the parameters from the precondition
extra_types=None – extra types for parsing
converters=None – optional converters that are applied to parameter after parsing
- htf.scenario(pattern: str, parser: str | None = None, extra_types: Dict[Any, Any] | None = None, converters: Dict[str, Any] | None = None) Callable[[Callable[[...], Any]], Any]
Decorates a method to make it an implementation of a given scenario in a behavior driven test.
It can be implemented as a normal method with a return value to implement set up code or as a generator to realize set up and tear down code for a scenario.
- Parameters:
pattern – gherkin feature pattern
parser=None – the parser to parse the parameters from the precondition
extra_types=None – extra types for parsing
converters=None – optional converters that are applied to parameters after parsing
Parsing Steps
HILSTER Testing Framework offers three different parsers to parse steps and to find the implementation for a step by its name.
The default parser is based on the parse
module.
There is an extended version of parse
called cfparse
.
And there is also a regular-expression parser (re
).
All parsers support pattern matching and type casting.
How Parsing and Matching Works
HILSTER Testing Framework collects all implemented steps and features. When features, scenarios and steps are run the implementing step is looked up by iterating over all collected steps with the corresponding scope and matching the current step to the pattern using a parser.
If there is one match the step will be executed with optional parameters extracted. Parameters can also be converted to other types before calling the implementing method.
Using the Default Parser (parse)
The default step parser is based on the parse
packet that is the opposite of Python’s format
method.
It supports the Format String Syntax.
Parameters are put between {
and }
(e.g. {number}
) and can have type casts in the format expression in the
form of {[parameter name]:[format specification]}
.
The parsed parameters are passed to the implementing method by its name.
The following Gherkin example describes addition.
Feature: Addition
Scenario: Addition
Given the calculator is cleared
When 1 is added to 2
And the equals button is pressed
Then the result is <result>
The following example parses a
and b
that are added as strings that are added.
@htf.when("{a} is added to {b}")
def add(a, b, context):
context.expected_result = int(a) + int(b) # this type-cast has to be performed here!
In the example the parameters a
and b
are passed as strings so they need to be converted to int
in the
implementation.
To automatically convert a
and b
to int while parsing you can change the format string to:
@htf.when("{a:g} is added to {b:g}")
def add(a, b, context):
context.expected_result = a + b # a and b are int
You can find more information about the type conversion on GitHub parse repository.
Using the Regular-Expression-Parser (re, regex)
The re
parser uses regular-expressions to do the parsing.
Thus it is the most flexible parser but is more complex.
To enable the CF-Parser the parser
argument of the step-decorator has to be set with cf
or cfparse
and
converters
can to be supplied optionally.
converters
is a dictionary of callables where the keys are the names of the parameters of the implementing method.
The regular-expression must make use of named subgroups (e.g. r"(?P<parameter name>\w+)"
).
You can find more information about named subgroups in the
Python re Module.
Feature: Addition
Scenario: Addition
Given the calculator is cleared
When 1 is added to 2
And the equals button is pressed
Then the result is <result>
The following example parses a
and b
that are added as strings that are added.
@htf.when(r"(?P<a>\d+) is added to (?P<b>\d+)", parser="re")
def add(a, b, context):
context.expected_result = int(a) + int(b) # this type-cast has to be performed here!
In the example the parameters a
and b
are passed as strings so they need to be converted to int
in the
implementation.
To automatically convert a
and b
to int while parsing you can change the format string to:
@htf.when(r"(?P<a>\d+) is added to (?P<b>\d+)", parser="re", converters=dict(a=int, b=int)))
def add(a, b, context):
context.expected_result = a + b # a and b are int
Using the CF-Parser (cfparse, cf)
The CF-Parser extends the default with support for cardinality fields, and it automatically creates type variants to parse repeated parameters.
To enable the CF-Parser the parser
argument of the step-decorator has to be set with cf
or cfparse
and
extra_types
have to be supplied, too.
The following examples show how to accumulate a list of integers with a variable length.
The following example accumulates a list of numbers with variable amount.
Feature: Accumulation
Scenario: Accumulate 1
When 1 is accumulated
Then the result is 1
Scenario: Accumulate 2
When 1, 2 is accumulated
Then the result is 3
Scenario: Accumulate many
When 1, 2, 3, 4, 5, 6, 7, 8, 9 is accumulated
Then the result is 45
The Python-implementation looks like
import parse
@parse.with_pattern(r"\d+")
def parse_number(text):
return int(text)
extra_types = dict(Number=parse_number)
@htf.when("{numbers:Number+} is accumulated", parser="cf", extra_types=extra_types)
def accumulate(numbers, context):
context.accumulated_numbers = sum(numbers)
@htf.then("the result is {result:g}")
def the_result_is(result, context):
htf.assert_equal(result, context.accumulated_numbers)
You can find more information about cardinality field support on GitHub parse-type repository.
Getting Available Steps and Statements
As a developer you need to get all available steps and statements without looking into the code all the time.
This can be done using htf.get_statements
and htf statements <steps/test specifiers>
.
On the command-line you can run:
htf statements steps.py
Collecting statements
Given statements:
Given the refrigeration machine controller is running at {speed:g} rpm
When conditions:
When the initial temperature is {temparature:g} °C
When {numbers:Number+} is accumulated
Then assertions:
Then the result is {result:g}
Then the refrigeration machine controller is cooling down
Then the refrigeration machine controller does not cool down
Features:
.*
Scenarios:
.*
Transformations:
X and Y data
Data Generators:
Data Generator
And from Python you can use htf.get_statements
:
htf.get_statements("steps.py")
>>> {
'given': [
'Given the refrigeration machine controller is running at {speed:g} rpm'
],
'when': [
'When the initial temperature is {temparature:g} °C',
'When {numbers:Number+} is accumulated'
],
'then': [
'Then the refrigeration machine controller is cooling down',
'Then the refrigeration machine controller does not cool down',
'Then the result is {result:g}'
],
'features': ['.*'],
'scenarios': ['.*'],
'transformations': ['X and Y data'],
'data_generators': ['Data Generator']
}
- htf.get_statements(tests: Any | List[Any] = '__main__') Dict[str, List[str]]
Get a dict of behavior driven development statements found in the specified tests.
- Parameters:
tests=None – a test-specifier or a list of test-specifiers (folder, file, test-case, test-case-method, module)
- Returns:
a dict with all behavior driven development statements found
- Return type:
Examples
You can find examples in the htf-demo, too.