Defining Steps¶
-
aloe.
step
(sentence=None)¶ Decorates a function, so that it will become a new step definition.
You give step sentence either (by priority):
- with step function argument;
- with function doc; or
- with the function name exploded by underscores.
Parameters can be passed to steps using regular expressions. Parameters are passed in the order they are captured. Be aware that captured values are strings.
The first parameter passed into the decorated function is the
Step
object built for this step.Examples:
@step("I go to the shops") def _i_go_to_the_shops_step(self): '''Implements I go to the shops''' ... @step def _i_go_to_the_shops_step(self): '''I go to the shops''' ... @step(r"I buy (\d+) oranges") def _purchase_oranges_step(self, num_oranges): '''Buy a certain number of oranges''' num_oranges = int(num_oranges) ...
Steps can be passed a table of data.
Given the following users are registered: | Username | Real name | | danni | Danni | | alexey | Alexey |
This is exposed in the step as
Step.table
andStep.hashes
.@step(r'Given the following users? (?:is|are) registered:') def _register_users(self): '''Register the given users''' for user in guess_types(self.hashes): register(username=user['Username'], realname=user['Real name'])
Steps can be passed a multi-line “Python string”.
Then I see a warning dialog: """ Changes could not be saved. [Try Again] """
This is exposed in the step as
Step.multiline
.The registered function will have an
unregister()
method that removes all the step definitions that are associated with it.
Common regular expressions for capturing data¶
String
Given I logged in as "alexey"@step(r'I logged in as "([^"]*)"')
Number
Then the price should be $12.99@step(r'The price should be \$(\d+(?:\.\d+)?)')
Path/URI/etc.
Given I visit /user/alexey/profile@step(r'I visit ([^\s]+)')
Step loading¶
Steps can and should be defined in separate modules to the main application
code. Aloe searches for modules to load steps from inside the features
directories.
Steps can be placed in separate files, for example,
features/steps/browser.py
and features/steps/data.py
, but all those
files must be importable, so this requires creating a (possibly empty)
features/steps/__init__.py
alongside.
Additional 3rd-party steps (such as aloe_django) can be imported in from
your __init__.py
.
An imported step can be overridden by using unregister()
on the
function registered as a step. It can be then reused by defining a new step
with the same or different sentence.
Tools for step writing¶
Useful tools for writing Aloe steps.
See also aloe.world
.
-
aloe.tools.
guess_types
(data)¶ Converts a record or list of records from strings contained in outlines, table or hashes into a version with the types guessed.
Parameters: data – a Scenario.outlines
,Step.table
,Step.hashes
or any otherlist
, list of lists or list of dicts.Will guess the following (in priority order):
The function operates recursively, so you should be able to pass nearly anything to it. At the very least basic types plus
dict
and iterables.
-
aloe.tools.
hook_not_reentrant
(func)¶ Decorate a hook as unable to be reentered while it is already in the stack.
Any further attempts to enter the hook before exiting will be replaced by a no-op.
This is generally useful for step hooks where a step might call
Step.behave_as()
and trigger a second level of step hooks i.e. when displaying information about the running test.
Writing good BDD steps¶
It’s very easy with BDD testing to accidentally reinvent Python testing using a pseudo-language. Doing so removes much of the point of using BDD testing in the first place, so here is some advice to help write better BDD steps.
Avoid implementation details
If you find yourself specifying implementation details of your application that aren’t important to your behaviors, abstract them into another step.
Implementation:
When I fill in username with "danni" And I fill in password with "secret" And I press "Log on" And I wait for AJAX to finish
Behavioral:
When I log on as "danni" with password "secret"
You can use
Step.behave_as()
to write a step that chains up several smaller steps.Implementation:
Given the following flags are set: | flags | | user_registration_disabled | | user_export_disabled |
Behavioral:
Given user registration is disabled And user export is disabled
Remember you can generate related steps using a loop.
for description, flag in ( ... ): @step(description + ' is enabled') def _enable_flag(self): set_flag(flag, enabled=True) @step(description + ' is disabled') def _disable_flag(self): set_flag(flag, enabled=False)
Furthermore, steps that are needed by all features can be moved to a
each_example()
callback.If you want to write reusable steps, you can sometimes mix behavior and declaration.
Then I should see results: | Business Name (primaryText) | Blurb (secondaryText) | | Pet Supplies.com | An online store for… |
Avoid conjunctions in steps
If you’re writing a step that contains an and or other conjunction consider breaking your step into two.
Bad:
When I log out and log back in as danni
Good:
When I log out And I log in as danni
You can pass state between steps using
world
.Support natural language
It’s easier to write tests if the language they support is natural, including things such as plurals.
Unnatural:
Given there are 1 users in the database
Natural:
Given there is 1 user in the database
This can be done with regular expressions.
@step('There (?:is|are) (\d+) users? in the database')