November 8, 2021

The Cheezy Internet: Composing tests using Cucumber and Gherkin

This is fourth in a series of blog posts. Care to go back to the beginning?

Following along with Jeff "Cheezy" Morgan's eBook, "Cucumber and Cheese: A Tester's Workshop" (2017), after setting up a development environment, we started creating in Ruby + Watir the basic building blocks for an automated test framework, as we saw in the last Adventures in Automation blog entry. 

Instead of going into great detail testing against the complex test site Jeff uses in Chapter 4: Cucumbers and Puppies of is book, with Sally's Puppy Adoption Agency, we are using the simpler test site, The-Internet https://the-internet.herokuapp.com/login, by Dave Haeffner. 

This blog post will explore scaffolding a site and composing acceptance tests using Cucumber and Gherkin. 

Scaffolding a Site Using TestGen

It's always a challenge for me when creating a new project to figure out where everything should go. What should the folder and file structure be? Luckily, Jeff Morgan created a Ruby gem called "testgen" which solves all of these problems. 
  • Change the directory to the src folder in your home directory: cd ~/src
  • Pick a name for your project, such as "cheezy_internet"
  • Install Jeff Morgan's Ruby gem testgen on your local machine: gem install testgen 
  • Use the Ruby gem testgen to create a new file hierarchy: testgen project cheezy_internet 

The following files will be created:
PS C:\Users\tmaher\src> testgen project cheezy_internet  
    create cheezy_internet  
    create cheezy_internet/cucumber.yml  
    create cheezy_internet/Gemfile  
    create cheezy_internet/Rakefile  
    create cheezy_internet/features  
    create cheezy_internet/features/support  
    create cheezy_internet/features/step_definitions  
    create cheezy_internet/features/support/env.rb  
    create cheezy_internet/features/support/hooks.rb  
    create cheezy_internet/features/support/pages  
  • Go into the new project folder that was created: cd cheezy_internet
  • Install everything with: bundle install

You can then use VS Code to open the new "cheezy_internet" folder. 

Creating New Test Features in Cucumber

From Cucumber.io / Docs / Overview: "Cucumber is a tool that supports Behaviour-Driven Development(BDD). If you’re new to Behaviour-Driven Development read our BDD introduction first.

"Cucumber reads executable specifications written in plain text and validates that the software does what those specifications say. The specifications consists of multiple examples, or scenarios. [...]

"Each scenario is a list of steps for Cucumber to work through. Cucumber verifies that the software conforms with the specification and generates a report indicating ✅ success or ❌ failure for each scenario. In order for Cucumber to understand the scenarios, they must follow some basic syntax rules, called Gherkin.

"[...] Step definitions connect Gherkin steps to programming code. A step definition carries out the action that should be performed by the step. So step definitions hard-wire the specification to the implementation".

Features Craft Step Definitions

"The purpose of the Feature keyword is to provide a high-level description of a software feature, and to group related scenarios.

"The first primary keyword in a Gherkin document must always be Feature, followed by a : and a short text that describes the feature.

"You can add free-form text underneath Feature to add more description.

"These description lines are ignored by Cucumber at runtime, but are available for reporting (they are included by reporting tools like the official HTML formatter)". - Cucumber.io / Gherkin Reference

Let's say we want to write about the Login feature of The-Internet. We can create a new file under the "features" folder called "Login.feature".

Login.feature
Feature: User Logging into Secure Area

    As an authorized user
    I want to be able to log im
    So that I can access the secure area

    Scenario: Valid User
        Given I arrive on the Login screen
        When I log in using valid credentials
        Then I should arrive at the Secure Area


We can run this features, if we are on the main "cheezy_internet" folder from the command line:

  • cucumber features\Login.feature
Since we don't yet have any step_definitions yet, it gives us samples to go by. If you wanted to you could create a new Ruby file under "step_definitions", and copy and paste the Given, When and Then. 

 1 scenario (1 undefined)  
 3 steps (3 undefined)  
 0m1.906s  
 You can implement step definitions for undefined steps with these snippets:  
 Given('I am on the Login screen') do  
  pending # Write code here that turns the phrase above into concrete actions  
 end  
 When('I log in using valid credentials') do  
  pending # Write code here that turns the phrase above into concrete actions  
 end  
 Then('I should arrive at the Secure Area') do  
  pending # Write code here that turns the phrase above into concrete actions  
 end  

Refine The Feature File


Let's say we wanted to explicitly feed in the username and password into the test, so that we could share the test among many different sets of valid credentials, we could rewrite the feature file: 

Feature: Logging into Secure Area

    As an authorized user
    I want to be able to log im
    So that I can access the secure area

    Scenario: Valid User Login
        Given I am on the Login screen
        When I log in using "tomsmith" and "SuperSecretPassword!"
        Then I should arrive at the Secure Area
        And I should see "Welcome to the Secure Area"

Running the test, and copying-and-pasting into a new file called "login_steps.rb" under the "step_definitions" folder, we get: 

login_steps.rb
Given('I am on the Login screen') do
    pending # Write code here
  end
 
  When('I log in using {string} and {string}') do |string, string2|
    pending # Write code here
  end
 
  Then('I should arrive at the Secure Area') do
    pending # Write code here
  end
 
  Then('I should see {string}') do |string|
    pending # Write code here
  end


Parts of a Step Definition File

Cucumber.io / Gherkin Reference: "Given steps are used to describe the initial context of the system - the scene of the scenario. It is typically something that happened in the past.

"When Cucumber executes a Given step, it will configure the system to be in a well-defined state, such as creating and configuring objects or adding data to a test database.

"The purpose of Given steps is to put the system in a known state before the user (or external system) starts interacting with the system (in the When steps). Avoid talking about user interaction in Given’s. If you were creating use cases, Given’s would be your preconditions [...]

"When steps are used to describe an event, or an action. This can be a person interacting with the system, or it can be an event triggered by another system.

"It’s strongly recommended you only have a single When step per Scenario. If you feel compelled to add more, it’s usually a sign that you should split the scenario up into multiple scenarios. [...]

"Then steps are used to describe an expected outcome, or result.

"The step definition of a Then step should use an assertion to compare the actual outcome (what the system actually does) to the expected outcome (what the step says the system is supposed to do).

"An outcome should be on an observable output. That is, something that comes out of the system (report, user interface, message), and not a behaviour deeply buried inside the system (like a record in a database)".

Fill Out The Step Definition


Let's say we enter the browser test code we came up with in the last blog entry, we get:

login_steps.rb
Given('I am on the Login screen') do
    @browser = Watir::Browser.new :chrome
    @browser.goto 'https://the-internet.herokuapp.com/login'
  end
 
When('I log in using {string} and {string}') do |username, password|
  @browser.text_field(id: 'username').set(username)
  @browser.text_field(id: 'password').set(password)
  @browser.button(value: 'Login').click
end
   
Then('I should see {string}') do |text|
    fail "Expected text not found: #{text}" unless @browser.text.include? text
end


Hrm. The "Then" clause could be better.  Let's use the "expect" RSpec matcher:

Then('I should see {string}') do |expected|
    expect(@browser.text).to include expected
end

Going back to the Given and When clause, we can also replace the parentheses and single quotes with regular expressions and Capture groups.

What are Capture Groups in Cucumber?


Jeff Morgan's "Cucumbers and Cheese":

"Cucumber has something called Capture Groups. This is simply anything that is surrounded by
parentheses. Whatever matches the capture group is placed into a variable passed to the block.
Let’s look at an example. In the previous chapter we had the following step:

"When /^I enter "([^"]*)" in the address field$/ do |address|

"The capture group is ([^"]*) and the value found there is placed in the address parameter.

"We forced this to happen by placing the double quotes around the value in the feature file and
cucumber took this as a clue to create the capture group. The truth is that there is really no need for
the double quotes. If you remove them from both the feature file and step definition you will see
that it works exactly the same.

"The last capture group and regular expression ([^"]*) is somewhat complex. Let’s spend a little
time and see what we can do to simplify it. Here are a few simple things to learn to help you write
simple regular expressions for cucumber.

• "The . character will match any character. For example .. matches “at” and “on” but it doesn’t
match “off” since it is three characters.
• "The * character means zero or more of the previous element so ab* matches “ab”, “abb” and
“a”. a.* matches “a”, “ab”, “abb”, “ac”, etc.
• "The + character means one or more of the previous element so ab+ matches “ab”, “abb” but
does not match “a”. a.+ matches “ab”, “abb”, “ac” but does not match just “a”.
• "Character classes are any set of characters contained within []. For example, [0123456789]
matches any number. [0-9] is shorthand for this example. Another common character class
is [A-Za-z] to represent any alpha character.
• "There are shorthand expressions for character classes. Here are a few: \d is equal to [0-9], \w
is equal to [A-Za-z0-9_], \s is equal to [ \t\r\n\v\f]".

"[^"]*"matches something (or nothing) in double quotes

login_steps:
Given /^I am on the Login screen$/ do
    @browser = Watir::Browser.new :chrome
    @browser.goto 'https://the-internet.herokuapp.com/login'
  end
 
When /^I log in using "([^"]*)" and "([^"]*)"$/ do |username, password|
  @browser.text_field(id: 'username').set(username)
  @browser.text_field(id: 'password').set(password)
  @browser.button(value: 'Login').click
end
   
Then /^I should see "([^"]*)"$/ do |expected|
    expect(@browser.text).to include expected
end

Create a Scenario Outline

What if there were many different valid usernames and passwords you could pick and choose from? 

From Cucumber.io / docs / gherkin: "The Scenario Outline keyword can be used to run the same Scenario multiple times, with different combinations of values [...]. Copying and pasting scenarios to use different values quickly becomes tedious and repetitive [...] We can collapse [...] similar scenarios into a Scenario Outline.

"Scenario outlines allow us to more concisely express these scenarios through the use of a template with < >-delimited parameters"

Cucumber allows us to create a Scenario Outline, where data can be fed into the test, executing once per row of test data. 


Feature: Logging into Secure Area

    As an authorized user
    I want to be able to log in
    So that I can access the secure area

    Scenario Outline: Valid User Login
        Given I am on the Login screen
        When I log in using "<username>" and "<password>"
        Then I should see "Welcome to the Secure Area"

    Examples:
        | username | password             |
        | tomsmith | SuperSecretPassword! |  



Now, we have a (mostly) complete feature file, and an improved step definition file. 

With the next blog article, we will explore adding Page Objects into our little project. 


Happy Testing!

-T.J. Maher
Sr. QA Engineer, Software Engineer in Test
Meetup Organizer, Ministry of Testing - Boston

Twitter | YouTubeLinkedIn | Articles

1 comment:

Kaylee Brown said...

Accounting is a subject that demands much more than subject knowledge like marketing mathematics statistics of the few. So even if you are very attentive in your class, chances are you might have missed any of the lectures on the above subjects, and thus it is becoming very challenging for you to complete those accounting assignments. It is okay to look for accounting homework assignments online as they can help you fight the stress you feel due to the submission. Finding an accounting assignment help is not easy. Still, with a little due diligence, you are sure to get a finished copy that is authentically made for you by an accounting expert, this way; you will have a better chance of building the things you love to accomplish.