Some personal impressions.
Behavior Driven Development (BDD), which combines the general techniques and principles of TDD with ideas from domain-driven design and object-oriented analysis and design, is already a well known approach in software development. There are a lot of good books and articles about it and my intention is not to summarize all this information neither somehow to glorify BDD – it is just a tool and, like every other tool, it can be very convenient if properly used. I will just mention some of the aspects which for me are the most important and I found really useful when I was supposed to use, read, write and automate BDD scenarios.
1. Intuitive and easy to understand.
When talking about BDD we understand a documentation written in a form Given-When-Then scenarios in a language known as Gherkin. Using this so called “Specifications by Example” (with a reference to the book with the same name by Gojko Adzic) whose idea is to use business language without a lot of specialized technical concepts, turns the boring documents in something really comprehensible and makes working with such documents almost fun. Of course, technical people like software developers involved in the process of writing scenarios are usually tempted to use words hard for understanding by the wider audience. One way to avoid this is the approach known as “Tres amigos”. This means that at least one representative of all 3 stakeholders – software developers, QA, business (business analyst, product owners, etc.) have to be involved. Thereby all different perspectives for solving the issue, are represented.
Example: Let’s assume we have a simple registration form where the user haс to choose a username and password and the password have to contain at least one big letter, one small letter and a number. Here are some of the scenarios that can be written for that case.
Given there is a user
And the user is filling in the registration form
And the user enters admin for username
And the user enters Pass1 for password
And the username admin is not existing in the system
When the user clicks the submit button
Then credentials for the user are accepted
And the user is redirected to the confirmation page
Given there is a user
And the user is filling in the registration form
And the user enters admin for username
And the user enters Pass1 for password
And the username admin is existing in the system
When the user clicks the submit button
Then an error message “Username admin is already existing in the system” is displayed
Given there is a user
And the user is filling the registration form
And the user enters admin for username
And the user enters pass1 for password
And the username admin is not existing in the system
When the user clicks the submit button
Then an error message “Password doesn’t cover some of the requirements. . .”
is displayed
We have to write a few more scenarios to fully cover the possible cases for that simple feature (other combinations for the password, empty username and password, using different languages and special symbols, etc) but these are quite enough to illustrate the idea. Notice that the words in italic – admin, pass1, etc – are passed and used as parameters in the automation functions (we will see how exactly a bit later). For finishing our introduction of how the scenarios are organized, I will mention that a set of scenarios about a feature, or a group of connected features, or in some cases – a significant part of a big feature are grouped in a feature file, which starts with a short description, following the manner above:
In order to give the users option to register to my system
As a web site owner
I want a registration page form
There are few small variations in this syntax, but the main idea is that this way we show what is the benefit of the feature (“In order to”), who wants it (“As a”) and how exactly shall we achieve it (“I want”).
2. Living documentation.
How can the documentation be living? Obviously it is not a matter of sheets of papers and documents floating around on their own but some useful, well written and easy to understand documentation, describing every single feature in the system under development (under testing) by giving simple examples of all the possible inputs, actions and outputs. The main characteristic of each “living” thing is that it reacts to every change in the surrounding environment. Thus the living documentation can be extended, edited, shrunk, restructured according to the new functional requirements – we can add new scenarios to an existing feature file, add some lines in existing scenario, change the input (Given) and/or output (Then) in one or some of the scenarios, add entirely new scenarios, features etc. But no matter what we do, if we are careful and use the appropriate tools (which are pretty enough) our documentation will be actual at every moment, easy to read, easy to be actualised, and a just reflection of our product.
If we take the example from above, the requirements at the beginning were the user to choose username, which does not exist in the system, and a password, with some constraints. But later the client wants to add another field for users email address, which will be used for confirmation of the registration and the confirmation page has to be extended with a text-box for entering the confirmation code, received by e-mail. So our first scenario has to be extended or even split in 2 new scenarios like this:
Given there is a user
And the user is on the registration form page
And the user enters admin for username
And the user enters Pass1 for password
And the user enters admin@email.com for email
And the username admin is not existing in the system
When the user clicks the submit button
Then credentials for the user are accepted
And the user is redirected to the confirmation page
Given there is a user
And the user is on the confirmation page
And the user enters the confirmation code
When the user clicks the submit button
Then the user is logged in the system
And the user is redirected to the home page
In such a way we have a single focus on our tests/tasks (the scenarios are used both from the developers and QAs). Usually the registration forms we meet in every web site are a bit more complicated, with a lot and different data required from us, with different combinations of mandatory and non-mandatory fields. One way to present this in a scenario is to extend our precondition part with more “Given” lines (each And after the first “Given” is presented in the system as “Given” – we use “And” just for readability. It is the same for the “Then”s and “When”s, but usually it is not a good practice to use more than one “When” – it is violating the rule for single focus of the test) but a scenario with 10 or more “Given”s doesn’t look so pretty. That is why we can use parameter tables:
Given there is a user
And the user is on the registration page
And the user enters following data in the form:
| Username | Password | Emai l | City | Address |
| admin | Pass1$ |admin@admin.com | Sofia | Main Str 1 |
. . . . . . .
The same can be applied for the “Then”s. But usually (according to my practice) every “Then” has to be on a separate line because it is a separate postcondition which can be checked separately. Of course, there can be exceptions from this rule (for example if the assertions are for some logically connected data). There are no strict rules on how we can write our scenarios – it depends on the adopted domain language and internal team rules.
3. Better structuring of the tests.
It is time to take a quick look behind the scenarios and how they match to the automation logic. Every line – no matter “Given”, “When” or “Then” matches single function in the code. This is done by annotations (Attributes in .NET) which use regular expressions:
@Given(“there is a user”) public void givenThereIsAUser(){ User user=GetUser(); } @Given (“the user is on the registration form page”) public void userIsOnRegistrationPage(){ webDriver.goToRegistrationPage(); } @Given(“the user enters admin for username”) public void userEnetersAUserName(){ WebElement userNameTextBox =driver.findElement(By.Id(“usenameTextBox”)); userNameTextBox.sendKeyes(“admin”); }
Notice that the function’s names are theoretically irrelevant (usually the tools are making them the same as the matched scenario line, when making them automatically) but it is always a good idea to give them some more generic or appropriate name. For example, our last “Given” from the example above is setting “admin” to the textbox for username. But we all will agree that in most of the cases such “specialised” function is not a great benefit for any program. We want to parameterize this function. It can be done in the following manner:
@Given(“the user enters .* for username”) public void userEnetersAUserName(String username){ WebElement userNameTextBox =driver.findElement(By.Id(“usenameTextBox”)); userNameTextBox.sendKeyes(username); }
The word admin from the notation is replaced by regular expression (in this case – the simplest one) and it is automatically recognised and passed as a parameter in the function. Parameters for one line are limited only by count of the words in the line. Notice that the only automatically recognised parameter types are String and int and for the others we have to apply some techniques, known as “String transformations”. Some points about the regular expressions:
- It’s a good practice to make a bit more complex regular expressions. In the example above, if in a different scenario there is a line “Given the user enters some invalid data for username”, “some invalid data” will match our regular expression and will be passed as a parameter. It is a good idea at least to make our expression in a way to match a single word if possible.
- Using the mentioned above “String transformation”, it is really useful to transform words like “is”, “isn’t”, “are”, “aren’t” in booleans (is=true, isn’t=false e.t.c.)
- It is not a good idea for our regular expressions to match positive and negative conditions (“is” and “isn’t”). Doing this will mix positive and negative test cases in one function, which is a really bad idea.
The tools used – maybe the most popular ones are Cucumber and SpecFlow (known as Cucumber for .NET) – have really good intellisense for writing the BDD scenarios. It helps a lot in the finding of already written functions or, at least, reminds us for some similar ones, but it is not a reason to not build a good infrastructure for our feature files, test classes, helpers, data access classes and so on. While writing the scenarios in a business language there is always a chance to say something in a few different ways. Good way to avoid such things is good names for our feature files, distributed in packages and folders and common domain language for the scenarios for all the people using them.
We also have to pay attention to the size of the test project. I have worked with such a big test project (with about 10 000 tests and a lot of duplicated and unused code) that the intellisense IDE was needing about an hour to load all the functions and intellisense to become usable. We were pressured to go back to the good old Ctrl+Shift+F
By using well written scenarios every stakeholder can be informed (not only the developers and QA but business people and even the clients) at every time what the product is doing and which part of it is covered of tests (manual or automated).
4. Better test (code) reusing – As mentioned in the previous point, well structured and tidy code, as well as the use of intellisense, are also prerequisites for better code reusing. We can easily notice that a part of our test – precondition (Given), action (When) or Postcondition/Test check (Then) – is already written or easily can be extended to cover our new test.
5. As a last point (but one of the best effects, the desert of our information lunch) is the fact, that by writing and covering all the possible scenarios in advance, this really shrinks the number of the bugs, found in the next phases. Having the scenarios the developers of the product know the critical points of the feature (the edge cases) and this has a psychological effect on one hand, and also a ready acceptance criteria on the other.
As a conclusion we could say, that the BDD is the Ferrari among all the approaches and of work under scrum. And If you are not big Tesla fan or at least until something Lamborghini-like does not appear – please, enjoy it!