• No results found

How the automation layer and the specification layer interact

The specification layer and the automation layer

2.5 How the automation layer and the specification layer interact

2.5.1 Step definitions

Tab

Table 2.2le 2.2 What nmmWhat needeeds to haps to happen unpen under thder the hood te hood to test if ao test if an emain email was senl was sent?t?

Keyword

Keyword The testing code The testing code in the step in the step definitiondefinition

Given establish connection to the database, populate it with some testing data, or configure the application to a desirable state

When simulate sending an email based on the data fetched from the database

Then check if the email was sent by integrating with the mailing service’s internal queue

©Manning Publications Co. We welcome reader comments about anything in the manuscript - other than typos and

50

Figure 2.8 Gherkin features test a simulation of the working system with testing code Figure 2.8 Gherkin features test a simulation of the working system with testing code executed through step

executed through step definitionsdefinitions

Every feature contains multiple scenario that contain multiple steps. These steps have their corresponding step definitions that are responsible for running the automation code that affects a simulation of a system, as well as checking if the code yields expected results.

A middle-sized Gherkin project can contain between 200 and 600 scenarios. If we assume that every scenario has at least three steps, one of each kind, that’s between 600 and 1800 steps in a single specification suite. And please remember that, unlike keywords, a step’s content isn’t predefined; you can write whatever you like as long as it fits within a single sentence and makes sense given the rest of your scenario. All in all, that’s quite a lot of content and a lot of step definitions to execute. How does that happen?

In general, Gherkin files must be parsed by some testing framework that will match steps and their step definitions. That’s precisely what Cucumber does.

First, Cucumber rejects all keywords when matching a step. Cucumber behaves like that because in some advanced cases you might want to write some interchangeable steps, for example you might want a When from one scenario to become a Givenin another scenario. We’ll talk more about why you might want to do so in chapter 3.

Second, Cucumber matches the rest of the step with the testing code in a step definition that matches the step’s name.

2.5.2 Matching steps with step definitions

51

Figure 2.9 The testing code contained in step

Figure 2.9 The testing code contained in step definitions is matched to examples anddefinitions is matched to examples and scenarios through regular expressions executed by the automation engine. Steps and scenarios through regular expressions executed by the automation engine. Steps and their step definitions are inseparably connected. Steps describe the business logic in their step definitions are inseparably connected. Steps describe the business logic in natural language; step definitions allow the automation engine to run the testing code natural language; step definitions allow the automation engine to run the testing code needed to check if the

needed to check if the step’s business logic is implemented correctlystep’s business logic is implemented correctly

From a technical point of view, every step’s content is matched with a step definition within the automation layer—like in a search and replace operation in any text editor you have ever used.

Listing 2.12 The general rule for matching step definition Listing 2.12 The general rule for matching step definition

We can read the expression above as a general rule for "the left hand side can be replaced with the right hand side." When anything that matches the left side—a step—is found, it’ll be replaced with whatever the right side—testing code—contains. Below, you’ll find some simple practical examples with dummy testing code written in Ruby. If you want a more advanced explanation, please check out the sidebar called Executing tests.

Step's content Testing code

©Manning Publications Co. We welcome reader comments about anything in the manuscript - other than typos and

52

SIDEBAR

SIDEBAR Executing testsExecuting tests

Here’s a quick tutorial on how to write and run very simple automated tests for our feature file with Cucumber and Gherkin. I put it in a sidebar, because it isn’t required to understand what’s going on in this chapter as well in the entire book.

But I do realize that some of the more tech-savvy readers will expect at least some explanation. I’ll only keep it brief, though.

Throughout the tutorial, you can use a command-line interpreter of your choice.

I assume that you understand Ruby code, know how to configure and use RubyGems, and that you already have installed Cucumber on your computer using the gem install cucumbercommand.

I will also be writing the simplest implementation code possible and testing it with the simplest testing code I could think of—which means that the result will not be the best representation of the automation layer code in the world.

Most teams I know of use Cucumber and Gherkin to end-to-end integration tests with headless web browser automation tools like Selenium. Selenium-driven specifications simulate a user working with a real instance of the system.

Personally, I like to use an approach based on Domain-Driven Design and write Cucumber tests that validate only the domain layer without testing end-to-end integrations. Throughout this sidebar, I will use neither of these approaches. I will only write simple mocks and simple unit tests.

If you want to learn more about the automation aspects of Cucumber and Gherkin, read "The Cucumber Book" by Matt Wynne and Aslak Hellesøy (The Pragmatic Programmers, 2012.) If you are a Java person, you might want to have a look at "BDD in Action" by John Ferguson Smart (Manning, 2014.) The first thing we need to do is make a directory where we’ll store our scenarios and tests.

Then we can run Cucumber and see what happens, even though we don’t have anything yet.

Tab

Table 2.3le 2.3 SpecmmSpecific sific steps teps matcmatched whed with thith their seir step detep definifinitiontionss

S

Stteepp’’s s ccoonntteenntt SStteep p ddeeffiinniittiioonn’’s s ccooddee PPuurrppoossee

Mike, a member of our team @mike = User.create(name: "Mike") Create Mike’s account that it isn’t 2 PM yet Time.freeze("1 PM") Freeze the time of our system’s

simulation to make sure the event is valid Mike chooses 2 PM as a

start time for his meeting

@meeting =

Meeting.create(start_time: "2 PM", user: @mike)

Attempt to save Mike’s meeting in the database

he shouldn’t be able to save his meeting

Executing cucumber --init will create a file structure needed to put our scenarios in the specification suite and write the tests.

The file structure is simple enough. You put your .featurefiles in the features

directory and you put step definitions in features/step_definitions. The directory is used for configuring the specification suite, so we won’t

support

worry about it for the purpose of our brief tutorial.

Let’s put our specification in the features directory and execute cucumber

again. We can call the file scheduling.feature, but the name doesn’t matter to Cucumber. It will read everything from the features directory anyway, so you can name your feature files as you see fit.

$ cucumber

No such file or directory - features. You can use `cucumber --init` to get started.

$ cucumber --init create features

create features/step_definitions create features/support

create features/support/env.rb

©Manning Publications Co. We welcome reader comments about anything in the manuscript - other than typos and

54

Cucumber detected our specification! You can also see at the end of the listing that it automatically generated some templates for step definitions even though we don’t have any testing code yet. The step definitions are marked as pending and will continue to be unless we put code inside. An observant eye will notice that steps and step definitions are matched through regular expressions.

Whenever Cucumber detects a regular expressions, it will allow you to use the matched string as an argument in the step definition.

$ cucumber

Feature: Scheduling

Because scheduling is a huge functionality, this specification file describes only the most important high-level scenario.

You can find more detailed scenarios in the rest of the files inside the "meetings" folder in the specification suite.

Scenario: Creating a new meeting successfully Given Mike, a member of our team

And that it isn't 2 PM yet

When Mike chooses 2 PM as a start time for his meeting Then he should be able to save his meeting

Scenario: Failing at creating a new meeting Given Mike, a member of our team

And that it's already 3 PM

When Mike chooses 2 PM as a start time for his meeting Then he shouldn't be able to save his meeting

2 scenarios (2 undefined) 8 steps (8 undefined) 0m0.236s

You can implement step definitions for undefined steps with these snippets:

Given(/^Mike, a member of our team$/) do

pending # Write code here that turns the phrase above into concrete actions end

Given(/^that it isn't (\d+) PM yet$/) do |arg1|

pending # Write code here that turns the phrase above into concrete actions end

When(/^Mike chooses (\d+) PM as a start time for his meeting$/) do |arg1|

pending # Write code here that turns the phrase above into concrete actions end

Then(/^he should be able to save his meeting$/) do

pending # Write code here that turns the phrase above into concrete actions end

Given(/^that it's already (\d+) PM$/) do |arg1|

pending # Write code here that turns the phrase above into concrete actions end

Then(/^he shouldn't be able to save his meeting$/) do

pending # Write code here that turns the phrase above into concrete actions end

55

I have already written most of our testing code for table 2.2.3, but I have to do a few improvements. First, I create a scheduling.rb file in

. I will save all my testing code there. My first

/features/step_definitions

serious coding will be creating simple classes such as Userand Meeting. The classes will simulate simplified models of two domain concepts: users and meetings.

For the purpose of this sidebar, I didn’t include any database connection or persistence methods. We assume that the model is saved when @savedis set to

. I just wanted to keep things simple and make the tests run.

true

The second big part of my code will be using a Ruby library called Timecop to freeze time in our testing environment. Timecopis a gem providing "time travel",

"time freezing", and "time acceleration" capabilities, making it simple to test time-dependent code. It provides a unified method to mock date and time classes in Ruby in a single call. Why would I want to do that? Look at the following lines of our specification:

Given that it isn’t 2 PM yet Given that it’s already 3 PM

©Manning Publications Co. We welcome reader comments about anything in the manuscript - other than typos and

56

We need to ensure that the current time will always be set to 2 PM in the first scenario and to 3 PM in the second scenario, regardless of the time in the real world. The first scenario will allow Mike to create his event at 2 PM because every time we run the simulation, we will make sure that time is frozen before 2 PM. But in the second scenario, we will froze time after 3 PM, and so we will be able to test whether Mike will not be able to create a meeting earlier.

The third important thing is that I will also use RSpec to write my test assertions.

RSpec is a Behavior-Driven Development testing framework for Ruby. You can easily combine RSpec with Cucumber thanks to the rspec-expectations

library. If you’re using bundler, add the rspec-expectations gem to your Gemfile. Cucumber will automatically load RSpec’s matchers and expectation methods. The assertions check if the output expected in the feature file matches the output from the executed tests. If it doesn’t, RSpec will raise an error and stop executing the tests.

To use Timecop and RSpec with Cucumber, we need to first install the required libraries using RubyGems.

As you can see, rspec expectations also loaded a few supporting libraries.

That’s perfectly fine. Right now, to use Timecopand rspec-expectationsin the testing code I will write, I have to first add

at the top of the scheduling.rbfile.

With classes like Useror Meetingand our two testing libraries, I can now write testing code for the step definitions—so I add the following code add the bottom of the the scheduling.rbfile.

$ gem install timecop

Fetching: timecop-0.8.1.gem (100%) Successfully installed timecop-0.8.1 1 gem installed

$ gem install rspec-expectations Fetching: rspec-support-3.5.0.gem (100%) Fetching: diff-lcs-1.2.5.gem (100%)

Fetching: rspec-expectations-3.5.0.gem (100%) Successfully installed rspec-support-3.5.0 Successfully installed diff-lcs-1.2.5

Successfully installed rspec-expectations-3.5.0 3 gems installed

require 'timecop'

require 'rspec/expectations'

57

If we execute cucumberagain, we will see that the testing code works well and our scenarios pass. (I cut out the content of the scenarios to make the listing shorter.)

Hooray!

Only together Gherkin and Cucumber work as an end-to-end tool. The specification layer elaborates requirements and acceptance criteria in natural language using important real world examples. The examples, expressed in steps, become links that connect acceptance criteria with automated tests—the automation layer—through regular expressions. The tests make sure if the system behaves as the requirements should require it to.

Given(/^Mike, a member of our team$/) do @mike = User.new

@mike.save(name: "Mike") end

Given(/^that it isn't (\d+) PM yet$/) do |arg1|

# We subtract 1 from arg1 because we need to make sure # that time is frozen before arg1

Timecop.freeze Time.parse("January 1st, 2016 #{arg1.to_i - 1} PM") end

When(/^Mike chooses (\d+) PM as a start time for his meeting$/) do |arg1|

time = Time.parse("January 1st, 2016 #{arg1} PM") @meeting = Meeting.new

@meeting.save(start_time: time) @mike.meetings << @meeting end

Then(/^he should be able to save his meeting$/) do expect(@mike.meetings.first.saved?).to eq true end

Given(/^that it's already (\d+) PM$/) do |arg1|

Timecop.freeze Time.parse("January 1st, 2016 #{arg1} PM") end

Then(/^he shouldn't be able to save his meeting$/) do expect(@mike.meetings.first.saved?).to eq false end

$ cucumber

(...)

2 scenarios (2 passed) 8 steps (8 passed) 0m0.057s

©Manning Publications Co. We welcome reader comments about anything in the manuscript - other than typos and

58

Figure 2.10 Examples, expressed in steps, act as a link between Gherkin and Cucumber.

Figure 2.10 Examples, expressed in steps, act as a link between Gherkin and Cucumber.

Steps, scenarios and feature files constitute the

Steps, scenarios and feature files constitute the specification layer. Step definitions andspecification layer. Step definitions and the testing code are the

the testing code are the fundaments of the automation layer. Together, the specificationfundaments of the automation layer. Together, the specification layer and the automation layer create

layer and the automation layer create an executable specification suitean executable specification suite

You could write scenarios without using Cucumber—but they wouldn’t be executable. And if they weren’t executable, they wouldn’t become acceptance tests.

Without the acceptance tests, you wouldn’t easily know when to update the scenarios when the system breaks or changes. And why would you keep outdated scenarios? You’d probably discard them just as you discard the sticky notes with user stories after you finish the story.

That’s why the system works only if every acceptance criterion becomes a scenario, scenarios become tests, and tests control the design of the system by validating the system frequently with the testing code. These three steps make a cohesive whole: a specification process that covers your development efforts from the start to its end.

59

1.