Defining Steps ============== .. toctree:: :maxdepth: 2 .. autofunction:: aloe.step(sentence=None) Common regular expressions for capturing data --------------------------------------------- **String** .. code-block:: gherkin Given I logged in as "alexey" .. code-block:: python @step(r'I logged in as "([^"]*)"') **Number** .. code-block:: gherkin Then the price should be $12.99 .. code-block:: python @step(r'The price should be \$(\d+(?:\.\d+)?)') **Path/URI/etc.** .. code-block:: gherkin Given I visit /user/alexey/profile .. code-block:: python @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 :meth:`unregister()` on the function registered as a step. It can be then reused by defining a new step with the same or different sentence. .. include:: links.rst Tools for step writing ---------------------- .. toctree:: :maxdepth: 2 .. automodule:: aloe.tools :members: 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. 1. **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: .. code-block:: gherkin 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: .. code-block:: gherkin When I log on as "danni" with password "secret" You can use :meth:`Step.behave_as` to write a step that chains up several smaller steps. Implementation: .. code-block:: gherkin Given the following flags are set: | flags | | user_registration_disabled | | user_export_disabled | Behavioral: .. code-block:: gherkin Given user registration is disabled And user export is disabled Remember you can generate related steps using a loop. .. code-block:: python 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 :func:`~aloe.before.each_example` callback. If you want to write reusable steps, you can sometimes mix behavior and declaration. .. code-block:: gherkin Then I should see results: | Business Name (primaryText) | Blurb (secondaryText) | | Pet Supplies.com | An online store for… | 2. **Avoid conjunctions in steps** If you're writing a step that contains an `and` or other conjunction consider breaking your step into two. Bad: .. code-block:: gherkin When I log out and log back in as danni Good: .. code-block:: gherkin When I log out And I log in as danni You can pass state between steps using :attr:`world`. 3. **Support natural language** It's easier to write tests if the language they support is natural, including things such as plurals. Unnatural: .. code-block:: gherkin Given there are 1 users in the database Natural: .. code-block:: gherkin Given there is 1 user in the database This can be done with regular expressions. .. code-block:: python @step('There (?:is|are) (\d+) users? in the database')