Behavior Driven Development
Behavior Driven Development (BDD) is a popular software development methodology. BDD is considered an extension of TDD, and is greatly inspired by Agile practices. The primary reason to choose BDD as your development process is to break down communication barriers between business and technical teams. BDD encourages the use of automated testing to verify all documented features of a project from the very beginning. This is why it is common to talk about BDD in the context of test frameworks (like Codeception). The BDD approach, however, is about much more than testing - it is a common language for all team members to use during the development process.
What is Behavior Driven Development
BDD was introduced by Dan North. He described it as:
outside-in, pull-based, multiple-stakeholder, multiple-scale, high-automation, agile methodology. It describes a cycle of interactions with well-defined outputs, resulting in the delivery of working, tested software that matters.
BDD has its own evolution from the days it was born, started by replacing “test” to “should” in unit tests, and moving towards powerful tools like Cucumber and Behat, which made user stories (human readable text) to be executed as an acceptance test.
The idea of story BDD can be narrowed to:
- describe features in a scenario with a formal text
- use examples to make abstract things concrete
- implement each step of a scenario for testing
- write actual code implementing the feature
By writing every feature in User Story format that is automatically executable as a test we ensure that: business, developers, QAs and managers are in the same boat.
BDD encourages exploration and debate in order to formalize the requirements and the features that needs to be implemented by requesting to write the User Stories in a way that everyone can understand.
By making tests to be a part of User Story, BDD allows non-technical personnel to write (or edit) Acceptance tests.
With this procedure we also ensure that everyone in a team knows what has been developed, what has not, what has been tested and what has not.
The ubiquitous language is always referred as common language. That is it’s main benefit. It is not a couple of our business specification’s words, and not a couple of developer’s technical terms. It is a common words and terms that can be understood by people for whom we are building the software and should be understood by developers. Establishing correct communication between this two groups people is vital for building successful project that will fit the domain and fulfill all business needs.
Each feature of a product should be born from a talk between
- business (analysts, product owner)
which are known in BDD as “three amigos”.
Such talks should produce written stories. There should be an actor that doing some things, the feature that should be fulfilled within the story and the result achieved.
We can try to write such simple story:
As we can see this simple story highlights core concepts that are called contracts. We should fulfill those contracts to model software correctly. But how we can verify that those contracts are being satisfied? Cucumber introduced a special language for such stories called Gherkin. Same story transformed to Gherkin will look like this:
Cucumber, Behat, and sure, Codeception can execute this scenario step by step as an automated test.
Every step in this scenario requires a code which defines it.
Let’s learn some more about Gherkin format and then we will see how to execute it with Codeception:
Whenever you start writing a story you are describing a specific feature of an application, with a set of scenarios and examples describing this feature.
Feature file is written in Gherkin format. Codeception can generate a feature file for you.
We will assume that we will use scenarios in feature files for acceptance tests, so feature files to be placed in
acceptance suite directory:
Generated template will look like this:
This template can be fulfilled by setting actor and goals:
Next, we will describe this feature by writing examples for it
Scenarios are live examples of feature usage. Inside a feature file it should be written inside a Feature block. Each scenario should contain its title:
Scenarios are written in step-by-step manner using Given-When-Then approach. At start, scenario should describe its context with Given keyword:
Here we also use word And to extend the Given and not to repeat it in each line.
This is how we described the initial conditions. Next, we perform some action. We use When keyword for it:
And in the end we are verifying our expectation using Then keyword. The action changed the initial given state, and produced some results. Let’s check that those results are what we actually expect.
We can test this scenario by executing it in dry-run mode. In this mode test won’t be executed (actually, we didn’t define any step for it, so it won’t be executed in any case).
Besides the scenario steps listed we got the notification that our steps are not defined yet.
We can define them easily by executing
gherkin:snippets command for the given suite:
This will produce code templates for all undefined steps in all feature files of this suite.
Our next step will be to define those steps and transforming feature-file into valid test.
To match steps from a feature file to PHP code we use annotation which are added to class methods.
By default Codeception expects that all methods marked with
Each annotation should contain a step string.
Steps can also be matched with regex expressions. This way we can make more flexible steps
Please note that regular expressions should start and end with
/ char. Regex is also used to match parameters and pass them as arguments into methods.
Parameters can be also passed in non-regex strings using “:” params placeholder.
This will match any word (passed in double quotes) or a number passed:
Steps are defined in Context files. Default context is an actor class, i.e. for acceptance testing suite default context is
AcceptanceTester class. However, you can define steps in any classes and include them as contexts. It is useful to define steps in StepObject and PageObject classes.
To list all defined steps run
As it was mentioned, feature files is not just a user story.
By writing features in formal language called Gherkin we can execute those scenarios as automated tests.
There is no restrictions in the way how those scenarios are supposed to be tested. Tests can be executed at functional, acceptance, or domain level. However, we will concentrate on acceptance or UI tests in current guide.
As we generated snippets for missing steps with
gherkin:snippets command, we will define them in
Please note that
:num1 placeholder can be used for strings and numbers (may contain currency sign).
In current case
$num1 is assigned to be 600. If you need to receive exact string, wrap the value into quotes:
By default they throw Incomplete exceptions to ensure test with missing steps won’t be accidentally marked as successful. We will need to implement those steps. As we are in acceptance suite we are probably using PHPBrowser or WebDriver modules. This means that we can use their methods inside Tester file, as we do with writing tests using
$I->. You can use
see methods inside a step definitions, so each Gherkin scenario step to be extended with basic Codeception steps. Let’s show how it can be implemented in our case:
To make testing more effective we assumed that we are using one of the ActiveRecord frameworks like Laravel, Yii, or Phalcon so we are able to dynamically create records in database with
haveRecord method. After that we are opening browser and testing our web pages to see that after selecting those products we really see the price was calculated correctly.
We can dry-run (or run) our feature file to see that Given/When/Then are expanded with substeps:
This way feature file runs just the same as any other Codeception test. Substeps give us detailed information of how the scenario is being executed.
One of the criticism for testing with Gherkin was that only technical team were aware of how the test scenario is executed. This could have lead to false-positive tests. Developers could have used empty steps for scenarios (or irrelevant ones) and produced invalid tests for valid scenarios. Codeception brings communication to a next level, everyone in a team can understand what happens on a lower (technical) level. Scenario expanding to substeps shows the actual test execution process. Anyone in a team can read the output, and invest their efforts into improving the test suite.
Let’s improve our BDD suite by using the advanced features of Gherkin language.
If a group of scenarios have the same initial steps, let’s that for dashboard we need always need to be logged in as administrator. We can use Background section to do the required preparations and not to repeat same steps across scenarios.
Steps in background are defined the same way as in scenarios.
Scenarios can become more descriptive when you represent repeating data as tables. Instead of writing several steps “I have product with :num1 $ price in my cart” we can have one step with multiple values in it.
Tables is a recommended ways to pass arrays into test scenarios.
Inside a step definition data is stored in argument passed as
In case scenarios represent the same logic but differ on data, we can use Scenario Outline to provide different examples for the same behavior. Scenario outline is just like a basic scenario with some values replaced with placeholders, which are filled from a table. Each set of values is executed as a different test.
Text values inside a scenarios can be set inside a
This string is passed as a standard PHP string parameter
Gherkin scenarios and features can contain tags marked with
@. Tags are equal to groups in Codeception.
This way if you define a feature with
@important tag, you can execute it inside
important group by running:
Tag should be placed before Scenario: or before Feature: keyword. In the last case all scenarios of that feature will be added to corresponding group.
As we mentioned earlier, steps should be defined inside context classes. By default all the steps are defined inside an Actor class, for instance,
AcceptanceTester. However, you can include more contexts. This can be configured inside global
codeception.yml or suite configuration file:
AdditionalSteps file should be accessible by autoloader and can be created by
Codeception\Lib\Di. This means that practically any class can be a context. If a class receives an actor class in constructor or in
_inject method, DI can inject it into it.
This way PageObjects, Helpers and StepObjects can become contexts as well. But more preferable to include context classes by their tags or roles.
If you have
Step\Admin class which defines only admin steps, it is a good idea to use it as context for all features containing with “As an admin”. In this case “admin” is a role and we can configure it to use additional context.
Contexts can be attached to tags as well. This may be useful if you want to redefine steps for some scenarios. Let’s say we want to bypass login steps for some scenarios loading already defined session. In this case we can create
Step\FastLogin class with redefined step “I am logged in as”.
Migrating From Behat
While Behat is a great tool for Behavior Driven Development, you still may prefer to use Codeception as your primary testing framework. In case you want to unify all your tests (unit/functional/acceptance), and make them be executed with one runner, Codeception is a good choice. Also Codeception provides rich set of well-maintained modules for various testing backends like Selenium Webdriver, Symfony, Laravel, etc.
If you decided to run your features with Codeception, we recommend to start with symlinking your
features directory into one of the test suites:
Then you will need to implement all step definitions. Run
gherkin:snippets to generate stubs for them.
By default it is recommended to place step definitions into actor class (Tester) and use its methods for steps implementation.
Tests vs Features
It is common to think that BDD scenario is equal to test. But it’s actually not. Not every test should be described as a feature. Not every test is written to test real business value. For instance, regression tests or negative scenario tests are not bringing any value to business. Business analysts don’t care about scenario reproducing bug #13, or what error message is displayed when user tries to enter wrong password on login screen. Writing all the tests inside a feature files creates informational overflow.
In Codeception you can combine tests written in Gherkin format with tests written in Cept/Cest/Test formats. This way you can keep your feature files compact with minimal set of scenarios, and write regular tests to cover all cases.
Corresponding features and tests can be attached to the same group. And what is more interesting, you can make tests to depend on feature scenarios. Let’s say we have
login.feature file with “Log regular user” scenario in it. In this case you can specify that every test which requires login to pass to depend on “Log regular user” scenario:
@depends block you should use test signature. Execute your feature with
dry-run to see signatures for all scenarios in it. By marking tests with
@depends you ensure that this test won’t be executed before the test it depends on.
If you like the concept of Behavior Driven Development or prefer to keep test scenarios in human readable format, Codeception allows you to write and execute scenarios in Gherkin. Feature files is just another test format inside Codeception, so it can be combined with Cept and Cest files inside the same suite. Steps definitions of your scenarios can use all the power of Codeception modules, PageObjects, and StepObjects.