Gherkin - simply re-use Given statements as When statements... acceptable?
Asked Answered
C

2

8

Here are three example BDD statements that should help explain my question:

Scenario: User logs in
Given I am on the login screen
When I enter the valid username "myUsername"
And I enter the valid password "myPassword"
And I press the login button
Then I should see the login successful page

Scenario: User buys a product
Given I am logged into the system using username "myUsername" and "myPassword"
When I purchase the product "myProduct"
Then I should have "myProduct" in the product inventory

vs

Scenario: User buys a product
Given I am on the login screen
And I enter the valid username "myUsername"
And I enter the valid password "myPassword"
And I press the login button
When I purchase the product "myProduct"
Then I should have "myProduct" in the product inventory

So scenario 1 above is fine, but which is best out of statement 2 and 3. Statement 2 reads nicely and more concisely. But my step definition for "Given I am logged into the system using username "myUsername" and "myPassword"" will need to repeat calls to the Page Objects (or equivalent) that scenario 1 called... seems like more dev effort.

So really just wondering if anyone knows which is best practise. I have searched online and found the following document: http://docs.behat.org/guides/1.gherkin.html

This suggestions scenario 2 is best, but then writes: "Authenticate a user (An exception to the no-interaction recommendation. Things that “happened earlier” are ok)" which kinda lends itself to scenario 3.

Cheers,

Charlie

Cesspool answered 10/1, 2014 at 16:31 Comment(0)
F
12

Here is my review of the scenarios you've written.

Scenario 1

Scenario: User logs in
Given I am on the login screen
When I enter the valid username "myUsername"
And I enter the valid password "myPassword"
And I press the login button
Then I should see the login successful page

Pros : You are correctly using the Given, When and Then statements. In this scenario the Given sets the initial state of the system, the When indicates actions which a user will take and the Then details the assertions made to verify the behaviour of the system.

Cons : Whilst what you have written will work, the problem you have is that this test is brittle. If your company was to mandate that a time-dependent security token also had to be specified during log-in (for example), you'd have to add another step to input this additional field. However if you rewrote this step to be declarative e.g.

Given I am on the login screen
When I submit valid log-in criteria
Then I should see the login successful page

Then if the log-in process was changed, you would only need to alter the code, the scenario would remain the same.

Scenario 2

Scenario: User buys a product
Given I am logged into the system using username "myUsername" and "myPassword"
When I purchase the product "myProduct"
Then I should have "myProduct" in the product inventory

Pros : Same as above.

Cons : Again the test is brittle as it's imperative i.e. you are specifying the exact log-in credentials and a specific product. I'd re-write this as:

Given I am logged into the system
When I purchase a product
Then I should have that product in the product inventory

You can save the product specified in the "When" step in ScenarioContext.Current. You would then be able to re-use this value in your "Then" step to assert that it is present in the product inventory.

Scenario 3

Scenario: User buys a product
Given I am on the login screen
And I enter the valid username "myUsername"
And I enter the valid password "myPassword"
And I press the login button
When I purchase the product "myProduct"
Then I should have "myProduct" in the product inventory

Cons : This is the worst of your scenarios as you are incorrectly using the Given statement. A Given statement should be used to define an initial system state for the test, so in this case "Given I am on the login screen" is a correct use, but "Given I enter the valid username "myUsername"" is an incorrect use. It is incorrect as it is indicating a user action, hence it should be covered by a When. Yes, you can use a Given to perform the same programmatic steps as a When, but it doesn't make it right!

I'd change this scenario to the version I suggested in Scenario 2.

Feeding answered 12/1, 2014 at 0:31 Comment(1)
Thanks for this Fresh. Just to add to for anyone wondering about the 'code resuse' part of my query. If a Given step definition has actions made up of steps covered in other When step definitions, I simply create a private method in the step definition (or elsewhere if used across definitions) that both the Given and When statement can call as required. This abolishes the need for copying and pasting the code :-)Cesspool
D
1

Firstly, there is absolutely nothing in Gherkin that prevents you from writing specifications in any order, you can even produce compound specifications such as

Given ...
When... 
Then ...
When ...
Then ...
Given ...
When ... 
Then ...

So why would you want to do this?

Well first lets consider a fourth variant of your scenario

Scenario: User logs in and buys a product
  Given I am on the login screen
  When I enter the valid username "myUsername"
  And I enter the valid password "myPassword"
  And I press the login button
  Then I should see the login successful page
  When I purchase the product "myProduct"
  Then I should have "myProduct" in the product inventory

This is of course just a compound of 1 and 2. You might have written this because you had finished testing login and wanted to quickly write and test buying a product. You already have the code for the Bindings in scenario one and now simply need to write the bindings for scenario two. You might consider this your simplest pragmatic change, and you will refactor it later. This nothing wrong with it, running the tests could be quicker, but also its not exactly ideal.

Now imagine that due to the nature of your shop you have written many tests that test different buying processes. We could be testing what happens when you add the same item again to your basket, or if you try to buy something out of stock, or different checkout experiences if you want special delivery. In fact your shop is so successful that you need to be really secure on the web and change your login process. Unfortunately you now have those three lines

Given I am on the login screen
When I enter the valid username "myUsername"
And I enter the valid password "myPassword"

repeated throughout your scenarios. If we accidentally break our login process with some bad code, suddenly all of our tests fail. You are presented with a wall of red and can't really narrow down where to start looking at the problems. Because we are dependant on logging in before we run our scenario we cannot isolate login from purchase.

This really is the difference between a Given and a When. A When is there to indicate that we are performing a process, a Given is a means of directly affecting the run time environment so that we simply have the correct state. It's basically the difference between

//Given
isLoggedIn = true

//When
if CheckValidPasswordForUser(user, password)
   isLoggedIn = true

The Given has no way to fail.

So coming all the way back to your original question,

Can you re-use Givens as Whens? Yes, but it in the long term it will confuse you.

But if you ask Can you re-use Whens as Givens? then I would definitely advise you not to. This will hide the fact that something that could break is being tested.

Finally there is one other thing to consider and that is the domain of your specification. Dan North has a really good article on this Whose Domain is it anyway?, but the general gist as applied to your example here is that when you are looking at product buying you can simply write

Given I am logged in as a customer

or

Given I am logged in as an administrator

because the username and password parts are to with login and not products, and that way you are protected from re-writing all your scenarios when you change your login process to use something else.

Dorn answered 11/1, 2014 at 10:30 Comment(2)
Thanks for the comments AlSki. I agree with your points, but does this approach mean I need to rewrite my login code both in the low-level Whens (enter password, enter username, etc) and the high-level Givens (I am logged in)? I know an alternative is to use my When steps within the Given step definition but I have been warned about that as potential spaghetti code creation.Cesspool
Its not so much re-write youu code,as possibly re-write your bindings. The idea is that although you have two different sets of code in the Given and the When, the code in the Given is simply a shortcut, in this case we directly set the internal state of the running program so it has a properly logged in state, without having to call complicated authorisation code that could fail.Dorn

© 2022 - 2024 — McMap. All rights reserved.