Test case design is the most important part of test automation since it is the starting point of the automation code. Writing effective and efficient test case can make your code more stable and more successful. For BDD approach, the feature files hold the test cases or scenarios and these scenarios are derived by depending on the acceptance criterias written in the stories. Therefore the feature files should cover all the acceptance criteria plus some more test cases produced from exploratory testing.

For dictionary meaning of imperative is giving authoritative command, and declarative is taking the form of a simple statement according to the Oxford dictionary. My understanding from this explanation is that you can say "do this ..." as an imperative way or you can say "this is like ..." as an declarative way. Check this funny but declarative examples from here. Lets use these basic information for our field as software engineer and derive test scenarios:
An imperative examples(How):
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Scenario: User should see greeting message for succesfull registeration | |
When user enters "Name" as "John" | |
And user enters "Surname" as "Doe" | |
And user enters "Email" as "johndoe@testrisk.com" | |
And user enters "Password" as "Pass123456" | |
And user selects gender as "Male" | |
And user selects "Birtday" as "1990" | |
And user checks terms and conditions | |
And user clicks at "Register" button | |
Then user should see "Welcome" pop-up |
correspondingly step definition should like the following:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
When(/^user enters "(.*?)" as "(.*?)"$/) do |field, text| | |
enter_text IDs.items[field] text | |
end | |
When(/^user selects gender as "(.*?)"$/) do |field| | |
touch IDs.items[field] | |
end | |
When(/^user selects "(.*?)" as "(.*?)"$/) do |list, value| | |
select_from_list IDs.items[list] value | |
end | |
When(/^user checks terms and conditions$/) do | |
touch IDs.items["Terms and Condition"] | |
end | |
When(/^user clicks at "(.*?)" button$/) do |button| | |
touch IDs.items[button] | |
end | |
Then(/^user should see "(.*?)" pop\-up$/) do |popup| | |
expect(element_exists(IDs.items[popup]).to be true) | |
end |
An declarative examples(What):
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Scenario: User should see greeting message for succesfull registration | |
When user submits the registration form with valid user information | |
Then user should see "Welcome" pop-up |
correspondingly step definition should like the following:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
When(/^user submits the registration form with valid user information$/) do | |
enter_text IDs.items["Name"], "John" | |
enter_text IDs.items["Surname"], "Doe" | |
enter_text IDs.items["Email"], "johdoe@testrisk.com" | |
enter_text IDs.items["Password"], "Password1213" | |
touch IDs.items["Male"] | |
select_from_list IDs.items["Birt"], "1990" | |
touch IDs.items["Terms and Condition"] | |
touch IDs.items["Register"] | |
end | |
Then(/^user should see "(.*?)" pop\-up$/) do |popup| | |
expect(element_exists(IDs.items[popup]).to be true) | |
end |
Prons and cons of imperative approach:
More detail about steps
A kind of micro (steps) management of test cases
More feed backs when failures occur
You can use the features of Cucumber more efficiently
Gives more information to consumer for whole steps
You can produce more reusable steps
Not suitable for generalisation
More documentary work
A bit confusing if you are checking the result of a long list
Prons and cons of declarative approach:
Good for cross-platform testing
Good for generalization
Good if we are interested in summary of test case results
Group of actions, easy to manage
More automation code
Not good for reusable steps
As we can see from the examples difference is related to abstraction level which the imperative approach gives more detail about what is doing for each steps. However this is not the case, important part is that declarative approach explains a case in a general way so it can be suitable for similar product. This could help you if you are writing test cases for different platform of same application. For example, if you are testing a log in form for both iOs and Android, and for both phone and tablet you can see some differences because of the standardisation of the platforms and device specific extra features. Therefore, writing a declarative test case and handling these kind of differences in automation code not in test step can make your .feature files more help full. I think deciding which approach to use is depending on your consumer needs and your automation framework. Best practice should be include both approach for different cases.
Prons and cons of declarative approach:
As we can see from the examples difference is related to abstraction level which the imperative approach gives more detail about what is doing for each steps. However this is not the case, important part is that declarative approach explains a case in a general way so it can be suitable for similar product. This could help you if you are writing test cases for different platform of same application. For example, if you are testing a log in form for both iOs and Android, and for both phone and tablet you can see some differences because of the standardisation of the platforms and device specific extra features. Therefore, writing a declarative test case and handling these kind of differences in automation code not in test step can make your .feature files more help full. I think deciding which approach to use is depending on your consumer needs and your automation framework. Best practice should be include both approach for different cases.